Merge "master" branch into "db".
authorBen Pfaff <blp@nicira.com>
Wed, 2 Dec 2009 19:49:53 +0000 (11:49 -0800)
committerBen Pfaff <blp@nicira.com>
Wed, 2 Dec 2009 19:49:53 +0000 (11:49 -0800)
191 files changed:
COPYING
Makefile.am
SubmittingPatches
configure.ac
extras/ezio/automake.mk
extras/ezio/ezio-term.c
extras/ezio/ovs-switchui.c
lib/aes128.c [new file with mode: 0644]
lib/aes128.h [new file with mode: 0644]
lib/automake.mk
lib/byteq.c [moved from extras/ezio/byteq.c with 81% similarity]
lib/byteq.h [moved from extras/ezio/byteq.h with 83% similarity]
lib/cfg.c
lib/command-line.c
lib/command-line.h
lib/common-syn.man [new file with mode: 0644]
lib/compiler.h
lib/coverage.c
lib/daemon-syn.man [new file with mode: 0644]
lib/daemon.c
lib/daemon.h
lib/dhcp-client.c
lib/dpif-netdev.c
lib/dynamic-string.c
lib/dynamic-string.h
lib/hash.c
lib/hash.h
lib/hmap.h
lib/json.c [new file with mode: 0644]
lib/json.h [new file with mode: 0644]
lib/jsonrpc.c [new file with mode: 0644]
lib/jsonrpc.h [new file with mode: 0644]
lib/learning-switch.c
lib/lockfile.c [new file with mode: 0644]
lib/lockfile.h [new file with mode: 0644]
lib/netdev-linux.c
lib/ovsdb-data.c [new file with mode: 0644]
lib/ovsdb-data.h [new file with mode: 0644]
lib/ovsdb-error.c [new file with mode: 0644]
lib/ovsdb-error.h [new file with mode: 0644]
lib/ovsdb-idl-provider.h [new file with mode: 0644]
lib/ovsdb-idl.c [new file with mode: 0644]
lib/ovsdb-idl.h [new file with mode: 0644]
lib/ovsdb-parser.c [new file with mode: 0644]
lib/ovsdb-parser.h [new file with mode: 0644]
lib/ovsdb-types.c [new file with mode: 0644]
lib/ovsdb-types.h [new file with mode: 0644]
lib/poll-loop.c
lib/process.c
lib/queue.h
lib/rconn.c
lib/reconnect.c [new file with mode: 0644]
lib/reconnect.h [new file with mode: 0644]
lib/sha1.c
lib/sha1.h
lib/shash.c
lib/shash.h
lib/sort.c [new file with mode: 0644]
lib/sort.h [new file with mode: 0644]
lib/stp.c
lib/stream-fd.c [new file with mode: 0644]
lib/stream-fd.h [new file with mode: 0644]
lib/stream-provider.h [new file with mode: 0644]
lib/stream-tcp.c [new file with mode: 0644]
lib/stream-unix.c [new file with mode: 0644]
lib/stream.c [new file with mode: 0644]
lib/stream.h [new file with mode: 0644]
lib/svec.c
lib/svec.h
lib/timeval.c
lib/timeval.h
lib/unicode.c [new file with mode: 0644]
lib/unicode.h [new file with mode: 0644]
lib/unixctl.c
lib/unixctl.h
lib/util.c
lib/util.h
lib/uuid.c [new file with mode: 0644]
lib/uuid.h [new file with mode: 0644]
lib/vlog-modules.def
lib/vlog-syn.man [new file with mode: 0644]
lib/vlog.c
ofproto/discovery.c
ofproto/executer.c
ofproto/in-band.c
ofproto/ofproto.c
ofproto/pinsched.c
ofproto/pktbuf.c
ofproto/status.c
ovsdb/SPECS [new file with mode: 0644]
ovsdb/automake.mk [new file with mode: 0644]
ovsdb/column.c [new file with mode: 0644]
ovsdb/column.h [new file with mode: 0644]
ovsdb/condition.c [new file with mode: 0644]
ovsdb/condition.h [new file with mode: 0644]
ovsdb/execution.c [new file with mode: 0644]
ovsdb/file.c [new file with mode: 0644]
ovsdb/file.h [new file with mode: 0644]
ovsdb/jsonrpc-server.c [new file with mode: 0644]
ovsdb/jsonrpc-server.h [new file with mode: 0644]
ovsdb/log.c [new file with mode: 0644]
ovsdb/log.h [new file with mode: 0644]
ovsdb/ovsdb-client.1.in [new file with mode: 0644]
ovsdb/ovsdb-client.c [new file with mode: 0644]
ovsdb/ovsdb-idlc.1 [new file with mode: 0644]
ovsdb/ovsdb-idlc.in [new file with mode: 0755]
ovsdb/ovsdb-server.1.in [new file with mode: 0644]
ovsdb/ovsdb-server.c [new file with mode: 0644]
ovsdb/ovsdb-tool.1.in [new file with mode: 0644]
ovsdb/ovsdb-tool.c [new file with mode: 0644]
ovsdb/ovsdb.c [new file with mode: 0644]
ovsdb/ovsdb.h [new file with mode: 0644]
ovsdb/query.c [new file with mode: 0644]
ovsdb/query.h [new file with mode: 0644]
ovsdb/row.c [new file with mode: 0644]
ovsdb/row.h [new file with mode: 0644]
ovsdb/simplejson/__init__.py [new file with mode: 0644]
ovsdb/simplejson/_speedups.c [new file with mode: 0644]
ovsdb/simplejson/decoder.py [new file with mode: 0644]
ovsdb/simplejson/encoder.py [new file with mode: 0644]
ovsdb/simplejson/scanner.py [new file with mode: 0644]
ovsdb/simplejson/tests/__init__.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_check_circular.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_decode.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_default.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_dump.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_encode_basestring_ascii.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_fail.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_float.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_indent.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_pass1.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_pass2.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_pass3.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_recursion.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_scanstring.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_separators.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_unicode.py [new file with mode: 0644]
ovsdb/simplejson/tool.py [new file with mode: 0644]
ovsdb/table.c [new file with mode: 0644]
ovsdb/table.h [new file with mode: 0644]
ovsdb/transaction.c [new file with mode: 0644]
ovsdb/transaction.h [new file with mode: 0644]
ovsdb/trigger.c [new file with mode: 0644]
ovsdb/trigger.h [new file with mode: 0644]
tests/.gitignore
tests/aes128.at [new file with mode: 0644]
tests/automake.mk
tests/dir_name.at [new file with mode: 0644]
tests/idltest.ovsidl [new file with mode: 0644]
tests/json.at [new file with mode: 0644]
tests/jsonrpc.at [new file with mode: 0644]
tests/library.at
tests/lockfile.at [new file with mode: 0644]
tests/ovsdb-column.at [new file with mode: 0644]
tests/ovsdb-condition.at [new file with mode: 0644]
tests/ovsdb-data.at [new file with mode: 0644]
tests/ovsdb-execution.at [new file with mode: 0644]
tests/ovsdb-file.at [new file with mode: 0644]
tests/ovsdb-idl.at [new file with mode: 0644]
tests/ovsdb-log.at [new file with mode: 0644]
tests/ovsdb-monitor.at [new file with mode: 0644]
tests/ovsdb-query.at [new file with mode: 0644]
tests/ovsdb-row.at [new file with mode: 0644]
tests/ovsdb-server.at [new file with mode: 0644]
tests/ovsdb-table.at [new file with mode: 0644]
tests/ovsdb-transaction.at [new file with mode: 0644]
tests/ovsdb-trigger.at [new file with mode: 0644]
tests/ovsdb-types.at [new file with mode: 0644]
tests/ovsdb.at [new file with mode: 0644]
tests/reconnect.at [new file with mode: 0644]
tests/test-aes128.c [new file with mode: 0644]
tests/test-classifier.c
tests/test-dir_name.c [new file with mode: 0644]
tests/test-json.c [new file with mode: 0644]
tests/test-jsonrpc.c [new file with mode: 0644]
tests/test-lockfile.c [new file with mode: 0644]
tests/test-ovsdb.c [new file with mode: 0644]
tests/test-reconnect.c [new file with mode: 0644]
tests/test-timeval.c [new file with mode: 0644]
tests/test-uuid.c [new file with mode: 0644]
tests/testsuite.at
tests/timeval.at [new file with mode: 0644]
tests/uuid.at [new file with mode: 0644]
tests/uuidfilt.pl [new file with mode: 0755]
utilities/ovs-dpctl.c
utilities/ovs-ofctl.c
vswitchd/automake.mk
vswitchd/bridge.c
vswitchd/ovs-vswitchd.c
vswitchd/proc-net-compat.c
vswitchd/vswitch-idl.ovsidl [new file with mode: 0644]

diff --git a/COPYING b/COPYING
index 134f02b..3f0659d 100644 (file)
--- a/COPYING
+++ b/COPYING
@@ -22,3 +22,27 @@ Public License, version 2.
 Files under the xenserver directory are licensed on a file-by-file
 basis.  Some files are under an uncertain license that may not be
 DFSG-compliant or GPL-compatible.  Refer to each file for details.
+
+The files under ovsdb/simplejson are covered by the following license:
+
+    Copyright (c) 2006 Bob Ippolito
+
+    Permission is hereby granted, free of charge, to any person
+    obtaining a copy of this software and associated documentation
+    files (the "Software"), to deal in the Software without
+    restriction, including without limitation the rights to use, copy,
+    modify, merge, publish, distribute, sublicense, and/or sell copies
+    of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+    DEALINGS IN THE SOFTWARE.
index de51e10..164ca3e 100644 (file)
@@ -25,6 +25,7 @@ else
 AM_LDFLAGS = -export-dynamic
 endif
 
+BUILT_SOURCES =
 CLEANFILES =
 DISTCLEANFILES =
 EXTRA_DIST = INSTALL.bridge \
@@ -32,7 +33,8 @@ EXTRA_DIST = INSTALL.bridge \
        INSTALL.userspace \
        INSTALL.OpenFlow \
        INSTALL.SSL \
-       INSTALL.XenServer 
+       INSTALL.XenServer \
+       README-gcov
 bin_PROGRAMS =
 sbin_PROGRAMS =
 bin_SCRIPTS =
@@ -41,16 +43,18 @@ dist_man_MANS =
 dist_pkgdata_SCRIPTS =
 dist_sbin_SCRIPTS =
 man_MANS =
+noinst_DATA =
 noinst_HEADERS =
 noinst_LIBRARIES =
 noinst_PROGRAMS =
 noinst_SCRIPTS =
+SUFFIXES =
 
 EXTRA_DIST += soexpand.pl
 
 ro_c = echo '/* -*- mode: c; buffer-read-only: t -*- */'
 
-SUFFIXES = .in
+SUFFIXES += .in
 .in:
        $(PERL) $(srcdir)/soexpand.pl -I$(srcdir) < $< | \
            sed \
@@ -63,6 +67,7 @@ SUFFIXES = .in
                 -e 's,[@]localstatedir[@],$(localstatedir),g' \
                 -e 's,[@]pkgdatadir[@],$(pkgdatadir),g' \
                 -e 's,[@]sysconfdir[@],$(sysconfdir),g' \
+                -e 's,[@]abs_top_srcdir[@],$(abs_top_srcdir),g' \
             > $@.tmp
        @if head -n 1 $@.tmp | grep -q '#!'; then \
            echo chmod +x $@.tmp; \
@@ -78,5 +83,6 @@ include include/automake.mk
 include third-party/automake.mk
 include debian/automake.mk
 include vswitchd/automake.mk
+include ovsdb/automake.mk
 include xenserver/automake.mk
 include extras/ezio/automake.mk
index 917cddb..280f11e 100644 (file)
@@ -193,7 +193,7 @@ index 32647ea..00cffbc 100644
      /* Get rid of deleted bridges and add new bridges. */
      svec_sort(&old_br);
 @@ -793,7 +780,7 @@ bridge_create(const char *name)
-     br = xcalloc(1, sizeof *br);
+     br = xzalloc(sizeof *br);
  
      error = dpif_create(name, &br->dpif);
 -    if (error == EEXIST) {
index 300b21e..a94ff93 100644 (file)
@@ -38,6 +38,8 @@ AC_USE_SYSTEM_EXTENSIONS
 AC_C_BIGENDIAN
 AC_SYS_LARGEFILE
 
+AC_SEARCH_LIBS([pow], [m])
+
 OVS_CHECK_COVERAGE
 OVS_CHECK_NDEBUG
 OVS_CHECK_NETLINK
index 7c7db24..eae11cd 100644 (file)
@@ -25,8 +25,6 @@ install-data-hook:
 
 bin_PROGRAMS += extras/ezio/ezio-term
 extras_ezio_ezio_term_SOURCES = \
-       extras/ezio/byteq.c \
-       extras/ezio/byteq.h \
        extras/ezio/ezio-term.c \
        extras/ezio/ezio.c \
        extras/ezio/ezio.h \
index 846ccfd..8f12a7b 100644 (file)
@@ -26,8 +26,8 @@
 #include <stdlib.h>
 #include <term.h>
 #include <unistd.h>
+#include "byteq.h"
 #include "command-line.h"
-#include "extras/ezio/byteq.h"
 #include "extras/ezio/tty.h"
 #include "extras/ezio/vt.h"
 #include "daemon.h"
index cd1f352..bd07e86 100644 (file)
@@ -1247,7 +1247,7 @@ allocate_message(struct message **msgp)
 {
     if (!*msgp) {
         /* Allocate and initialize message. */
-        *msgp = xcalloc(1, sizeof **msgp);
+        *msgp = xzalloc(sizeof **msgp);
         (*msgp)->index = n_messages;
 
         /* Add to list of messages. */
diff --git a/lib/aes128.c b/lib/aes128.c
new file mode 100644 (file)
index 0000000..aa7f560
--- /dev/null
@@ -0,0 +1,846 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Based on rijndael.txt by Philip J. Erdelsky, downloaded from
+ * http://www.efgh.com/software/rijndael.htm on September 24, 2009.  The
+ * license information there is: "Public domain; no restrictions on use."
+ * The Apache license above applies only to Nicira's modifications to the
+ * original code.
+ */
+
+#include <config.h>
+
+#include "aes128.h"
+
+#include <assert.h>
+
+#include "util.h"
+
+static const uint32_t Te0[256] = {
+    0xc66363a5U, 0xf87c7c84U, 0xee777799U, 0xf67b7b8dU,
+    0xfff2f20dU, 0xd66b6bbdU, 0xde6f6fb1U, 0x91c5c554U,
+    0x60303050U, 0x02010103U, 0xce6767a9U, 0x562b2b7dU,
+    0xe7fefe19U, 0xb5d7d762U, 0x4dababe6U, 0xec76769aU,
+    0x8fcaca45U, 0x1f82829dU, 0x89c9c940U, 0xfa7d7d87U,
+    0xeffafa15U, 0xb25959ebU, 0x8e4747c9U, 0xfbf0f00bU,
+    0x41adadecU, 0xb3d4d467U, 0x5fa2a2fdU, 0x45afafeaU,
+    0x239c9cbfU, 0x53a4a4f7U, 0xe4727296U, 0x9bc0c05bU,
+    0x75b7b7c2U, 0xe1fdfd1cU, 0x3d9393aeU, 0x4c26266aU,
+    0x6c36365aU, 0x7e3f3f41U, 0xf5f7f702U, 0x83cccc4fU,
+    0x6834345cU, 0x51a5a5f4U, 0xd1e5e534U, 0xf9f1f108U,
+    0xe2717193U, 0xabd8d873U, 0x62313153U, 0x2a15153fU,
+    0x0804040cU, 0x95c7c752U, 0x46232365U, 0x9dc3c35eU,
+    0x30181828U, 0x379696a1U, 0x0a05050fU, 0x2f9a9ab5U,
+    0x0e070709U, 0x24121236U, 0x1b80809bU, 0xdfe2e23dU,
+    0xcdebeb26U, 0x4e272769U, 0x7fb2b2cdU, 0xea75759fU,
+    0x1209091bU, 0x1d83839eU, 0x582c2c74U, 0x341a1a2eU,
+    0x361b1b2dU, 0xdc6e6eb2U, 0xb45a5aeeU, 0x5ba0a0fbU,
+    0xa45252f6U, 0x763b3b4dU, 0xb7d6d661U, 0x7db3b3ceU,
+    0x5229297bU, 0xdde3e33eU, 0x5e2f2f71U, 0x13848497U,
+    0xa65353f5U, 0xb9d1d168U, 0x00000000U, 0xc1eded2cU,
+    0x40202060U, 0xe3fcfc1fU, 0x79b1b1c8U, 0xb65b5bedU,
+    0xd46a6abeU, 0x8dcbcb46U, 0x67bebed9U, 0x7239394bU,
+    0x944a4adeU, 0x984c4cd4U, 0xb05858e8U, 0x85cfcf4aU,
+    0xbbd0d06bU, 0xc5efef2aU, 0x4faaaae5U, 0xedfbfb16U,
+    0x864343c5U, 0x9a4d4dd7U, 0x66333355U, 0x11858594U,
+    0x8a4545cfU, 0xe9f9f910U, 0x04020206U, 0xfe7f7f81U,
+    0xa05050f0U, 0x783c3c44U, 0x259f9fbaU, 0x4ba8a8e3U,
+    0xa25151f3U, 0x5da3a3feU, 0x804040c0U, 0x058f8f8aU,
+    0x3f9292adU, 0x219d9dbcU, 0x70383848U, 0xf1f5f504U,
+    0x63bcbcdfU, 0x77b6b6c1U, 0xafdada75U, 0x42212163U,
+    0x20101030U, 0xe5ffff1aU, 0xfdf3f30eU, 0xbfd2d26dU,
+    0x81cdcd4cU, 0x180c0c14U, 0x26131335U, 0xc3ecec2fU,
+    0xbe5f5fe1U, 0x359797a2U, 0x884444ccU, 0x2e171739U,
+    0x93c4c457U, 0x55a7a7f2U, 0xfc7e7e82U, 0x7a3d3d47U,
+    0xc86464acU, 0xba5d5de7U, 0x3219192bU, 0xe6737395U,
+    0xc06060a0U, 0x19818198U, 0x9e4f4fd1U, 0xa3dcdc7fU,
+    0x44222266U, 0x542a2a7eU, 0x3b9090abU, 0x0b888883U,
+    0x8c4646caU, 0xc7eeee29U, 0x6bb8b8d3U, 0x2814143cU,
+    0xa7dede79U, 0xbc5e5ee2U, 0x160b0b1dU, 0xaddbdb76U,
+    0xdbe0e03bU, 0x64323256U, 0x743a3a4eU, 0x140a0a1eU,
+    0x924949dbU, 0x0c06060aU, 0x4824246cU, 0xb85c5ce4U,
+    0x9fc2c25dU, 0xbdd3d36eU, 0x43acacefU, 0xc46262a6U,
+    0x399191a8U, 0x319595a4U, 0xd3e4e437U, 0xf279798bU,
+    0xd5e7e732U, 0x8bc8c843U, 0x6e373759U, 0xda6d6db7U,
+    0x018d8d8cU, 0xb1d5d564U, 0x9c4e4ed2U, 0x49a9a9e0U,
+    0xd86c6cb4U, 0xac5656faU, 0xf3f4f407U, 0xcfeaea25U,
+    0xca6565afU, 0xf47a7a8eU, 0x47aeaee9U, 0x10080818U,
+    0x6fbabad5U, 0xf0787888U, 0x4a25256fU, 0x5c2e2e72U,
+    0x381c1c24U, 0x57a6a6f1U, 0x73b4b4c7U, 0x97c6c651U,
+    0xcbe8e823U, 0xa1dddd7cU, 0xe874749cU, 0x3e1f1f21U,
+    0x964b4bddU, 0x61bdbddcU, 0x0d8b8b86U, 0x0f8a8a85U,
+    0xe0707090U, 0x7c3e3e42U, 0x71b5b5c4U, 0xcc6666aaU,
+    0x904848d8U, 0x06030305U, 0xf7f6f601U, 0x1c0e0e12U,
+    0xc26161a3U, 0x6a35355fU, 0xae5757f9U, 0x69b9b9d0U,
+    0x17868691U, 0x99c1c158U, 0x3a1d1d27U, 0x279e9eb9U,
+    0xd9e1e138U, 0xebf8f813U, 0x2b9898b3U, 0x22111133U,
+    0xd26969bbU, 0xa9d9d970U, 0x078e8e89U, 0x339494a7U,
+    0x2d9b9bb6U, 0x3c1e1e22U, 0x15878792U, 0xc9e9e920U,
+    0x87cece49U, 0xaa5555ffU, 0x50282878U, 0xa5dfdf7aU,
+    0x038c8c8fU, 0x59a1a1f8U, 0x09898980U, 0x1a0d0d17U,
+    0x65bfbfdaU, 0xd7e6e631U, 0x844242c6U, 0xd06868b8U,
+    0x824141c3U, 0x299999b0U, 0x5a2d2d77U, 0x1e0f0f11U,
+    0x7bb0b0cbU, 0xa85454fcU, 0x6dbbbbd6U, 0x2c16163aU,
+};
+
+static const uint32_t Te1[256] = {
+    0xa5c66363U, 0x84f87c7cU, 0x99ee7777U, 0x8df67b7bU,
+    0x0dfff2f2U, 0xbdd66b6bU, 0xb1de6f6fU, 0x5491c5c5U,
+    0x50603030U, 0x03020101U, 0xa9ce6767U, 0x7d562b2bU,
+    0x19e7fefeU, 0x62b5d7d7U, 0xe64dababU, 0x9aec7676U,
+    0x458fcacaU, 0x9d1f8282U, 0x4089c9c9U, 0x87fa7d7dU,
+    0x15effafaU, 0xebb25959U, 0xc98e4747U, 0x0bfbf0f0U,
+    0xec41adadU, 0x67b3d4d4U, 0xfd5fa2a2U, 0xea45afafU,
+    0xbf239c9cU, 0xf753a4a4U, 0x96e47272U, 0x5b9bc0c0U,
+    0xc275b7b7U, 0x1ce1fdfdU, 0xae3d9393U, 0x6a4c2626U,
+    0x5a6c3636U, 0x417e3f3fU, 0x02f5f7f7U, 0x4f83ccccU,
+    0x5c683434U, 0xf451a5a5U, 0x34d1e5e5U, 0x08f9f1f1U,
+    0x93e27171U, 0x73abd8d8U, 0x53623131U, 0x3f2a1515U,
+    0x0c080404U, 0x5295c7c7U, 0x65462323U, 0x5e9dc3c3U,
+    0x28301818U, 0xa1379696U, 0x0f0a0505U, 0xb52f9a9aU,
+    0x090e0707U, 0x36241212U, 0x9b1b8080U, 0x3ddfe2e2U,
+    0x26cdebebU, 0x694e2727U, 0xcd7fb2b2U, 0x9fea7575U,
+    0x1b120909U, 0x9e1d8383U, 0x74582c2cU, 0x2e341a1aU,
+    0x2d361b1bU, 0xb2dc6e6eU, 0xeeb45a5aU, 0xfb5ba0a0U,
+    0xf6a45252U, 0x4d763b3bU, 0x61b7d6d6U, 0xce7db3b3U,
+    0x7b522929U, 0x3edde3e3U, 0x715e2f2fU, 0x97138484U,
+    0xf5a65353U, 0x68b9d1d1U, 0x00000000U, 0x2cc1ededU,
+    0x60402020U, 0x1fe3fcfcU, 0xc879b1b1U, 0xedb65b5bU,
+    0xbed46a6aU, 0x468dcbcbU, 0xd967bebeU, 0x4b723939U,
+    0xde944a4aU, 0xd4984c4cU, 0xe8b05858U, 0x4a85cfcfU,
+    0x6bbbd0d0U, 0x2ac5efefU, 0xe54faaaaU, 0x16edfbfbU,
+    0xc5864343U, 0xd79a4d4dU, 0x55663333U, 0x94118585U,
+    0xcf8a4545U, 0x10e9f9f9U, 0x06040202U, 0x81fe7f7fU,
+    0xf0a05050U, 0x44783c3cU, 0xba259f9fU, 0xe34ba8a8U,
+    0xf3a25151U, 0xfe5da3a3U, 0xc0804040U, 0x8a058f8fU,
+    0xad3f9292U, 0xbc219d9dU, 0x48703838U, 0x04f1f5f5U,
+    0xdf63bcbcU, 0xc177b6b6U, 0x75afdadaU, 0x63422121U,
+    0x30201010U, 0x1ae5ffffU, 0x0efdf3f3U, 0x6dbfd2d2U,
+    0x4c81cdcdU, 0x14180c0cU, 0x35261313U, 0x2fc3ececU,
+    0xe1be5f5fU, 0xa2359797U, 0xcc884444U, 0x392e1717U,
+    0x5793c4c4U, 0xf255a7a7U, 0x82fc7e7eU, 0x477a3d3dU,
+    0xacc86464U, 0xe7ba5d5dU, 0x2b321919U, 0x95e67373U,
+    0xa0c06060U, 0x98198181U, 0xd19e4f4fU, 0x7fa3dcdcU,
+    0x66442222U, 0x7e542a2aU, 0xab3b9090U, 0x830b8888U,
+    0xca8c4646U, 0x29c7eeeeU, 0xd36bb8b8U, 0x3c281414U,
+    0x79a7dedeU, 0xe2bc5e5eU, 0x1d160b0bU, 0x76addbdbU,
+    0x3bdbe0e0U, 0x56643232U, 0x4e743a3aU, 0x1e140a0aU,
+    0xdb924949U, 0x0a0c0606U, 0x6c482424U, 0xe4b85c5cU,
+    0x5d9fc2c2U, 0x6ebdd3d3U, 0xef43acacU, 0xa6c46262U,
+    0xa8399191U, 0xa4319595U, 0x37d3e4e4U, 0x8bf27979U,
+    0x32d5e7e7U, 0x438bc8c8U, 0x596e3737U, 0xb7da6d6dU,
+    0x8c018d8dU, 0x64b1d5d5U, 0xd29c4e4eU, 0xe049a9a9U,
+    0xb4d86c6cU, 0xfaac5656U, 0x07f3f4f4U, 0x25cfeaeaU,
+    0xafca6565U, 0x8ef47a7aU, 0xe947aeaeU, 0x18100808U,
+    0xd56fbabaU, 0x88f07878U, 0x6f4a2525U, 0x725c2e2eU,
+    0x24381c1cU, 0xf157a6a6U, 0xc773b4b4U, 0x5197c6c6U,
+    0x23cbe8e8U, 0x7ca1ddddU, 0x9ce87474U, 0x213e1f1fU,
+    0xdd964b4bU, 0xdc61bdbdU, 0x860d8b8bU, 0x850f8a8aU,
+    0x90e07070U, 0x427c3e3eU, 0xc471b5b5U, 0xaacc6666U,
+    0xd8904848U, 0x05060303U, 0x01f7f6f6U, 0x121c0e0eU,
+    0xa3c26161U, 0x5f6a3535U, 0xf9ae5757U, 0xd069b9b9U,
+    0x91178686U, 0x5899c1c1U, 0x273a1d1dU, 0xb9279e9eU,
+    0x38d9e1e1U, 0x13ebf8f8U, 0xb32b9898U, 0x33221111U,
+    0xbbd26969U, 0x70a9d9d9U, 0x89078e8eU, 0xa7339494U,
+    0xb62d9b9bU, 0x223c1e1eU, 0x92158787U, 0x20c9e9e9U,
+    0x4987ceceU, 0xffaa5555U, 0x78502828U, 0x7aa5dfdfU,
+    0x8f038c8cU, 0xf859a1a1U, 0x80098989U, 0x171a0d0dU,
+    0xda65bfbfU, 0x31d7e6e6U, 0xc6844242U, 0xb8d06868U,
+    0xc3824141U, 0xb0299999U, 0x775a2d2dU, 0x111e0f0fU,
+    0xcb7bb0b0U, 0xfca85454U, 0xd66dbbbbU, 0x3a2c1616U,
+};
+
+static const uint32_t Te2[256] = {
+    0x63a5c663U, 0x7c84f87cU, 0x7799ee77U, 0x7b8df67bU,
+    0xf20dfff2U, 0x6bbdd66bU, 0x6fb1de6fU, 0xc55491c5U,
+    0x30506030U, 0x01030201U, 0x67a9ce67U, 0x2b7d562bU,
+    0xfe19e7feU, 0xd762b5d7U, 0xabe64dabU, 0x769aec76U,
+    0xca458fcaU, 0x829d1f82U, 0xc94089c9U, 0x7d87fa7dU,
+    0xfa15effaU, 0x59ebb259U, 0x47c98e47U, 0xf00bfbf0U,
+    0xadec41adU, 0xd467b3d4U, 0xa2fd5fa2U, 0xafea45afU,
+    0x9cbf239cU, 0xa4f753a4U, 0x7296e472U, 0xc05b9bc0U,
+    0xb7c275b7U, 0xfd1ce1fdU, 0x93ae3d93U, 0x266a4c26U,
+    0x365a6c36U, 0x3f417e3fU, 0xf702f5f7U, 0xcc4f83ccU,
+    0x345c6834U, 0xa5f451a5U, 0xe534d1e5U, 0xf108f9f1U,
+    0x7193e271U, 0xd873abd8U, 0x31536231U, 0x153f2a15U,
+    0x040c0804U, 0xc75295c7U, 0x23654623U, 0xc35e9dc3U,
+    0x18283018U, 0x96a13796U, 0x050f0a05U, 0x9ab52f9aU,
+    0x07090e07U, 0x12362412U, 0x809b1b80U, 0xe23ddfe2U,
+    0xeb26cdebU, 0x27694e27U, 0xb2cd7fb2U, 0x759fea75U,
+    0x091b1209U, 0x839e1d83U, 0x2c74582cU, 0x1a2e341aU,
+    0x1b2d361bU, 0x6eb2dc6eU, 0x5aeeb45aU, 0xa0fb5ba0U,
+    0x52f6a452U, 0x3b4d763bU, 0xd661b7d6U, 0xb3ce7db3U,
+    0x297b5229U, 0xe33edde3U, 0x2f715e2fU, 0x84971384U,
+    0x53f5a653U, 0xd168b9d1U, 0x00000000U, 0xed2cc1edU,
+    0x20604020U, 0xfc1fe3fcU, 0xb1c879b1U, 0x5bedb65bU,
+    0x6abed46aU, 0xcb468dcbU, 0xbed967beU, 0x394b7239U,
+    0x4ade944aU, 0x4cd4984cU, 0x58e8b058U, 0xcf4a85cfU,
+    0xd06bbbd0U, 0xef2ac5efU, 0xaae54faaU, 0xfb16edfbU,
+    0x43c58643U, 0x4dd79a4dU, 0x33556633U, 0x85941185U,
+    0x45cf8a45U, 0xf910e9f9U, 0x02060402U, 0x7f81fe7fU,
+    0x50f0a050U, 0x3c44783cU, 0x9fba259fU, 0xa8e34ba8U,
+    0x51f3a251U, 0xa3fe5da3U, 0x40c08040U, 0x8f8a058fU,
+    0x92ad3f92U, 0x9dbc219dU, 0x38487038U, 0xf504f1f5U,
+    0xbcdf63bcU, 0xb6c177b6U, 0xda75afdaU, 0x21634221U,
+    0x10302010U, 0xff1ae5ffU, 0xf30efdf3U, 0xd26dbfd2U,
+    0xcd4c81cdU, 0x0c14180cU, 0x13352613U, 0xec2fc3ecU,
+    0x5fe1be5fU, 0x97a23597U, 0x44cc8844U, 0x17392e17U,
+    0xc45793c4U, 0xa7f255a7U, 0x7e82fc7eU, 0x3d477a3dU,
+    0x64acc864U, 0x5de7ba5dU, 0x192b3219U, 0x7395e673U,
+    0x60a0c060U, 0x81981981U, 0x4fd19e4fU, 0xdc7fa3dcU,
+    0x22664422U, 0x2a7e542aU, 0x90ab3b90U, 0x88830b88U,
+    0x46ca8c46U, 0xee29c7eeU, 0xb8d36bb8U, 0x143c2814U,
+    0xde79a7deU, 0x5ee2bc5eU, 0x0b1d160bU, 0xdb76addbU,
+    0xe03bdbe0U, 0x32566432U, 0x3a4e743aU, 0x0a1e140aU,
+    0x49db9249U, 0x060a0c06U, 0x246c4824U, 0x5ce4b85cU,
+    0xc25d9fc2U, 0xd36ebdd3U, 0xacef43acU, 0x62a6c462U,
+    0x91a83991U, 0x95a43195U, 0xe437d3e4U, 0x798bf279U,
+    0xe732d5e7U, 0xc8438bc8U, 0x37596e37U, 0x6db7da6dU,
+    0x8d8c018dU, 0xd564b1d5U, 0x4ed29c4eU, 0xa9e049a9U,
+    0x6cb4d86cU, 0x56faac56U, 0xf407f3f4U, 0xea25cfeaU,
+    0x65afca65U, 0x7a8ef47aU, 0xaee947aeU, 0x08181008U,
+    0xbad56fbaU, 0x7888f078U, 0x256f4a25U, 0x2e725c2eU,
+    0x1c24381cU, 0xa6f157a6U, 0xb4c773b4U, 0xc65197c6U,
+    0xe823cbe8U, 0xdd7ca1ddU, 0x749ce874U, 0x1f213e1fU,
+    0x4bdd964bU, 0xbddc61bdU, 0x8b860d8bU, 0x8a850f8aU,
+    0x7090e070U, 0x3e427c3eU, 0xb5c471b5U, 0x66aacc66U,
+    0x48d89048U, 0x03050603U, 0xf601f7f6U, 0x0e121c0eU,
+    0x61a3c261U, 0x355f6a35U, 0x57f9ae57U, 0xb9d069b9U,
+    0x86911786U, 0xc15899c1U, 0x1d273a1dU, 0x9eb9279eU,
+    0xe138d9e1U, 0xf813ebf8U, 0x98b32b98U, 0x11332211U,
+    0x69bbd269U, 0xd970a9d9U, 0x8e89078eU, 0x94a73394U,
+    0x9bb62d9bU, 0x1e223c1eU, 0x87921587U, 0xe920c9e9U,
+    0xce4987ceU, 0x55ffaa55U, 0x28785028U, 0xdf7aa5dfU,
+    0x8c8f038cU, 0xa1f859a1U, 0x89800989U, 0x0d171a0dU,
+    0xbfda65bfU, 0xe631d7e6U, 0x42c68442U, 0x68b8d068U,
+    0x41c38241U, 0x99b02999U, 0x2d775a2dU, 0x0f111e0fU,
+    0xb0cb7bb0U, 0x54fca854U, 0xbbd66dbbU, 0x163a2c16U,
+};
+
+static const uint32_t Te3[256] = {
+    0x6363a5c6U, 0x7c7c84f8U, 0x777799eeU, 0x7b7b8df6U,
+    0xf2f20dffU, 0x6b6bbdd6U, 0x6f6fb1deU, 0xc5c55491U,
+    0x30305060U, 0x01010302U, 0x6767a9ceU, 0x2b2b7d56U,
+    0xfefe19e7U, 0xd7d762b5U, 0xababe64dU, 0x76769aecU,
+    0xcaca458fU, 0x82829d1fU, 0xc9c94089U, 0x7d7d87faU,
+    0xfafa15efU, 0x5959ebb2U, 0x4747c98eU, 0xf0f00bfbU,
+    0xadadec41U, 0xd4d467b3U, 0xa2a2fd5fU, 0xafafea45U,
+    0x9c9cbf23U, 0xa4a4f753U, 0x727296e4U, 0xc0c05b9bU,
+    0xb7b7c275U, 0xfdfd1ce1U, 0x9393ae3dU, 0x26266a4cU,
+    0x36365a6cU, 0x3f3f417eU, 0xf7f702f5U, 0xcccc4f83U,
+    0x34345c68U, 0xa5a5f451U, 0xe5e534d1U, 0xf1f108f9U,
+    0x717193e2U, 0xd8d873abU, 0x31315362U, 0x15153f2aU,
+    0x04040c08U, 0xc7c75295U, 0x23236546U, 0xc3c35e9dU,
+    0x18182830U, 0x9696a137U, 0x05050f0aU, 0x9a9ab52fU,
+    0x0707090eU, 0x12123624U, 0x80809b1bU, 0xe2e23ddfU,
+    0xebeb26cdU, 0x2727694eU, 0xb2b2cd7fU, 0x75759feaU,
+    0x09091b12U, 0x83839e1dU, 0x2c2c7458U, 0x1a1a2e34U,
+    0x1b1b2d36U, 0x6e6eb2dcU, 0x5a5aeeb4U, 0xa0a0fb5bU,
+    0x5252f6a4U, 0x3b3b4d76U, 0xd6d661b7U, 0xb3b3ce7dU,
+    0x29297b52U, 0xe3e33eddU, 0x2f2f715eU, 0x84849713U,
+    0x5353f5a6U, 0xd1d168b9U, 0x00000000U, 0xeded2cc1U,
+    0x20206040U, 0xfcfc1fe3U, 0xb1b1c879U, 0x5b5bedb6U,
+    0x6a6abed4U, 0xcbcb468dU, 0xbebed967U, 0x39394b72U,
+    0x4a4ade94U, 0x4c4cd498U, 0x5858e8b0U, 0xcfcf4a85U,
+    0xd0d06bbbU, 0xefef2ac5U, 0xaaaae54fU, 0xfbfb16edU,
+    0x4343c586U, 0x4d4dd79aU, 0x33335566U, 0x85859411U,
+    0x4545cf8aU, 0xf9f910e9U, 0x02020604U, 0x7f7f81feU,
+    0x5050f0a0U, 0x3c3c4478U, 0x9f9fba25U, 0xa8a8e34bU,
+    0x5151f3a2U, 0xa3a3fe5dU, 0x4040c080U, 0x8f8f8a05U,
+    0x9292ad3fU, 0x9d9dbc21U, 0x38384870U, 0xf5f504f1U,
+    0xbcbcdf63U, 0xb6b6c177U, 0xdada75afU, 0x21216342U,
+    0x10103020U, 0xffff1ae5U, 0xf3f30efdU, 0xd2d26dbfU,
+    0xcdcd4c81U, 0x0c0c1418U, 0x13133526U, 0xecec2fc3U,
+    0x5f5fe1beU, 0x9797a235U, 0x4444cc88U, 0x1717392eU,
+    0xc4c45793U, 0xa7a7f255U, 0x7e7e82fcU, 0x3d3d477aU,
+    0x6464acc8U, 0x5d5de7baU, 0x19192b32U, 0x737395e6U,
+    0x6060a0c0U, 0x81819819U, 0x4f4fd19eU, 0xdcdc7fa3U,
+    0x22226644U, 0x2a2a7e54U, 0x9090ab3bU, 0x8888830bU,
+    0x4646ca8cU, 0xeeee29c7U, 0xb8b8d36bU, 0x14143c28U,
+    0xdede79a7U, 0x5e5ee2bcU, 0x0b0b1d16U, 0xdbdb76adU,
+    0xe0e03bdbU, 0x32325664U, 0x3a3a4e74U, 0x0a0a1e14U,
+    0x4949db92U, 0x06060a0cU, 0x24246c48U, 0x5c5ce4b8U,
+    0xc2c25d9fU, 0xd3d36ebdU, 0xacacef43U, 0x6262a6c4U,
+    0x9191a839U, 0x9595a431U, 0xe4e437d3U, 0x79798bf2U,
+    0xe7e732d5U, 0xc8c8438bU, 0x3737596eU, 0x6d6db7daU,
+    0x8d8d8c01U, 0xd5d564b1U, 0x4e4ed29cU, 0xa9a9e049U,
+    0x6c6cb4d8U, 0x5656faacU, 0xf4f407f3U, 0xeaea25cfU,
+    0x6565afcaU, 0x7a7a8ef4U, 0xaeaee947U, 0x08081810U,
+    0xbabad56fU, 0x787888f0U, 0x25256f4aU, 0x2e2e725cU,
+    0x1c1c2438U, 0xa6a6f157U, 0xb4b4c773U, 0xc6c65197U,
+    0xe8e823cbU, 0xdddd7ca1U, 0x74749ce8U, 0x1f1f213eU,
+    0x4b4bdd96U, 0xbdbddc61U, 0x8b8b860dU, 0x8a8a850fU,
+    0x707090e0U, 0x3e3e427cU, 0xb5b5c471U, 0x6666aaccU,
+    0x4848d890U, 0x03030506U, 0xf6f601f7U, 0x0e0e121cU,
+    0x6161a3c2U, 0x35355f6aU, 0x5757f9aeU, 0xb9b9d069U,
+    0x86869117U, 0xc1c15899U, 0x1d1d273aU, 0x9e9eb927U,
+    0xe1e138d9U, 0xf8f813ebU, 0x9898b32bU, 0x11113322U,
+    0x6969bbd2U, 0xd9d970a9U, 0x8e8e8907U, 0x9494a733U,
+    0x9b9bb62dU, 0x1e1e223cU, 0x87879215U, 0xe9e920c9U,
+    0xcece4987U, 0x5555ffaaU, 0x28287850U, 0xdfdf7aa5U,
+    0x8c8c8f03U, 0xa1a1f859U, 0x89898009U, 0x0d0d171aU,
+    0xbfbfda65U, 0xe6e631d7U, 0x4242c684U, 0x6868b8d0U,
+    0x4141c382U, 0x9999b029U, 0x2d2d775aU, 0x0f0f111eU,
+    0xb0b0cb7bU, 0x5454fca8U, 0xbbbbd66dU, 0x16163a2cU,
+};
+
+static const uint32_t Te4[256] = {
+    0x63636363U, 0x7c7c7c7cU, 0x77777777U, 0x7b7b7b7bU,
+    0xf2f2f2f2U, 0x6b6b6b6bU, 0x6f6f6f6fU, 0xc5c5c5c5U,
+    0x30303030U, 0x01010101U, 0x67676767U, 0x2b2b2b2bU,
+    0xfefefefeU, 0xd7d7d7d7U, 0xababababU, 0x76767676U,
+    0xcacacacaU, 0x82828282U, 0xc9c9c9c9U, 0x7d7d7d7dU,
+    0xfafafafaU, 0x59595959U, 0x47474747U, 0xf0f0f0f0U,
+    0xadadadadU, 0xd4d4d4d4U, 0xa2a2a2a2U, 0xafafafafU,
+    0x9c9c9c9cU, 0xa4a4a4a4U, 0x72727272U, 0xc0c0c0c0U,
+    0xb7b7b7b7U, 0xfdfdfdfdU, 0x93939393U, 0x26262626U,
+    0x36363636U, 0x3f3f3f3fU, 0xf7f7f7f7U, 0xccccccccU,
+    0x34343434U, 0xa5a5a5a5U, 0xe5e5e5e5U, 0xf1f1f1f1U,
+    0x71717171U, 0xd8d8d8d8U, 0x31313131U, 0x15151515U,
+    0x04040404U, 0xc7c7c7c7U, 0x23232323U, 0xc3c3c3c3U,
+    0x18181818U, 0x96969696U, 0x05050505U, 0x9a9a9a9aU,
+    0x07070707U, 0x12121212U, 0x80808080U, 0xe2e2e2e2U,
+    0xebebebebU, 0x27272727U, 0xb2b2b2b2U, 0x75757575U,
+    0x09090909U, 0x83838383U, 0x2c2c2c2cU, 0x1a1a1a1aU,
+    0x1b1b1b1bU, 0x6e6e6e6eU, 0x5a5a5a5aU, 0xa0a0a0a0U,
+    0x52525252U, 0x3b3b3b3bU, 0xd6d6d6d6U, 0xb3b3b3b3U,
+    0x29292929U, 0xe3e3e3e3U, 0x2f2f2f2fU, 0x84848484U,
+    0x53535353U, 0xd1d1d1d1U, 0x00000000U, 0xededededU,
+    0x20202020U, 0xfcfcfcfcU, 0xb1b1b1b1U, 0x5b5b5b5bU,
+    0x6a6a6a6aU, 0xcbcbcbcbU, 0xbebebebeU, 0x39393939U,
+    0x4a4a4a4aU, 0x4c4c4c4cU, 0x58585858U, 0xcfcfcfcfU,
+    0xd0d0d0d0U, 0xefefefefU, 0xaaaaaaaaU, 0xfbfbfbfbU,
+    0x43434343U, 0x4d4d4d4dU, 0x33333333U, 0x85858585U,
+    0x45454545U, 0xf9f9f9f9U, 0x02020202U, 0x7f7f7f7fU,
+    0x50505050U, 0x3c3c3c3cU, 0x9f9f9f9fU, 0xa8a8a8a8U,
+    0x51515151U, 0xa3a3a3a3U, 0x40404040U, 0x8f8f8f8fU,
+    0x92929292U, 0x9d9d9d9dU, 0x38383838U, 0xf5f5f5f5U,
+    0xbcbcbcbcU, 0xb6b6b6b6U, 0xdadadadaU, 0x21212121U,
+    0x10101010U, 0xffffffffU, 0xf3f3f3f3U, 0xd2d2d2d2U,
+    0xcdcdcdcdU, 0x0c0c0c0cU, 0x13131313U, 0xececececU,
+    0x5f5f5f5fU, 0x97979797U, 0x44444444U, 0x17171717U,
+    0xc4c4c4c4U, 0xa7a7a7a7U, 0x7e7e7e7eU, 0x3d3d3d3dU,
+    0x64646464U, 0x5d5d5d5dU, 0x19191919U, 0x73737373U,
+    0x60606060U, 0x81818181U, 0x4f4f4f4fU, 0xdcdcdcdcU,
+    0x22222222U, 0x2a2a2a2aU, 0x90909090U, 0x88888888U,
+    0x46464646U, 0xeeeeeeeeU, 0xb8b8b8b8U, 0x14141414U,
+    0xdedededeU, 0x5e5e5e5eU, 0x0b0b0b0bU, 0xdbdbdbdbU,
+    0xe0e0e0e0U, 0x32323232U, 0x3a3a3a3aU, 0x0a0a0a0aU,
+    0x49494949U, 0x06060606U, 0x24242424U, 0x5c5c5c5cU,
+    0xc2c2c2c2U, 0xd3d3d3d3U, 0xacacacacU, 0x62626262U,
+    0x91919191U, 0x95959595U, 0xe4e4e4e4U, 0x79797979U,
+    0xe7e7e7e7U, 0xc8c8c8c8U, 0x37373737U, 0x6d6d6d6dU,
+    0x8d8d8d8dU, 0xd5d5d5d5U, 0x4e4e4e4eU, 0xa9a9a9a9U,
+    0x6c6c6c6cU, 0x56565656U, 0xf4f4f4f4U, 0xeaeaeaeaU,
+    0x65656565U, 0x7a7a7a7aU, 0xaeaeaeaeU, 0x08080808U,
+    0xbabababaU, 0x78787878U, 0x25252525U, 0x2e2e2e2eU,
+    0x1c1c1c1cU, 0xa6a6a6a6U, 0xb4b4b4b4U, 0xc6c6c6c6U,
+    0xe8e8e8e8U, 0xddddddddU, 0x74747474U, 0x1f1f1f1fU,
+    0x4b4b4b4bU, 0xbdbdbdbdU, 0x8b8b8b8bU, 0x8a8a8a8aU,
+    0x70707070U, 0x3e3e3e3eU, 0xb5b5b5b5U, 0x66666666U,
+    0x48484848U, 0x03030303U, 0xf6f6f6f6U, 0x0e0e0e0eU,
+    0x61616161U, 0x35353535U, 0x57575757U, 0xb9b9b9b9U,
+    0x86868686U, 0xc1c1c1c1U, 0x1d1d1d1dU, 0x9e9e9e9eU,
+    0xe1e1e1e1U, 0xf8f8f8f8U, 0x98989898U, 0x11111111U,
+    0x69696969U, 0xd9d9d9d9U, 0x8e8e8e8eU, 0x94949494U,
+    0x9b9b9b9bU, 0x1e1e1e1eU, 0x87878787U, 0xe9e9e9e9U,
+    0xcecececeU, 0x55555555U, 0x28282828U, 0xdfdfdfdfU,
+    0x8c8c8c8cU, 0xa1a1a1a1U, 0x89898989U, 0x0d0d0d0dU,
+    0xbfbfbfbfU, 0xe6e6e6e6U, 0x42424242U, 0x68686868U,
+    0x41414141U, 0x99999999U, 0x2d2d2d2dU, 0x0f0f0f0fU,
+    0xb0b0b0b0U, 0x54545454U, 0xbbbbbbbbU, 0x16161616U,
+};
+
+static const uint32_t Td0[256] = {
+    0x51f4a750U, 0x7e416553U, 0x1a17a4c3U, 0x3a275e96U,
+    0x3bab6bcbU, 0x1f9d45f1U, 0xacfa58abU, 0x4be30393U,
+    0x2030fa55U, 0xad766df6U, 0x88cc7691U, 0xf5024c25U,
+    0x4fe5d7fcU, 0xc52acbd7U, 0x26354480U, 0xb562a38fU,
+    0xdeb15a49U, 0x25ba1b67U, 0x45ea0e98U, 0x5dfec0e1U,
+    0xc32f7502U, 0x814cf012U, 0x8d4697a3U, 0x6bd3f9c6U,
+    0x038f5fe7U, 0x15929c95U, 0xbf6d7aebU, 0x955259daU,
+    0xd4be832dU, 0x587421d3U, 0x49e06929U, 0x8ec9c844U,
+    0x75c2896aU, 0xf48e7978U, 0x99583e6bU, 0x27b971ddU,
+    0xbee14fb6U, 0xf088ad17U, 0xc920ac66U, 0x7dce3ab4U,
+    0x63df4a18U, 0xe51a3182U, 0x97513360U, 0x62537f45U,
+    0xb16477e0U, 0xbb6bae84U, 0xfe81a01cU, 0xf9082b94U,
+    0x70486858U, 0x8f45fd19U, 0x94de6c87U, 0x527bf8b7U,
+    0xab73d323U, 0x724b02e2U, 0xe31f8f57U, 0x6655ab2aU,
+    0xb2eb2807U, 0x2fb5c203U, 0x86c57b9aU, 0xd33708a5U,
+    0x302887f2U, 0x23bfa5b2U, 0x02036abaU, 0xed16825cU,
+    0x8acf1c2bU, 0xa779b492U, 0xf307f2f0U, 0x4e69e2a1U,
+    0x65daf4cdU, 0x0605bed5U, 0xd134621fU, 0xc4a6fe8aU,
+    0x342e539dU, 0xa2f355a0U, 0x058ae132U, 0xa4f6eb75U,
+    0x0b83ec39U, 0x4060efaaU, 0x5e719f06U, 0xbd6e1051U,
+    0x3e218af9U, 0x96dd063dU, 0xdd3e05aeU, 0x4de6bd46U,
+    0x91548db5U, 0x71c45d05U, 0x0406d46fU, 0x605015ffU,
+    0x1998fb24U, 0xd6bde997U, 0x894043ccU, 0x67d99e77U,
+    0xb0e842bdU, 0x07898b88U, 0xe7195b38U, 0x79c8eedbU,
+    0xa17c0a47U, 0x7c420fe9U, 0xf8841ec9U, 0x00000000U,
+    0x09808683U, 0x322bed48U, 0x1e1170acU, 0x6c5a724eU,
+    0xfd0efffbU, 0x0f853856U, 0x3daed51eU, 0x362d3927U,
+    0x0a0fd964U, 0x685ca621U, 0x9b5b54d1U, 0x24362e3aU,
+    0x0c0a67b1U, 0x9357e70fU, 0xb4ee96d2U, 0x1b9b919eU,
+    0x80c0c54fU, 0x61dc20a2U, 0x5a774b69U, 0x1c121a16U,
+    0xe293ba0aU, 0xc0a02ae5U, 0x3c22e043U, 0x121b171dU,
+    0x0e090d0bU, 0xf28bc7adU, 0x2db6a8b9U, 0x141ea9c8U,
+    0x57f11985U, 0xaf75074cU, 0xee99ddbbU, 0xa37f60fdU,
+    0xf701269fU, 0x5c72f5bcU, 0x44663bc5U, 0x5bfb7e34U,
+    0x8b432976U, 0xcb23c6dcU, 0xb6edfc68U, 0xb8e4f163U,
+    0xd731dccaU, 0x42638510U, 0x13972240U, 0x84c61120U,
+    0x854a247dU, 0xd2bb3df8U, 0xaef93211U, 0xc729a16dU,
+    0x1d9e2f4bU, 0xdcb230f3U, 0x0d8652ecU, 0x77c1e3d0U,
+    0x2bb3166cU, 0xa970b999U, 0x119448faU, 0x47e96422U,
+    0xa8fc8cc4U, 0xa0f03f1aU, 0x567d2cd8U, 0x223390efU,
+    0x87494ec7U, 0xd938d1c1U, 0x8ccaa2feU, 0x98d40b36U,
+    0xa6f581cfU, 0xa57ade28U, 0xdab78e26U, 0x3fadbfa4U,
+    0x2c3a9de4U, 0x5078920dU, 0x6a5fcc9bU, 0x547e4662U,
+    0xf68d13c2U, 0x90d8b8e8U, 0x2e39f75eU, 0x82c3aff5U,
+    0x9f5d80beU, 0x69d0937cU, 0x6fd52da9U, 0xcf2512b3U,
+    0xc8ac993bU, 0x10187da7U, 0xe89c636eU, 0xdb3bbb7bU,
+    0xcd267809U, 0x6e5918f4U, 0xec9ab701U, 0x834f9aa8U,
+    0xe6956e65U, 0xaaffe67eU, 0x21bccf08U, 0xef15e8e6U,
+    0xbae79bd9U, 0x4a6f36ceU, 0xea9f09d4U, 0x29b07cd6U,
+    0x31a4b2afU, 0x2a3f2331U, 0xc6a59430U, 0x35a266c0U,
+    0x744ebc37U, 0xfc82caa6U, 0xe090d0b0U, 0x33a7d815U,
+    0xf104984aU, 0x41ecdaf7U, 0x7fcd500eU, 0x1791f62fU,
+    0x764dd68dU, 0x43efb04dU, 0xccaa4d54U, 0xe49604dfU,
+    0x9ed1b5e3U, 0x4c6a881bU, 0xc12c1fb8U, 0x4665517fU,
+    0x9d5eea04U, 0x018c355dU, 0xfa877473U, 0xfb0b412eU,
+    0xb3671d5aU, 0x92dbd252U, 0xe9105633U, 0x6dd64713U,
+    0x9ad7618cU, 0x37a10c7aU, 0x59f8148eU, 0xeb133c89U,
+    0xcea927eeU, 0xb761c935U, 0xe11ce5edU, 0x7a47b13cU,
+    0x9cd2df59U, 0x55f2733fU, 0x1814ce79U, 0x73c737bfU,
+    0x53f7cdeaU, 0x5ffdaa5bU, 0xdf3d6f14U, 0x7844db86U,
+    0xcaaff381U, 0xb968c43eU, 0x3824342cU, 0xc2a3405fU,
+    0x161dc372U, 0xbce2250cU, 0x283c498bU, 0xff0d9541U,
+    0x39a80171U, 0x080cb3deU, 0xd8b4e49cU, 0x6456c190U,
+    0x7bcb8461U, 0xd532b670U, 0x486c5c74U, 0xd0b85742U,
+};
+
+static const uint32_t Td1[256] = {
+    0x5051f4a7U, 0x537e4165U, 0xc31a17a4U, 0x963a275eU,
+    0xcb3bab6bU, 0xf11f9d45U, 0xabacfa58U, 0x934be303U,
+    0x552030faU, 0xf6ad766dU, 0x9188cc76U, 0x25f5024cU,
+    0xfc4fe5d7U, 0xd7c52acbU, 0x80263544U, 0x8fb562a3U,
+    0x49deb15aU, 0x6725ba1bU, 0x9845ea0eU, 0xe15dfec0U,
+    0x02c32f75U, 0x12814cf0U, 0xa38d4697U, 0xc66bd3f9U,
+    0xe7038f5fU, 0x9515929cU, 0xebbf6d7aU, 0xda955259U,
+    0x2dd4be83U, 0xd3587421U, 0x2949e069U, 0x448ec9c8U,
+    0x6a75c289U, 0x78f48e79U, 0x6b99583eU, 0xdd27b971U,
+    0xb6bee14fU, 0x17f088adU, 0x66c920acU, 0xb47dce3aU,
+    0x1863df4aU, 0x82e51a31U, 0x60975133U, 0x4562537fU,
+    0xe0b16477U, 0x84bb6baeU, 0x1cfe81a0U, 0x94f9082bU,
+    0x58704868U, 0x198f45fdU, 0x8794de6cU, 0xb7527bf8U,
+    0x23ab73d3U, 0xe2724b02U, 0x57e31f8fU, 0x2a6655abU,
+    0x07b2eb28U, 0x032fb5c2U, 0x9a86c57bU, 0xa5d33708U,
+    0xf2302887U, 0xb223bfa5U, 0xba02036aU, 0x5ced1682U,
+    0x2b8acf1cU, 0x92a779b4U, 0xf0f307f2U, 0xa14e69e2U,
+    0xcd65daf4U, 0xd50605beU, 0x1fd13462U, 0x8ac4a6feU,
+    0x9d342e53U, 0xa0a2f355U, 0x32058ae1U, 0x75a4f6ebU,
+    0x390b83ecU, 0xaa4060efU, 0x065e719fU, 0x51bd6e10U,
+    0xf93e218aU, 0x3d96dd06U, 0xaedd3e05U, 0x464de6bdU,
+    0xb591548dU, 0x0571c45dU, 0x6f0406d4U, 0xff605015U,
+    0x241998fbU, 0x97d6bde9U, 0xcc894043U, 0x7767d99eU,
+    0xbdb0e842U, 0x8807898bU, 0x38e7195bU, 0xdb79c8eeU,
+    0x47a17c0aU, 0xe97c420fU, 0xc9f8841eU, 0x00000000U,
+    0x83098086U, 0x48322bedU, 0xac1e1170U, 0x4e6c5a72U,
+    0xfbfd0effU, 0x560f8538U, 0x1e3daed5U, 0x27362d39U,
+    0x640a0fd9U, 0x21685ca6U, 0xd19b5b54U, 0x3a24362eU,
+    0xb10c0a67U, 0x0f9357e7U, 0xd2b4ee96U, 0x9e1b9b91U,
+    0x4f80c0c5U, 0xa261dc20U, 0x695a774bU, 0x161c121aU,
+    0x0ae293baU, 0xe5c0a02aU, 0x433c22e0U, 0x1d121b17U,
+    0x0b0e090dU, 0xadf28bc7U, 0xb92db6a8U, 0xc8141ea9U,
+    0x8557f119U, 0x4caf7507U, 0xbbee99ddU, 0xfda37f60U,
+    0x9ff70126U, 0xbc5c72f5U, 0xc544663bU, 0x345bfb7eU,
+    0x768b4329U, 0xdccb23c6U, 0x68b6edfcU, 0x63b8e4f1U,
+    0xcad731dcU, 0x10426385U, 0x40139722U, 0x2084c611U,
+    0x7d854a24U, 0xf8d2bb3dU, 0x11aef932U, 0x6dc729a1U,
+    0x4b1d9e2fU, 0xf3dcb230U, 0xec0d8652U, 0xd077c1e3U,
+    0x6c2bb316U, 0x99a970b9U, 0xfa119448U, 0x2247e964U,
+    0xc4a8fc8cU, 0x1aa0f03fU, 0xd8567d2cU, 0xef223390U,
+    0xc787494eU, 0xc1d938d1U, 0xfe8ccaa2U, 0x3698d40bU,
+    0xcfa6f581U, 0x28a57adeU, 0x26dab78eU, 0xa43fadbfU,
+    0xe42c3a9dU, 0x0d507892U, 0x9b6a5fccU, 0x62547e46U,
+    0xc2f68d13U, 0xe890d8b8U, 0x5e2e39f7U, 0xf582c3afU,
+    0xbe9f5d80U, 0x7c69d093U, 0xa96fd52dU, 0xb3cf2512U,
+    0x3bc8ac99U, 0xa710187dU, 0x6ee89c63U, 0x7bdb3bbbU,
+    0x09cd2678U, 0xf46e5918U, 0x01ec9ab7U, 0xa8834f9aU,
+    0x65e6956eU, 0x7eaaffe6U, 0x0821bccfU, 0xe6ef15e8U,
+    0xd9bae79bU, 0xce4a6f36U, 0xd4ea9f09U, 0xd629b07cU,
+    0xaf31a4b2U, 0x312a3f23U, 0x30c6a594U, 0xc035a266U,
+    0x37744ebcU, 0xa6fc82caU, 0xb0e090d0U, 0x1533a7d8U,
+    0x4af10498U, 0xf741ecdaU, 0x0e7fcd50U, 0x2f1791f6U,
+    0x8d764dd6U, 0x4d43efb0U, 0x54ccaa4dU, 0xdfe49604U,
+    0xe39ed1b5U, 0x1b4c6a88U, 0xb8c12c1fU, 0x7f466551U,
+    0x049d5eeaU, 0x5d018c35U, 0x73fa8774U, 0x2efb0b41U,
+    0x5ab3671dU, 0x5292dbd2U, 0x33e91056U, 0x136dd647U,
+    0x8c9ad761U, 0x7a37a10cU, 0x8e59f814U, 0x89eb133cU,
+    0xeecea927U, 0x35b761c9U, 0xede11ce5U, 0x3c7a47b1U,
+    0x599cd2dfU, 0x3f55f273U, 0x791814ceU, 0xbf73c737U,
+    0xea53f7cdU, 0x5b5ffdaaU, 0x14df3d6fU, 0x867844dbU,
+    0x81caaff3U, 0x3eb968c4U, 0x2c382434U, 0x5fc2a340U,
+    0x72161dc3U, 0x0cbce225U, 0x8b283c49U, 0x41ff0d95U,
+    0x7139a801U, 0xde080cb3U, 0x9cd8b4e4U, 0x906456c1U,
+    0x617bcb84U, 0x70d532b6U, 0x74486c5cU, 0x42d0b857U,
+};
+
+static const uint32_t Td2[256] = {
+    0xa75051f4U, 0x65537e41U, 0xa4c31a17U, 0x5e963a27U,
+    0x6bcb3babU, 0x45f11f9dU, 0x58abacfaU, 0x03934be3U,
+    0xfa552030U, 0x6df6ad76U, 0x769188ccU, 0x4c25f502U,
+    0xd7fc4fe5U, 0xcbd7c52aU, 0x44802635U, 0xa38fb562U,
+    0x5a49deb1U, 0x1b6725baU, 0x0e9845eaU, 0xc0e15dfeU,
+    0x7502c32fU, 0xf012814cU, 0x97a38d46U, 0xf9c66bd3U,
+    0x5fe7038fU, 0x9c951592U, 0x7aebbf6dU, 0x59da9552U,
+    0x832dd4beU, 0x21d35874U, 0x692949e0U, 0xc8448ec9U,
+    0x896a75c2U, 0x7978f48eU, 0x3e6b9958U, 0x71dd27b9U,
+    0x4fb6bee1U, 0xad17f088U, 0xac66c920U, 0x3ab47dceU,
+    0x4a1863dfU, 0x3182e51aU, 0x33609751U, 0x7f456253U,
+    0x77e0b164U, 0xae84bb6bU, 0xa01cfe81U, 0x2b94f908U,
+    0x68587048U, 0xfd198f45U, 0x6c8794deU, 0xf8b7527bU,
+    0xd323ab73U, 0x02e2724bU, 0x8f57e31fU, 0xab2a6655U,
+    0x2807b2ebU, 0xc2032fb5U, 0x7b9a86c5U, 0x08a5d337U,
+    0x87f23028U, 0xa5b223bfU, 0x6aba0203U, 0x825ced16U,
+    0x1c2b8acfU, 0xb492a779U, 0xf2f0f307U, 0xe2a14e69U,
+    0xf4cd65daU, 0xbed50605U, 0x621fd134U, 0xfe8ac4a6U,
+    0x539d342eU, 0x55a0a2f3U, 0xe132058aU, 0xeb75a4f6U,
+    0xec390b83U, 0xefaa4060U, 0x9f065e71U, 0x1051bd6eU,
+    0x8af93e21U, 0x063d96ddU, 0x05aedd3eU, 0xbd464de6U,
+    0x8db59154U, 0x5d0571c4U, 0xd46f0406U, 0x15ff6050U,
+    0xfb241998U, 0xe997d6bdU, 0x43cc8940U, 0x9e7767d9U,
+    0x42bdb0e8U, 0x8b880789U, 0x5b38e719U, 0xeedb79c8U,
+    0x0a47a17cU, 0x0fe97c42U, 0x1ec9f884U, 0x00000000U,
+    0x86830980U, 0xed48322bU, 0x70ac1e11U, 0x724e6c5aU,
+    0xfffbfd0eU, 0x38560f85U, 0xd51e3daeU, 0x3927362dU,
+    0xd9640a0fU, 0xa621685cU, 0x54d19b5bU, 0x2e3a2436U,
+    0x67b10c0aU, 0xe70f9357U, 0x96d2b4eeU, 0x919e1b9bU,
+    0xc54f80c0U, 0x20a261dcU, 0x4b695a77U, 0x1a161c12U,
+    0xba0ae293U, 0x2ae5c0a0U, 0xe0433c22U, 0x171d121bU,
+    0x0d0b0e09U, 0xc7adf28bU, 0xa8b92db6U, 0xa9c8141eU,
+    0x198557f1U, 0x074caf75U, 0xddbbee99U, 0x60fda37fU,
+    0x269ff701U, 0xf5bc5c72U, 0x3bc54466U, 0x7e345bfbU,
+    0x29768b43U, 0xc6dccb23U, 0xfc68b6edU, 0xf163b8e4U,
+    0xdccad731U, 0x85104263U, 0x22401397U, 0x112084c6U,
+    0x247d854aU, 0x3df8d2bbU, 0x3211aef9U, 0xa16dc729U,
+    0x2f4b1d9eU, 0x30f3dcb2U, 0x52ec0d86U, 0xe3d077c1U,
+    0x166c2bb3U, 0xb999a970U, 0x48fa1194U, 0x642247e9U,
+    0x8cc4a8fcU, 0x3f1aa0f0U, 0x2cd8567dU, 0x90ef2233U,
+    0x4ec78749U, 0xd1c1d938U, 0xa2fe8ccaU, 0x0b3698d4U,
+    0x81cfa6f5U, 0xde28a57aU, 0x8e26dab7U, 0xbfa43fadU,
+    0x9de42c3aU, 0x920d5078U, 0xcc9b6a5fU, 0x4662547eU,
+    0x13c2f68dU, 0xb8e890d8U, 0xf75e2e39U, 0xaff582c3U,
+    0x80be9f5dU, 0x937c69d0U, 0x2da96fd5U, 0x12b3cf25U,
+    0x993bc8acU, 0x7da71018U, 0x636ee89cU, 0xbb7bdb3bU,
+    0x7809cd26U, 0x18f46e59U, 0xb701ec9aU, 0x9aa8834fU,
+    0x6e65e695U, 0xe67eaaffU, 0xcf0821bcU, 0xe8e6ef15U,
+    0x9bd9bae7U, 0x36ce4a6fU, 0x09d4ea9fU, 0x7cd629b0U,
+    0xb2af31a4U, 0x23312a3fU, 0x9430c6a5U, 0x66c035a2U,
+    0xbc37744eU, 0xcaa6fc82U, 0xd0b0e090U, 0xd81533a7U,
+    0x984af104U, 0xdaf741ecU, 0x500e7fcdU, 0xf62f1791U,
+    0xd68d764dU, 0xb04d43efU, 0x4d54ccaaU, 0x04dfe496U,
+    0xb5e39ed1U, 0x881b4c6aU, 0x1fb8c12cU, 0x517f4665U,
+    0xea049d5eU, 0x355d018cU, 0x7473fa87U, 0x412efb0bU,
+    0x1d5ab367U, 0xd25292dbU, 0x5633e910U, 0x47136dd6U,
+    0x618c9ad7U, 0x0c7a37a1U, 0x148e59f8U, 0x3c89eb13U,
+    0x27eecea9U, 0xc935b761U, 0xe5ede11cU, 0xb13c7a47U,
+    0xdf599cd2U, 0x733f55f2U, 0xce791814U, 0x37bf73c7U,
+    0xcdea53f7U, 0xaa5b5ffdU, 0x6f14df3dU, 0xdb867844U,
+    0xf381caafU, 0xc43eb968U, 0x342c3824U, 0x405fc2a3U,
+    0xc372161dU, 0x250cbce2U, 0x498b283cU, 0x9541ff0dU,
+    0x017139a8U, 0xb3de080cU, 0xe49cd8b4U, 0xc1906456U,
+    0x84617bcbU, 0xb670d532U, 0x5c74486cU, 0x5742d0b8U,
+};
+
+static const uint32_t Td3[256] = {
+    0xf4a75051U, 0x4165537eU, 0x17a4c31aU, 0x275e963aU,
+    0xab6bcb3bU, 0x9d45f11fU, 0xfa58abacU, 0xe303934bU,
+    0x30fa5520U, 0x766df6adU, 0xcc769188U, 0x024c25f5U,
+    0xe5d7fc4fU, 0x2acbd7c5U, 0x35448026U, 0x62a38fb5U,
+    0xb15a49deU, 0xba1b6725U, 0xea0e9845U, 0xfec0e15dU,
+    0x2f7502c3U, 0x4cf01281U, 0x4697a38dU, 0xd3f9c66bU,
+    0x8f5fe703U, 0x929c9515U, 0x6d7aebbfU, 0x5259da95U,
+    0xbe832dd4U, 0x7421d358U, 0xe0692949U, 0xc9c8448eU,
+    0xc2896a75U, 0x8e7978f4U, 0x583e6b99U, 0xb971dd27U,
+    0xe14fb6beU, 0x88ad17f0U, 0x20ac66c9U, 0xce3ab47dU,
+    0xdf4a1863U, 0x1a3182e5U, 0x51336097U, 0x537f4562U,
+    0x6477e0b1U, 0x6bae84bbU, 0x81a01cfeU, 0x082b94f9U,
+    0x48685870U, 0x45fd198fU, 0xde6c8794U, 0x7bf8b752U,
+    0x73d323abU, 0x4b02e272U, 0x1f8f57e3U, 0x55ab2a66U,
+    0xeb2807b2U, 0xb5c2032fU, 0xc57b9a86U, 0x3708a5d3U,
+    0x2887f230U, 0xbfa5b223U, 0x036aba02U, 0x16825cedU,
+    0xcf1c2b8aU, 0x79b492a7U, 0x07f2f0f3U, 0x69e2a14eU,
+    0xdaf4cd65U, 0x05bed506U, 0x34621fd1U, 0xa6fe8ac4U,
+    0x2e539d34U, 0xf355a0a2U, 0x8ae13205U, 0xf6eb75a4U,
+    0x83ec390bU, 0x60efaa40U, 0x719f065eU, 0x6e1051bdU,
+    0x218af93eU, 0xdd063d96U, 0x3e05aeddU, 0xe6bd464dU,
+    0x548db591U, 0xc45d0571U, 0x06d46f04U, 0x5015ff60U,
+    0x98fb2419U, 0xbde997d6U, 0x4043cc89U, 0xd99e7767U,
+    0xe842bdb0U, 0x898b8807U, 0x195b38e7U, 0xc8eedb79U,
+    0x7c0a47a1U, 0x420fe97cU, 0x841ec9f8U, 0x00000000U,
+    0x80868309U, 0x2bed4832U, 0x1170ac1eU, 0x5a724e6cU,
+    0x0efffbfdU, 0x8538560fU, 0xaed51e3dU, 0x2d392736U,
+    0x0fd9640aU, 0x5ca62168U, 0x5b54d19bU, 0x362e3a24U,
+    0x0a67b10cU, 0x57e70f93U, 0xee96d2b4U, 0x9b919e1bU,
+    0xc0c54f80U, 0xdc20a261U, 0x774b695aU, 0x121a161cU,
+    0x93ba0ae2U, 0xa02ae5c0U, 0x22e0433cU, 0x1b171d12U,
+    0x090d0b0eU, 0x8bc7adf2U, 0xb6a8b92dU, 0x1ea9c814U,
+    0xf1198557U, 0x75074cafU, 0x99ddbbeeU, 0x7f60fda3U,
+    0x01269ff7U, 0x72f5bc5cU, 0x663bc544U, 0xfb7e345bU,
+    0x4329768bU, 0x23c6dccbU, 0xedfc68b6U, 0xe4f163b8U,
+    0x31dccad7U, 0x63851042U, 0x97224013U, 0xc6112084U,
+    0x4a247d85U, 0xbb3df8d2U, 0xf93211aeU, 0x29a16dc7U,
+    0x9e2f4b1dU, 0xb230f3dcU, 0x8652ec0dU, 0xc1e3d077U,
+    0xb3166c2bU, 0x70b999a9U, 0x9448fa11U, 0xe9642247U,
+    0xfc8cc4a8U, 0xf03f1aa0U, 0x7d2cd856U, 0x3390ef22U,
+    0x494ec787U, 0x38d1c1d9U, 0xcaa2fe8cU, 0xd40b3698U,
+    0xf581cfa6U, 0x7ade28a5U, 0xb78e26daU, 0xadbfa43fU,
+    0x3a9de42cU, 0x78920d50U, 0x5fcc9b6aU, 0x7e466254U,
+    0x8d13c2f6U, 0xd8b8e890U, 0x39f75e2eU, 0xc3aff582U,
+    0x5d80be9fU, 0xd0937c69U, 0xd52da96fU, 0x2512b3cfU,
+    0xac993bc8U, 0x187da710U, 0x9c636ee8U, 0x3bbb7bdbU,
+    0x267809cdU, 0x5918f46eU, 0x9ab701ecU, 0x4f9aa883U,
+    0x956e65e6U, 0xffe67eaaU, 0xbccf0821U, 0x15e8e6efU,
+    0xe79bd9baU, 0x6f36ce4aU, 0x9f09d4eaU, 0xb07cd629U,
+    0xa4b2af31U, 0x3f23312aU, 0xa59430c6U, 0xa266c035U,
+    0x4ebc3774U, 0x82caa6fcU, 0x90d0b0e0U, 0xa7d81533U,
+    0x04984af1U, 0xecdaf741U, 0xcd500e7fU, 0x91f62f17U,
+    0x4dd68d76U, 0xefb04d43U, 0xaa4d54ccU, 0x9604dfe4U,
+    0xd1b5e39eU, 0x6a881b4cU, 0x2c1fb8c1U, 0x65517f46U,
+    0x5eea049dU, 0x8c355d01U, 0x877473faU, 0x0b412efbU,
+    0x671d5ab3U, 0xdbd25292U, 0x105633e9U, 0xd647136dU,
+    0xd7618c9aU, 0xa10c7a37U, 0xf8148e59U, 0x133c89ebU,
+    0xa927eeceU, 0x61c935b7U, 0x1ce5ede1U, 0x47b13c7aU,
+    0xd2df599cU, 0xf2733f55U, 0x14ce7918U, 0xc737bf73U,
+    0xf7cdea53U, 0xfdaa5b5fU, 0x3d6f14dfU, 0x44db8678U,
+    0xaff381caU, 0x68c43eb9U, 0x24342c38U, 0xa3405fc2U,
+    0x1dc37216U, 0xe2250cbcU, 0x3c498b28U, 0x0d9541ffU,
+    0xa8017139U, 0x0cb3de08U, 0xb4e49cd8U, 0x56c19064U,
+    0xcb84617bU, 0x32b670d5U, 0x6c5c7448U, 0xb85742d0U,
+};
+
+static const uint32_t Td4[256] = {
+    0x52525252U, 0x09090909U, 0x6a6a6a6aU, 0xd5d5d5d5U,
+    0x30303030U, 0x36363636U, 0xa5a5a5a5U, 0x38383838U,
+    0xbfbfbfbfU, 0x40404040U, 0xa3a3a3a3U, 0x9e9e9e9eU,
+    0x81818181U, 0xf3f3f3f3U, 0xd7d7d7d7U, 0xfbfbfbfbU,
+    0x7c7c7c7cU, 0xe3e3e3e3U, 0x39393939U, 0x82828282U,
+    0x9b9b9b9bU, 0x2f2f2f2fU, 0xffffffffU, 0x87878787U,
+    0x34343434U, 0x8e8e8e8eU, 0x43434343U, 0x44444444U,
+    0xc4c4c4c4U, 0xdedededeU, 0xe9e9e9e9U, 0xcbcbcbcbU,
+    0x54545454U, 0x7b7b7b7bU, 0x94949494U, 0x32323232U,
+    0xa6a6a6a6U, 0xc2c2c2c2U, 0x23232323U, 0x3d3d3d3dU,
+    0xeeeeeeeeU, 0x4c4c4c4cU, 0x95959595U, 0x0b0b0b0bU,
+    0x42424242U, 0xfafafafaU, 0xc3c3c3c3U, 0x4e4e4e4eU,
+    0x08080808U, 0x2e2e2e2eU, 0xa1a1a1a1U, 0x66666666U,
+    0x28282828U, 0xd9d9d9d9U, 0x24242424U, 0xb2b2b2b2U,
+    0x76767676U, 0x5b5b5b5bU, 0xa2a2a2a2U, 0x49494949U,
+    0x6d6d6d6dU, 0x8b8b8b8bU, 0xd1d1d1d1U, 0x25252525U,
+    0x72727272U, 0xf8f8f8f8U, 0xf6f6f6f6U, 0x64646464U,
+    0x86868686U, 0x68686868U, 0x98989898U, 0x16161616U,
+    0xd4d4d4d4U, 0xa4a4a4a4U, 0x5c5c5c5cU, 0xccccccccU,
+    0x5d5d5d5dU, 0x65656565U, 0xb6b6b6b6U, 0x92929292U,
+    0x6c6c6c6cU, 0x70707070U, 0x48484848U, 0x50505050U,
+    0xfdfdfdfdU, 0xededededU, 0xb9b9b9b9U, 0xdadadadaU,
+    0x5e5e5e5eU, 0x15151515U, 0x46464646U, 0x57575757U,
+    0xa7a7a7a7U, 0x8d8d8d8dU, 0x9d9d9d9dU, 0x84848484U,
+    0x90909090U, 0xd8d8d8d8U, 0xababababU, 0x00000000U,
+    0x8c8c8c8cU, 0xbcbcbcbcU, 0xd3d3d3d3U, 0x0a0a0a0aU,
+    0xf7f7f7f7U, 0xe4e4e4e4U, 0x58585858U, 0x05050505U,
+    0xb8b8b8b8U, 0xb3b3b3b3U, 0x45454545U, 0x06060606U,
+    0xd0d0d0d0U, 0x2c2c2c2cU, 0x1e1e1e1eU, 0x8f8f8f8fU,
+    0xcacacacaU, 0x3f3f3f3fU, 0x0f0f0f0fU, 0x02020202U,
+    0xc1c1c1c1U, 0xafafafafU, 0xbdbdbdbdU, 0x03030303U,
+    0x01010101U, 0x13131313U, 0x8a8a8a8aU, 0x6b6b6b6bU,
+    0x3a3a3a3aU, 0x91919191U, 0x11111111U, 0x41414141U,
+    0x4f4f4f4fU, 0x67676767U, 0xdcdcdcdcU, 0xeaeaeaeaU,
+    0x97979797U, 0xf2f2f2f2U, 0xcfcfcfcfU, 0xcecececeU,
+    0xf0f0f0f0U, 0xb4b4b4b4U, 0xe6e6e6e6U, 0x73737373U,
+    0x96969696U, 0xacacacacU, 0x74747474U, 0x22222222U,
+    0xe7e7e7e7U, 0xadadadadU, 0x35353535U, 0x85858585U,
+    0xe2e2e2e2U, 0xf9f9f9f9U, 0x37373737U, 0xe8e8e8e8U,
+    0x1c1c1c1cU, 0x75757575U, 0xdfdfdfdfU, 0x6e6e6e6eU,
+    0x47474747U, 0xf1f1f1f1U, 0x1a1a1a1aU, 0x71717171U,
+    0x1d1d1d1dU, 0x29292929U, 0xc5c5c5c5U, 0x89898989U,
+    0x6f6f6f6fU, 0xb7b7b7b7U, 0x62626262U, 0x0e0e0e0eU,
+    0xaaaaaaaaU, 0x18181818U, 0xbebebebeU, 0x1b1b1b1bU,
+    0xfcfcfcfcU, 0x56565656U, 0x3e3e3e3eU, 0x4b4b4b4bU,
+    0xc6c6c6c6U, 0xd2d2d2d2U, 0x79797979U, 0x20202020U,
+    0x9a9a9a9aU, 0xdbdbdbdbU, 0xc0c0c0c0U, 0xfefefefeU,
+    0x78787878U, 0xcdcdcdcdU, 0x5a5a5a5aU, 0xf4f4f4f4U,
+    0x1f1f1f1fU, 0xddddddddU, 0xa8a8a8a8U, 0x33333333U,
+    0x88888888U, 0x07070707U, 0xc7c7c7c7U, 0x31313131U,
+    0xb1b1b1b1U, 0x12121212U, 0x10101010U, 0x59595959U,
+    0x27272727U, 0x80808080U, 0xececececU, 0x5f5f5f5fU,
+    0x60606060U, 0x51515151U, 0x7f7f7f7fU, 0xa9a9a9a9U,
+    0x19191919U, 0xb5b5b5b5U, 0x4a4a4a4aU, 0x0d0d0d0dU,
+    0x2d2d2d2dU, 0xe5e5e5e5U, 0x7a7a7a7aU, 0x9f9f9f9fU,
+    0x93939393U, 0xc9c9c9c9U, 0x9c9c9c9cU, 0xefefefefU,
+    0xa0a0a0a0U, 0xe0e0e0e0U, 0x3b3b3b3bU, 0x4d4d4d4dU,
+    0xaeaeaeaeU, 0x2a2a2a2aU, 0xf5f5f5f5U, 0xb0b0b0b0U,
+    0xc8c8c8c8U, 0xebebebebU, 0xbbbbbbbbU, 0x3c3c3c3cU,
+    0x83838383U, 0x53535353U, 0x99999999U, 0x61616161U,
+    0x17171717U, 0x2b2b2b2bU, 0x04040404U, 0x7e7e7e7eU,
+    0xbabababaU, 0x77777777U, 0xd6d6d6d6U, 0x26262626U,
+    0xe1e1e1e1U, 0x69696969U, 0x14141414U, 0x63636363U,
+    0x55555555U, 0x21212121U, 0x0c0c0c0cU, 0x7d7d7d7dU,
+};
+
+static const uint32_t rcon[] = {
+    0x01000000, 0x02000000, 0x04000000, 0x08000000,
+    0x10000000, 0x20000000, 0x40000000, 0x80000000,
+    0x1B000000, 0x36000000,
+};
+
+static uint32_t
+get_u32(const uint8_t *p)
+{
+    uint32_t p0 = p[0];
+    uint32_t p1 = p[1];
+    uint32_t p2 = p[2];
+    uint32_t p3 = p[3];
+    return (p0 << 24) | (p1 << 16) | (p2 << 8) | p3;
+}
+
+static void
+put_u32(uint8_t *p, uint32_t x)
+{
+    p[0] = x >> 24;
+    p[1] = x >> 16;
+    p[2] = x >> 8;
+    p[3] = x;
+}
+
+/* Expands 128-bit 'key' into the encryption key 'schedule'. */
+void
+aes128_schedule(struct aes128 *aes, const uint8_t key[16])
+{
+    uint32_t *rk = aes->rk;
+    int i;
+
+    rk[0] = get_u32(key);
+    rk[1] = get_u32(key + 4);
+    rk[2] = get_u32(key + 8);
+    rk[3] = get_u32(key + 12);
+    for (i = 0; i < 10; i++, rk += 4) {
+        uint32_t temp = rk[3];
+        rk[4] = (rk[0]
+                 ^ (Te4[(temp >> 16) & 0xff] & 0xff000000)
+                 ^ (Te4[(temp >>  8) & 0xff] & 0x00ff0000)
+                 ^ (Te4[(temp      ) & 0xff] & 0x0000ff00)
+                 ^ (Te4[(temp >> 24)       ] & 0x000000ff)
+                 ^ rcon[i]);
+        rk[5] = rk[1] ^ rk[4];
+        rk[6] = rk[2] ^ rk[5];
+        rk[7] = rk[3] ^ rk[6];
+    }
+    assert(rk == &aes->rk[40]);
+}
+
+void
+aes128_encrypt(const struct aes128 *aes, const void *input_, void *output_)
+{
+    const uint8_t *input = input_;
+    uint8_t *output = output_;
+    const uint32_t *rk = aes->rk;
+    uint32_t s0, s1, s2, s3;
+    uint32_t t0, t1, t2, t3;
+    int r;
+
+    /* Map byte array block to cipher state and add initial round key. */
+    s0 = get_u32(input     ) ^ rk[0];
+    s1 = get_u32(input +  4) ^ rk[1];
+    s2 = get_u32(input +  8) ^ rk[2];
+    s3 = get_u32(input + 12) ^ rk[3];
+
+    /* 10 full rounds. */
+    r = 10 / 2;
+    for (;;) {
+        t0 = (Te0[(s0 >> 24)       ]
+              ^ Te1[(s1 >> 16) & 0xff]
+              ^ Te2[(s2 >>  8) & 0xff]
+              ^ Te3[(s3      ) & 0xff]
+              ^ rk[4]);
+        t1 = (Te0[(s1 >> 24)       ]
+              ^ Te1[(s2 >> 16) & 0xff]
+              ^ Te2[(s3 >>  8) & 0xff]
+              ^ Te3[(s0      ) & 0xff]
+              ^ rk[5]);
+        t2 = (Te0[(s2 >> 24)       ]
+              ^ Te1[(s3 >> 16) & 0xff]
+              ^ Te2[(s0 >>  8) & 0xff]
+              ^ Te3[(s1      ) & 0xff]
+              ^ rk[6]);
+        t3 = (Te0[(s3 >> 24)       ]
+              ^ Te1[(s0 >> 16) & 0xff]
+              ^ Te2[(s1 >>  8) & 0xff]
+              ^ Te3[(s2      ) & 0xff]
+              ^ rk[7]);
+
+        rk += 8;
+        if (--r == 0) {
+            break;
+        }
+
+        s0 = (Te0[(t0 >> 24)       ]
+              ^ Te1[(t1 >> 16) & 0xff]
+              ^ Te2[(t2 >>  8) & 0xff]
+              ^ Te3[(t3      ) & 0xff]
+              ^ rk[0]);
+        s1 = (Te0[(t1 >> 24)       ]
+              ^ Te1[(t2 >> 16) & 0xff]
+              ^ Te2[(t3 >>  8) & 0xff]
+              ^ Te3[(t0      ) & 0xff]
+              ^ rk[1]);
+        s2 = (Te0[(t2 >> 24)       ]
+              ^ Te1[(t3 >> 16) & 0xff]
+              ^ Te2[(t0 >>  8) & 0xff]
+              ^ Te3[(t1      ) & 0xff]
+              ^ rk[2]);
+        s3 = (Te0[(t3 >> 24)       ]
+              ^ Te1[(t0 >> 16) & 0xff]
+              ^ Te2[(t1 >>  8) & 0xff]
+              ^ Te3[(t2      ) & 0xff]
+              ^ rk[3]);
+    }
+
+    /* Apply last round and map cipher state to byte array block. */
+    s0 = ((Te4[(t0 >> 24)       ] & 0xff000000)
+          ^ (Te4[(t1 >> 16) & 0xff] & 0x00ff0000)
+          ^ (Te4[(t2 >>  8) & 0xff] & 0x0000ff00)
+          ^ (Te4[(t3      ) & 0xff] & 0x000000ff)
+          ^ rk[0]);
+    put_u32(output     , s0);
+    s1 = ((Te4[(t1 >> 24)       ] & 0xff000000)
+          ^ (Te4[(t2 >> 16) & 0xff] & 0x00ff0000)
+          ^ (Te4[(t3 >>  8) & 0xff] & 0x0000ff00)
+          ^ (Te4[(t0      ) & 0xff] & 0x000000ff)
+          ^ rk[1]);
+    put_u32(output +  4, s1);
+    s2 = ((Te4[(t2 >> 24)       ] & 0xff000000)
+          ^ (Te4[(t3 >> 16) & 0xff] & 0x00ff0000)
+          ^ (Te4[(t0 >>  8) & 0xff] & 0x0000ff00)
+          ^ (Te4[(t1      ) & 0xff] & 0x000000ff)
+          ^ rk[2]);
+    put_u32(output +  8, s2);
+    s3 = ((Te4[(t3 >> 24)       ] & 0xff000000)
+          ^ (Te4[(t0 >> 16) & 0xff] & 0x00ff0000)
+          ^ (Te4[(t1 >>  8) & 0xff] & 0x0000ff00)
+          ^ (Te4[(t2      ) & 0xff] & 0x000000ff)
+          ^ rk[3]);
+    put_u32(output + 12, s3);
+}
diff --git a/lib/aes128.h b/lib/aes128.h
new file mode 100644 (file)
index 0000000..bbd7475
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Based on rijndael.txt by Philip J. Erdelsky, downloaded from
+ * http://www.efgh.com/software/rijndael.htm on September 24, 2009.  The
+ * license information there is: "Public domain; no restrictions on use."
+ * The Apache license above applies only to Nicira's modifications to the
+ * original code.
+ */
+
+#ifndef AES128_H
+#define AES128_H
+
+#include <stdint.h>
+
+struct aes128 {
+    uint32_t rk[128/8 + 28];
+};
+
+void aes128_schedule(struct aes128 *, const uint8_t key[16]);
+void aes128_encrypt(const struct aes128 *, const void *, void *);
+
+#endif  /* aes128.h */
index 9ba513a..ee5a195 100644 (file)
@@ -8,10 +8,14 @@
 noinst_LIBRARIES += lib/libopenvswitch.a
 
 lib_libopenvswitch_a_SOURCES = \
+       lib/aes128.c \
+       lib/aes128.h \
        lib/backtrace.c \
        lib/backtrace.h \
        lib/bitmap.c \
        lib/bitmap.h \
+       lib/byteq.c \
+       lib/byteq.h \
        lib/cfg.c \
        lib/cfg.h \
        lib/classifier.c \
@@ -49,12 +53,18 @@ lib_libopenvswitch_a_SOURCES = \
        lib/hash.h \
        lib/hmap.c \
        lib/hmap.h \
+       lib/json.c \
+       lib/json.h \
+       lib/jsonrpc.c \
+       lib/jsonrpc.h \
        lib/leak-checker.c \
        lib/leak-checker.h \
        lib/learning-switch.c \
        lib/learning-switch.h \
        lib/list.c \
        lib/list.h \
+       lib/lockfile.c \
+       lib/lockfile.h \
        lib/mac-learning.c \
        lib/mac-learning.h \
        lib/netdev-linux.c \
@@ -67,6 +77,17 @@ lib_libopenvswitch_a_SOURCES = \
        lib/ofp-print.h \
        lib/ofpbuf.c \
        lib/ofpbuf.h \
+       lib/ovsdb-data.c \
+       lib/ovsdb-data.h \
+       lib/ovsdb-error.c \
+       lib/ovsdb-error.h \
+       lib/ovsdb-idl-provider.h \
+       lib/ovsdb-idl.c \
+       lib/ovsdb-idl.h \
+       lib/ovsdb-parser.c \
+       lib/ovsdb-parser.h \
+       lib/ovsdb-types.c \
+       lib/ovsdb-types.h \
        lib/packets.c \
        lib/packets.h \
        lib/pcap.c \
@@ -83,6 +104,8 @@ lib_libopenvswitch_a_SOURCES = \
        lib/random.h \
        lib/rconn.c \
        lib/rconn.h \
+       lib/reconnect.c \
+       lib/reconnect.h \
        lib/rtnetlink.c \
        lib/rtnetlink.h \
        lib/sat-math.h \
@@ -94,8 +117,17 @@ lib_libopenvswitch_a_SOURCES = \
        lib/signals.h \
        lib/socket-util.c \
        lib/socket-util.h \
+       lib/sort.c \
+       lib/sort.h \
        lib/stp.c \
        lib/stp.h \
+       lib/stream-fd.c \
+       lib/stream-fd.h \
+       lib/stream-provider.h \
+       lib/stream-tcp.c \
+       lib/stream-unix.c \
+       lib/stream.c \
+       lib/stream.h \
        lib/svec.c \
        lib/svec.h \
        lib/tag.c \
@@ -103,8 +135,12 @@ lib_libopenvswitch_a_SOURCES = \
        lib/timeval.c \
        lib/timeval.h \
        lib/type-props.h \
+       lib/uuid.c \
+       lib/uuid.h \
        lib/unixctl.c \
        lib/unixctl.h \
+       lib/unicode.c \
+       lib/unicode.h \
        lib/util.c \
        lib/util.h \
        lib/valgrind.h \
@@ -153,10 +189,13 @@ EXTRA_DIST += \
 
 EXTRA_DIST += \
        lib/common.man \
+       lib/common-syn.man \
        lib/daemon.man \
+       lib/daemon-syn.man \
        lib/dpif.man \
        lib/leak-checker.man \
        lib/vlog-unixctl.man \
+       lib/vlog-syn.man \
        lib/vlog.man
 
 
@@ -175,9 +214,9 @@ install-data-local:
 
 # All the source files that have coverage counters.
 COVERAGE_FILES = \
-       lib/cfg.c \
        lib/dpif.c \
        lib/flow.c \
+       lib/lockfile.c \
        lib/hmap.c \
        lib/mac-learning.c \
        lib/netdev.c \
@@ -188,6 +227,7 @@ COVERAGE_FILES = \
        lib/process.c \
        lib/rconn.c \
        lib/rtnetlink.c \
+       lib/stream.c \
        lib/timeval.c \
        lib/unixctl.c \
        lib/util.c \
similarity index 81%
rename from extras/ezio/byteq.c
rename to lib/byteq.c
index b7df400..aa1e06f 100644 (file)
@@ -14,7 +14,7 @@
  */
 
 #include <config.h>
-#include "extras/ezio/byteq.h"
+#include "byteq.h"
 #include <assert.h>
 #include <errno.h>
 #include <string.h>
 /* The queue size must be a power of 2. */
 BUILD_ASSERT_DECL(!(BYTEQ_SIZE & (BYTEQ_SIZE - 1)));
 
-static uint8_t *head(struct byteq *);
-static int headroom(const struct byteq *);
-static void advance_head(struct byteq *, unsigned int n);
-static int tailroom(const struct byteq *);
-static const uint8_t *tail(const struct byteq *);
-static void advance_tail(struct byteq *, unsigned int n);
-
 /* Initializes 'q' as empty. */
 void
 byteq_init(struct byteq *q)
@@ -73,7 +66,7 @@ void
 byteq_put(struct byteq *q, uint8_t c)
 {
     assert(!byteq_is_full(q));
-    *head(q) = c;
+    *byteq_head(q) = c;
     q->head++;
 }
 
@@ -85,9 +78,9 @@ byteq_putn(struct byteq *q, const void *p_, size_t n)
     const uint8_t *p = p_;
     assert(byteq_avail(q) >= n);
     while (n > 0) {
-        size_t chunk = MIN(n, headroom(q));
-        memcpy(head(q), p, chunk);
-        advance_head(q, chunk);
+        size_t chunk = MIN(n, byteq_headroom(q));
+        memcpy(byteq_head(q), p, chunk);
+        byteq_advance_head(q, chunk);
         p += chunk;
         n -= chunk;
     }
@@ -108,7 +101,7 @@ byteq_get(struct byteq *q)
 {
     uint8_t c;
     assert(!byteq_is_empty(q));
-    c = *tail(q);
+    c = *byteq_tail(q);
     q->tail++;
     return c;
 }
@@ -120,9 +113,9 @@ int
 byteq_write(struct byteq *q, int fd)
 {
     while (!byteq_is_empty(q)) {
-        ssize_t n = write(fd, tail(q), tailroom(q));
+        ssize_t n = write(fd, byteq_tail(q), byteq_tailroom(q));
         if (n > 0) {
-            advance_tail(q, n);
+            byteq_advance_tail(q, n);
         } else {
             assert(n < 0);
             return errno;
@@ -139,20 +132,20 @@ int
 byteq_read(struct byteq *q, int fd)
 {
     while (!byteq_is_full(q)) {
-        ssize_t n = read(fd, head(q), headroom(q));
+        ssize_t n = read(fd, byteq_head(q), byteq_headroom(q));
         if (n > 0) {
-            advance_head(q, n);
+            byteq_advance_head(q, n);
         } else {
             return !n ? EOF : errno;
         }
     }
     return 0;
 }
-\f
+
 /* Returns the number of contiguous bytes of in-use space starting at the tail
  * of 'q'. */
-static int
-tailroom(const struct byteq *q)
+int
+byteq_tailroom(const struct byteq *q)
 {
     int used = byteq_used(q);
     int tail_to_end = BYTEQ_SIZE - (q->tail & (BYTEQ_SIZE - 1));
@@ -161,33 +154,33 @@ tailroom(const struct byteq *q)
 
 /* Returns the first in-use byte of 'q', the point at which data is removed
  * from 'q'. */
-static const uint8_t *
-tail(const struct byteq *q)
+const uint8_t *
+byteq_tail(const struct byteq *q)
 {
     return &q->buffer[q->tail & (BYTEQ_SIZE - 1)];
 }
 
 /* Removes 'n' bytes from the tail of 'q', which must have at least 'n' bytes
  * of tailroom. */
-static void
-advance_tail(struct byteq *q, unsigned int n)
+void
+byteq_advance_tail(struct byteq *q, unsigned int n)
 {
-    assert(tailroom(q) >= n);
+    assert(byteq_tailroom(q) >= n);
     q->tail += n;
 }
 
 /* Returns the byte after the last in-use byte of 'q', the point at which new
  * data will be added to 'q'. */
-static uint8_t *
-head(struct byteq *q)
+uint8_t *
+byteq_head(struct byteq *q)
 {
     return &q->buffer[q->head & (BYTEQ_SIZE - 1)];
 }
 
 /* Returns the number of contiguous bytes of free space starting at the head
  * of 'q'. */
-static int
-headroom(const struct byteq *q)
+int
+byteq_headroom(const struct byteq *q)
 {
     int avail = byteq_avail(q);
     int head_to_end = BYTEQ_SIZE - (q->head & (BYTEQ_SIZE - 1));
@@ -196,9 +189,9 @@ headroom(const struct byteq *q)
 
 /* Adds to 'q' the 'n' bytes after the last currently in-use byte of 'q'.  'q'
  * must have at least 'n' bytes of headroom. */
-static void
-advance_head(struct byteq *q, unsigned int n)
+void
+byteq_advance_head(struct byteq *q, unsigned int n)
 {
-    assert(headroom(q) >= n);
+    assert(byteq_headroom(q) >= n);
     q->head += n;
 }
similarity index 83%
rename from extras/ezio/byteq.h
rename to lib/byteq.h
index 0a016cd..84d8696 100644 (file)
@@ -42,4 +42,11 @@ uint8_t byteq_get(struct byteq *);
 int byteq_write(struct byteq *, int fd);
 int byteq_read(struct byteq *, int fd);
 
+uint8_t *byteq_head(struct byteq *);
+int byteq_headroom(const struct byteq *);
+void byteq_advance_head(struct byteq *, unsigned int n);
+int byteq_tailroom(const struct byteq *);
+const uint8_t *byteq_tail(const struct byteq *);
+void byteq_advance_tail(struct byteq *, unsigned int n);
+
 #endif /* byteq.h */
index d0170b6..d61cd77 100644 (file)
--- a/lib/cfg.c
+++ b/lib/cfg.c
 #include <netinet/in.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
 #include "coverage.h"
 #include "dynamic-string.h"
+#include "lockfile.h"
 #include "ofpbuf.h"
 #include "packets.h"
 #include "svec.h"
@@ -52,8 +51,7 @@ static char *cfg_name;
 static char *tmp_name;
 
 /* Lock information. */
-static char *lock_name;
-static int lock_fd = -1;
+static struct lockfile *lockfile;
 
 /* Flag to indicate whether local modifications have been made. */
 static bool dirty;
@@ -106,15 +104,14 @@ cfg_init(void)
 int
 cfg_set_file(const char *file_name)
 {
-    const char *slash;
+    char *lock_name;
     int fd;
 
     if (cfg_name) {
-        assert(lock_fd < 0);
+        assert(!lockfile);
         free(cfg_name);
-        free(lock_name);
         free(tmp_name);
-        cfg_name = lock_name = tmp_name = NULL;
+        cfg_name = tmp_name = NULL;
     }
 
     /* Make sure that we can open this file for reading. */
@@ -131,18 +128,11 @@ cfg_set_file(const char *file_name)
      * rename(tmp_name, cfg_name) will work. */
     tmp_name = xasprintf("%s.~tmp~", file_name);
 
-    /* Put the lock file in the same directory as cfg_name, but prefixed by
-     * a dot so as not to garner administrator interest. */
-    slash = strrchr(file_name, '/');
-    if (slash) {
-        lock_name = xasprintf("%.*s/.%s.~lock~",
-                              (int) (slash - file_name), file_name, slash + 1);
-    } else {
-        lock_name = xasprintf(".%s.~lock~", file_name);
-    }
-
+    lock_name = lockfile_name(file_name);
     VLOG_INFO("using \"%s\" as configuration file, \"%s\" as lock file",
               file_name, lock_name);
+    free(lock_name);
+
     return 0;
 }
 
@@ -280,54 +270,12 @@ cfg_get_cookie(uint8_t *cookie)
 void
 cfg_unlock(void)
 {
-    if (lock_fd != -1) {
-        COVERAGE_INC(cfg_unlock);
-        close(lock_fd);
-        lock_fd = -1;
+    if (lockfile) {
+        lockfile_unlock(lockfile);
+        lockfile = NULL;
     }
 }
 
-static int
-open_lockfile(const char *name)
-{
-    for (;;) {
-        /* Try to open an existing lock file. */
-        int fd = open(name, O_RDWR);
-        if (fd >= 0) {
-            return fd;
-        } else if (errno != ENOENT) {
-            VLOG_WARN("%s: failed to open lock file: %s",
-                      name, strerror(errno));
-            return -errno;
-        }
-
-        /* Try to create a new lock file. */
-        VLOG_INFO("%s: lock file does not exist, creating", name);
-        fd = open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
-        if (fd >= 0) {
-            return fd;
-        } else if (errno != EEXIST) {
-            VLOG_WARN("%s: failed to create lock file: %s",
-                      name, strerror(errno));
-            return -errno;
-        }
-
-        /* Someone else created the lock file.  Try again. */
-    }
-}
-
-static int
-try_lock(int fd, bool block)
-{
-    struct flock l;
-    memset(&l, 0, sizeof l);
-    l.l_type = F_WRLCK;
-    l.l_whence = SEEK_SET;
-    l.l_start = 0;
-    l.l_len = 0;
-    return fcntl(fd, block ? F_SETLKW : F_SETLK, &l) == -1 ? errno : 0;
-}
-
 /* Locks the configuration file against modification by other processes and
  * re-reads it from disk.
  *
@@ -339,65 +287,18 @@ try_lock(int fd, bool block)
 int
 cfg_lock(uint8_t *cookie, int timeout)
 {
-    long long int start;
-    long long int elapsed = 0;
-    int fd;
-    uint8_t curr_cookie[CFG_COOKIE_LEN];
-
-    assert(lock_fd < 0);
-    COVERAGE_INC(cfg_lock);
-
-    time_refresh();
-    start = time_msec();
-    for (;;) {
-        int error;
-
-        /* Open lock file. */
-        fd = open_lockfile(lock_name);
-        if (fd < 0) {
-            return -fd;
-        }
-
-        /* Try to lock it.  This will block (if 'timeout' > 0). */
-        error = try_lock(fd, timeout > 0);
-        time_refresh();
-        elapsed = time_msec() - start;
-        if (!error) {
-            /* Success! */
-            break;
-        }
-
-        /* Lock failed.  Close the lock file and reopen it on the next
-         * iteration, just in case someone deletes it underneath us (even
-         * though that should not happen). */
-        close(fd);
-        if (error != EINTR) {
-            /* Hard error, give up. */
-            COVERAGE_INC(cfg_lock_error);
-            VLOG_WARN("%s: failed to lock file "
-                      "(after %lld ms, with %d-ms timeout): %s",
-                      lock_name, elapsed, timeout, strerror(error));
-            return error;
-        }
+    int error;
 
-        /* Probably, the periodic timer set up by time_init() woke up us.  Just
-         * check whether it's time to give up. */
-        if (timeout != INT_MAX && elapsed >= timeout) {
-            COVERAGE_INC(cfg_lock_timeout);
-            VLOG_WARN("%s: giving up on lock file after %lld ms",
-                      lock_name, elapsed);
-            return ETIMEDOUT;
-        }
-        COVERAGE_INC(cfg_lock_retry);
-    }
-    if (elapsed) {
-        VLOG_WARN("%s: waited %lld ms for lock file", lock_name, elapsed);
+    assert(!lockfile);
+    error = lockfile_lock(cfg_name, timeout, &lockfile);
+    if (error) {
+        return error;
     }
-    lock_fd = fd;
 
     cfg_read();
 
     if (cookie) {
+        uint8_t curr_cookie[CFG_COOKIE_LEN];
         cfg_get_cookie(curr_cookie);
 
         if (memcmp(curr_cookie, cookie, sizeof *curr_cookie)) {
index 6b79c5e..a2a43a1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008 Nicira Networks.
+ * Copyright (c) 2008, 2009 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
 #include "command-line.h"
 #include <getopt.h>
 #include <limits.h>
+#include <stdlib.h>
 #include "util.h"
 #include "vlog.h"
 
@@ -47,3 +48,42 @@ long_options_to_short_options(const struct option options[])
     return xstrdup(short_options);
 }
 
+/* Runs the command designated by argv[0] within the command table specified by
+ * 'commands', which must be terminated by a command whose 'name' member is a
+ * null pointer.
+ *
+ * Command-line options should be stripped off, so that a typical invocation
+ * looks like "run_command(argc - optind, argv + optind, my_commands);". */
+void
+run_command(int argc, char *argv[], const struct command commands[])
+{
+    const struct command *p;
+
+    if (argc < 1) {
+        ovs_fatal(0, "missing command name; use --help for help");
+    }
+
+    for (p = commands; p->name != NULL; p++) {
+        if (!strcmp(p->name, argv[0])) {
+            int n_arg = argc - 1;
+            if (n_arg < p->min_args) {
+                ovs_fatal(0, "'%s' command requires at least %d arguments",
+                          p->name, p->min_args);
+            } else if (n_arg > p->max_args) {
+                ovs_fatal(0, "'%s' command takes at most %d arguments",
+                          p->name, p->max_args);
+            } else {
+                p->handler(argc, argv);
+                if (ferror(stdout)) {
+                    ovs_fatal(0, "write to stdout failed");
+                }
+                if (ferror(stderr)) {
+                    ovs_fatal(0, "write to stderr failed");
+                }
+                return;
+            }
+        }
+    }
+
+    ovs_fatal(0, "unknown command '%s'; use --help for help", argv[0]);
+}
index 426ce62..bc2071e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008 Nicira Networks.
+ * Copyright (c) 2008, 2009 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 /* Utilities for command-line parsing. */
 
+#include "compiler.h"
+
 struct option;
+
+struct command {
+    const char *name;
+    int min_args;
+    int max_args;
+    void (*handler)(int argc, char *argv[]);
+};
+
 char *long_options_to_short_options(const struct option *options);
+void run_command(int argc, char *argv[], const struct command[]);
 
 #endif /* command-line.h */
diff --git a/lib/common-syn.man b/lib/common-syn.man
new file mode 100644 (file)
index 0000000..ae1bed5
--- /dev/null
@@ -0,0 +1,4 @@
+.IP "Common options:"
+[\fB-h\fR | \fB--help\fR]
+[\fB-V\fR | \fB--version\fR]
+
index 17e245f..216dd6a 100644 (file)
@@ -26,5 +26,6 @@
 #define ALWAYS_INLINE __attribute__((always_inline))
 #define likely(x) __builtin_expect((x),1)
 #define unlikely(x) __builtin_expect((x),0)
+#define WARN_UNUSED_RESULT __attribute__((__warn_unused_result__))
 
 #endif /* compiler.h */
index cdc796e..8b5e9d9 100644 (file)
@@ -30,7 +30,8 @@
 static unsigned int epoch;
 
 static void
-coverage_unixctl_log(struct unixctl_conn *conn, const char *args UNUSED)
+coverage_unixctl_log(struct unixctl_conn *conn, const char *args UNUSED,
+                     void *aux UNUSED)
 {
     coverage_log(VLL_WARN, false);
     unixctl_command_reply(conn, 200, NULL);
@@ -39,7 +40,7 @@ coverage_unixctl_log(struct unixctl_conn *conn, const char *args UNUSED)
 void
 coverage_init(void)
 {
-    unixctl_command_register("coverage/log", coverage_unixctl_log);
+    unixctl_command_register("coverage/log", coverage_unixctl_log, NULL);
 }
 
 /* Sorts coverage counters in descending order by count, within equal counts
diff --git a/lib/daemon-syn.man b/lib/daemon-syn.man
new file mode 100644 (file)
index 0000000..4970564
--- /dev/null
@@ -0,0 +1,5 @@
+.IP "Daemon options:"
+[\fB--pidfile\fR[\fB=\fIpidfile\fR]]
+[\fB--overwrite-pidfile\fR]
+[\fB--detach\fR]
+[\fB--no-chdir\fR]
index a35c639..0dcc66f 100644 (file)
@@ -23,6 +23,7 @@
 #include <unistd.h>
 #include "fatal-signal.h"
 #include "dirs.h"
+#include "lockfile.h"
 #include "timeval.h"
 #include "util.h"
 
@@ -80,6 +81,13 @@ set_no_chdir(void)
     chdir_ = false;
 }
 
+/* Will we chdir to "/" as part of daemonizing? */
+bool
+is_chdir_enabled(void)
+{
+    return chdir_;
+}
+
 /* Normally, die_if_already_running() will terminate the program with a message
  * if a locked pidfile already exists.  If this function is called,
  * die_if_already_running() will merely log a warning. */
@@ -97,6 +105,13 @@ set_detach(void)
     detach = true;
 }
 
+/* Will daemonize() really detach? */
+bool
+get_detach(void)
+{
+    return detach;
+}
+
 /* If a pidfile has been configured and that pidfile already exists and is
  * locked by a running process, returns the pid of the running process.
  * Otherwise, returns 0. */
@@ -224,6 +239,7 @@ daemonize(void)
                 chdir("/");
             }
             time_postfork();
+            lockfile_postfork();
             break;
 
         case -1:
index d0c324c..06280ac 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008 Nicira Networks.
+ * Copyright (c) 2008, 2009 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -55,7 +55,9 @@ char *make_pidfile_name(const char *name);
 void set_pidfile(const char *name);
 const char *get_pidfile(void);
 void set_no_chdir(void);
+bool is_chdir_enabled(void);
 void set_detach(void);
+bool get_detach(void);
 void daemonize(void);
 void die_if_already_running(void);
 void ignore_existing_pidfile(void);
index 4f90781..720cd2f 100644 (file)
@@ -172,7 +172,7 @@ dhclient_create(const char *netdev_name,
         return error;
     }
 
-    cli = xcalloc(1, sizeof *cli);
+    cli = xzalloc(sizeof *cli);
     cli->modify_request = modify_request;
     cli->validate_offer = validate_offer;
     cli->aux = aux;
index c4b5a99..816d402 100644 (file)
@@ -219,7 +219,7 @@ create_dp_netdev(const char *name, int dp_idx, struct dpif **dpifp)
     }
 
     /* Create datapath. */
-    dp_netdevs[dp_idx] = dp = xcalloc(1, sizeof *dp);
+    dp_netdevs[dp_idx] = dp = xzalloc(sizeof *dp);
     list_push_back(&dp_netdev_list, &dp->node);
     dp->dp_idx = dp_idx;
     dp->open_cnt = 0;
@@ -804,7 +804,7 @@ add_flow(struct dpif *dpif, struct odp_flow *odp_flow)
     struct dp_netdev_flow *flow;
     int error;
 
-    flow = xcalloc(1, sizeof *flow);
+    flow = xzalloc(sizeof *flow);
     flow->key = odp_flow->key;
     flow->key.reserved = 0;
 
index 9684ffa..527dee8 100644 (file)
@@ -71,6 +71,31 @@ ds_put_char(struct ds *ds, char c)
     *ds_put_uninit(ds, 1) = c;
 }
 
+/* Appends unicode code point 'uc' to 'ds' in UTF-8 encoding. */
+void
+ds_put_utf8(struct ds *ds, int uc)
+{
+    if (uc <= 0x7f) {
+        ds_put_char(ds, uc);
+    } else if (uc <= 0x7ff) {
+        ds_put_char(ds, 0xc0 | (uc >> 6));
+        ds_put_char(ds, 0x80 | (uc & 0x3f));
+    } else if (uc <= 0xffff) {
+        ds_put_char(ds, 0xe0 | (uc >> 12));
+        ds_put_char(ds, 0x80 | ((uc >> 6) & 0x3f));
+        ds_put_char(ds, 0x80 | (uc & 0x3f));
+    } else if (uc <= 0x10ffff) {
+        ds_put_char(ds, 0xf0 | (uc >> 18));
+        ds_put_char(ds, 0x80 | ((uc >> 12) & 0x3f));
+        ds_put_char(ds, 0x80 | ((uc >> 6) & 0x3f));
+        ds_put_char(ds, 0x80 | (uc & 0x3f));
+    } else {
+        /* Invalid code point.  Insert the Unicode general substitute
+         * REPLACEMENT CHARACTER. */
+        ds_put_utf8(ds, 0xfffd);
+    }
+}
+
 void
 ds_put_char_multiple(struct ds *ds, char c, size_t n)
 {
@@ -90,6 +115,13 @@ ds_put_cstr(struct ds *ds, const char *s)
     memcpy(ds_put_uninit(ds, s_len), s, s_len);
 }
 
+void
+ds_put_and_free_cstr(struct ds *ds, char *s)
+{
+    ds_put_cstr(ds, s);
+    free(s);
+}
+
 void
 ds_put_format(struct ds *ds, const char *format, ...)
 {
index a44e0b3..136cedc 100644 (file)
@@ -40,9 +40,11 @@ void ds_truncate(struct ds *, size_t new_length);
 void ds_reserve(struct ds *, size_t min_length);
 char *ds_put_uninit(struct ds *, size_t n);
 void ds_put_char(struct ds *, char);
+void ds_put_utf8(struct ds *, int uc);
 void ds_put_char_multiple(struct ds *, char, size_t n);
 void ds_put_buffer(struct ds *, const char *, size_t n);
 void ds_put_cstr(struct ds *, const char *);
+void ds_put_and_free_cstr(struct ds *, char *);
 void ds_put_format(struct ds *, const char *, ...) PRINTF_FORMAT(2, 3);
 void ds_put_format_valist(struct ds *, const char *, va_list)
     PRINTF_FORMAT(2, 0);
index f1daa6b..63b4784 100644 (file)
@@ -52,6 +52,21 @@ hash_words(const uint32_t *p, size_t n, uint32_t basis)
     return c;
 }
 
+/* Returns the hash of the pair of aligned 32-bit words at 'p', starting from
+ * 'basis'. */
+uint32_t
+hash_2words(const uint32_t *p, uint32_t basis)
+{
+    uint32_t a, b, c;
+
+    a = b = c = 0xdeadbeef + (2 << 2) + basis;
+    b += p[1];
+    a += p[0];
+    HASH_FINAL(a, b, c);
+
+    return c;
+}
+
 /* Returns the hash of the 'n' bytes at 'p', starting from 'basis'. */
 uint32_t
 hash_bytes(const void *p_, size_t n, uint32_t basis)
index 5485f8c..3f14038 100644 (file)
 #ifndef HASH_H
 #define HASH_H 1
 
+#include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
 #include <string.h>
+#include "util.h"
 
 /* This is the public domain lookup3 hash by Bob Jenkins from
  * http://burtleburtle.net/bob/c/lookup3.c, modified for style. */
@@ -47,6 +49,7 @@
     } while (0)
 
 uint32_t hash_words(const uint32_t *, size_t n_word, uint32_t basis);
+uint32_t hash_2words(const uint32_t *, uint32_t basis);
 uint32_t hash_bytes(const void *, size_t n_bytes, uint32_t basis);
 
 static inline uint32_t hash_string(const char *s, uint32_t basis)
@@ -62,10 +65,39 @@ static inline uint32_t hash_int(uint32_t x, uint32_t basis)
     x ^= x >> 17;
     x -= x << 9;
     x ^= x << 4;
+    x += basis;
     x -= x << 3;
     x ^= x << 10;
     x ^= x >> 15;
-    return x + basis;
+    return x;
+}
+
+/* An attempt at a useful 1-bit hash function.  Has not been analyzed for
+ * quality. */
+static inline uint32_t hash_boolean(bool x, uint32_t basis)
+{
+    enum { P0 = 0xc2b73583 };   /* This is hash_int(1, 0). */
+    enum { P1 = 0xe90f1258 };   /* This is hash_int(2, 0). */
+    return (x ? P0 : P1) ^ HASH_ROT(basis, 1);
+}
+
+static inline uint32_t hash_double(double x, uint32_t basis)
+{
+    BUILD_ASSERT_DECL(sizeof x == 8);
+    return hash_2words((const uint32_t *) &x, basis);
+}
+
+static inline uint32_t hash_pointer(const void *p, uint32_t basis)
+{
+    /* Often pointers are hashed simply by casting to integer type, but that
+     * has pitfalls since the lower bits of a pointer are often all 0 for
+     * alignment reasons.  It's hard to guess where the entropy really is, so
+     * we give up here and just use a high-quality hash function.
+     *
+     * The double cast suppresses a warning on 64-bit systems about casting to
+     * an integer to different size.  That's OK in this case, since most of the
+     * entropy in the pointer is almost certainly in the lower 32 bits. */
+    return hash_int((uint32_t) (uintptr_t) p, basis);
 }
 
 #endif /* hash.h */
index 1809a91..5396c79 100644 (file)
@@ -66,17 +66,46 @@ static inline void hmap_moved(struct hmap *,
 static inline void hmap_replace(struct hmap *, const struct hmap_node *old,
                                 struct hmap_node *new);
 
-/* Search. */
+/* Search.
+ *
+ * HMAP_FOR_EACH_WITH_HASH iterates NODE over all of the nodes in HMAP that
+ * have hash value equal to HASH.  HMAP_FOR_EACH_IN_BUCKET iterates NODE over
+ * all of the nodes in HMAP that would fall in the same bucket as HASH.  STRUCT
+ * and MEMBER must be the name of the struct that contains the 'struct
+ * hmap_node' and the name of the 'struct hmap_node' member, respectively.
+ *
+ * These macros may be used interchangeably to search for a particular value in
+ * an hmap, see, e.g. shash_find() for an example.  Usually, using
+ * HMAP_FOR_EACH_WITH_HASH provides an optimization, because comparing a hash
+ * value is usually cheaper than comparing an entire hash map key.  But for
+ * simple hash map keys, it makes sense to use HMAP_FOR_EACH_IN_BUCKET because
+ * it avoids doing two comparisons when a single simple comparison suffices.
+ *
+ * The loop should not change NODE to point to a different node or insert or
+ * delete nodes in HMAP (unless it "break"s out of the loop to terminate
+ * iteration).
+ *
+ * HASH is only evaluated once.
+ */
 #define HMAP_FOR_EACH_WITH_HASH(NODE, STRUCT, MEMBER, HASH, HMAP)       \
     for ((NODE) = CONTAINER_OF(hmap_first_with_hash(HMAP, HASH),        \
                                STRUCT, MEMBER);                         \
          &(NODE)->MEMBER != NULL;                                       \
          (NODE) = CONTAINER_OF(hmap_next_with_hash(&(NODE)->MEMBER),    \
                                STRUCT, MEMBER))
+#define HMAP_FOR_EACH_IN_BUCKET(NODE, STRUCT, MEMBER, HASH, HMAP)       \
+    for ((NODE) = CONTAINER_OF(hmap_first_in_bucket(HMAP, HASH),        \
+                               STRUCT, MEMBER);                         \
+         &(NODE)->MEMBER != NULL;                                       \
+         (NODE) = CONTAINER_OF(hmap_next_in_bucket(&(NODE)->MEMBER),    \
+                               STRUCT, MEMBER))
 
 static inline struct hmap_node *hmap_first_with_hash(const struct hmap *,
                                                      size_t hash);
 static inline struct hmap_node *hmap_next_with_hash(const struct hmap_node *);
+static inline struct hmap_node *hmap_first_in_bucket(const struct hmap *,
+                                                     size_t hash);
+static inline struct hmap_node *hmap_next_in_bucket(const struct hmap_node *);
 
 /* Iteration.
  *
@@ -190,6 +219,7 @@ hmap_replace(struct hmap *hmap,
     }
     *bucket = new;
     new->hash = old->hash;
+    new->next = old->next;
 }
 
 static inline struct hmap_node *
@@ -209,6 +239,28 @@ hmap_first_with_hash(const struct hmap *hmap, size_t hash)
     return hmap_next_with_hash__(hmap->buckets[hash & hmap->mask], hash);
 }
 
+/* Returns the first node in 'hmap' in the bucket in which the given 'hash'
+ * would land, or a null pointer if that bucket is empty. */
+static inline struct hmap_node *
+hmap_first_in_bucket(const struct hmap *hmap, size_t hash)
+{
+    return hmap->buckets[hash & hmap->mask];
+}
+
+/* Returns the next node in the same bucket as 'node', or a null pointer if
+ * there are no more nodes in that bucket.
+ *
+ * If the hash map has been reallocated since 'node' was visited, some nodes
+ * may be skipped; if new nodes with the same hash value have been added, they
+ * will be skipped.  (Removing 'node' from the hash map does not prevent
+ * calling this function, since node->next is preserved, although freeing
+ * 'node' of course does.) */
+static inline struct hmap_node *
+hmap_next_in_bucket(const struct hmap_node *node)
+{
+    return node->next;
+}
+
 /* Returns the next node in the same hash map as 'node' with the same hash
  * value, or a null pointer if no more nodes have that hash value.
  *
diff --git a/lib/json.c b/lib/json.c
new file mode 100644 (file)
index 0000000..cdcfba5
--- /dev/null
@@ -0,0 +1,1570 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "json.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <float.h>
+#include <limits.h>
+#include <math.h>
+#include <string.h>
+
+#include "dynamic-string.h"
+#include "hash.h"
+#include "shash.h"
+#include "unicode.h"
+#include "util.h"
+
+/* The type of a JSON token. */
+enum json_token_type {
+    T_EOF = 0,
+    T_BEGIN_ARRAY = '[',
+    T_END_ARRAY = ']',
+    T_BEGIN_OBJECT = '{',
+    T_END_OBJECT = '}',
+    T_NAME_SEPARATOR = ':',
+    T_VALUE_SEPARATOR = ',',
+    T_FALSE = UCHAR_MAX + 1,
+    T_NULL,
+    T_TRUE,
+    T_INTEGER,
+    T_REAL,
+    T_STRING
+};
+
+/* A JSON token.
+ *
+ * RFC 4627 doesn't define a lexical structure for JSON but I believe this to
+ * be compliant with the standard.
+ */
+struct json_token {
+    enum json_token_type type;
+    union {
+        double real;
+        long long int integer;
+        const char *string;
+    } u;
+};
+
+enum json_lex_state {
+    JSON_LEX_START,             /* Not inside a token. */
+    JSON_LEX_NUMBER,            /* Reading a number. */
+    JSON_LEX_KEYWORD,           /* Reading a keyword. */
+    JSON_LEX_STRING,            /* Reading a quoted string. */
+    JSON_LEX_ESCAPE             /* In a quoted string just after a "\". */
+};
+
+enum json_parse_state {
+    JSON_PARSE_START,           /* Beginning of input. */
+    JSON_PARSE_END,             /* End of input. */
+
+    /* Objects. */
+    JSON_PARSE_OBJECT_INIT,     /* Expecting '}' or an object name. */
+    JSON_PARSE_OBJECT_NAME,     /* Expecting an object name. */
+    JSON_PARSE_OBJECT_COLON,    /* Expecting ':'. */
+    JSON_PARSE_OBJECT_VALUE,    /* Expecting an object value. */
+    JSON_PARSE_OBJECT_NEXT,     /* Expecting ',' or '}'. */
+
+    /* Arrays. */
+    JSON_PARSE_ARRAY_INIT,      /* Expecting ']' or a value. */
+    JSON_PARSE_ARRAY_VALUE,     /* Expecting a value. */
+    JSON_PARSE_ARRAY_NEXT       /* Expecting ',' or ']'. */
+};
+
+struct json_parser_node {
+    struct json *json;
+};
+
+/* A JSON parser. */
+struct json_parser {
+    int flags;
+
+    /* Lexical analysis. */
+    enum json_lex_state lex_state;
+    struct ds buffer;           /* Buffer for accumulating token text. */
+
+    /* Parsing. */
+    enum json_parse_state parse_state;
+#define JSON_MAX_HEIGHT 1000
+    struct json_parser_node *stack;
+    size_t height, allocated_height;
+    char *member_name;
+
+    /* Parse status. */
+    bool done;
+    char *error;                /* Error message, if any, null if none yet. */
+};
+
+static struct json *json_create(enum json_type type);
+static void json_parser_input(struct json_parser *, struct json_token *);
+
+static void json_error(struct json_parser *p, const char *format, ...)
+    PRINTF_FORMAT(2, 3);
+\f
+const char *
+json_type_to_string(enum json_type type)
+{
+    switch (type) {
+    case JSON_NULL:
+        return "null";
+
+    case JSON_FALSE:
+        return "false";
+
+    case JSON_TRUE:
+        return "true";
+
+    case JSON_OBJECT:
+        return "object";
+
+    case JSON_ARRAY:
+        return "array";
+
+    case JSON_INTEGER:
+    case JSON_REAL:
+        return "number";
+
+    case JSON_STRING:
+        return "string";
+
+    case JSON_N_TYPES:
+    default:
+        return "<invalid>";
+    }
+}
+\f
+/* Functions for manipulating struct json. */
+
+struct json *
+json_null_create(void)
+{
+    return json_create(JSON_NULL);
+}
+
+struct json *
+json_boolean_create(bool b)
+{
+    return json_create(b ? JSON_TRUE : JSON_FALSE);
+}
+
+struct json *
+json_string_create_nocopy(char *s)
+{
+    struct json *json = json_create(JSON_STRING);
+    json->u.string = s;
+    return json;
+}
+
+struct json *
+json_string_create(const char *s)
+{
+    return json_string_create_nocopy(xstrdup(s));
+}
+
+struct json *
+json_array_create_empty(void)
+{
+    struct json *json = json_create(JSON_ARRAY);
+    json->u.array.elems = NULL;
+    json->u.array.n = 0;
+    json->u.array.n_allocated = 0;
+    return json;
+}
+
+void
+json_array_add(struct json *array_, struct json *element)
+{
+    struct json_array *array = json_array(array_);
+    if (array->n >= array->n_allocated) {
+        array->elems = x2nrealloc(array->elems, &array->n_allocated,
+                                  sizeof *array->elems);
+    }
+    array->elems[array->n++] = element;
+}
+
+void
+json_array_trim(struct json *array_)
+{
+    struct json_array *array = json_array(array_);
+    if (array->n < array->n_allocated){
+        array->n_allocated = array->n;
+        array->elems = xrealloc(array->elems, array->n * sizeof *array->elems);
+    }
+}
+
+struct json *
+json_array_create(struct json **elements, size_t n)
+{
+    struct json *json = json_create(JSON_ARRAY);
+    json->u.array.elems = elements;
+    json->u.array.n = n;
+    json->u.array.n_allocated = n;
+    return json;
+}
+
+struct json *
+json_array_create_2(struct json *elem0, struct json *elem1)
+{
+    struct json **elems = xmalloc(2 * sizeof *elems);
+    elems[0] = elem0;
+    elems[1] = elem1;
+    return json_array_create(elems, 2);
+}
+
+struct json *
+json_array_create_3(struct json *elem0, struct json *elem1, struct json *elem2)
+{
+    struct json **elems = xmalloc(3 * sizeof *elems);
+    elems[0] = elem0;
+    elems[1] = elem1;
+    elems[2] = elem2;
+    return json_array_create(elems, 3);
+}
+
+struct json *
+json_object_create(void)
+{
+    struct json *json = json_create(JSON_OBJECT);
+    json->u.object = xmalloc(sizeof *json->u.object);
+    shash_init(json->u.object);
+    return json;
+}
+
+struct json *
+json_integer_create(long long int integer)
+{
+    struct json *json = json_create(JSON_INTEGER);
+    json->u.integer = integer;
+    return json;
+}
+
+struct json *
+json_real_create(double real)
+{
+    struct json *json = json_create(JSON_REAL);
+    json->u.real = real;
+    return json;
+}
+
+void
+json_object_put(struct json *json, const char *name, struct json *value)
+{
+    shash_add(json->u.object, name, value);
+}
+
+void
+json_object_put_string(struct json *json, const char *name, const char *value)
+{
+    json_object_put(json, name, json_string_create(value));
+}
+
+const char *
+json_string(const struct json *json)
+{
+    assert(json->type == JSON_STRING);
+    return json->u.string;
+}
+
+struct json_array *
+json_array(const struct json *json)
+{
+    assert(json->type == JSON_ARRAY);
+    return (struct json_array *) &json->u.array;
+}
+
+struct shash *
+json_object(const struct json *json)
+{
+    assert(json->type == JSON_OBJECT);
+    return (struct shash *) json->u.object;
+}
+
+bool
+json_boolean(const struct json *json)
+{
+    assert(json->type == JSON_TRUE || json->type == JSON_FALSE);
+    return json->type == JSON_TRUE;
+}
+
+double
+json_real(const struct json *json)
+{
+    assert(json->type == JSON_REAL || json->type == JSON_INTEGER);
+    return json->type == JSON_REAL ? json->u.real : json->u.integer;
+}
+
+int64_t
+json_integer(const struct json *json)
+{
+    assert(json->type == JSON_INTEGER);
+    return json->u.integer;
+}
+\f
+static void json_destroy_object(struct shash *object);
+static void json_destroy_array(struct json_array *array);
+
+/* Frees 'json' and everything it points to, recursively. */
+void
+json_destroy(struct json *json)
+{
+    if (json) {
+        switch (json->type) {
+        case JSON_OBJECT:
+            json_destroy_object(json->u.object);
+            break;
+
+        case JSON_ARRAY:
+            json_destroy_array(&json->u.array);
+            break;
+
+        case JSON_STRING:
+            free(json->u.string);
+            break;
+
+        case JSON_NULL:
+        case JSON_FALSE:
+        case JSON_TRUE:
+        case JSON_INTEGER:
+        case JSON_REAL:
+            break;
+
+        case JSON_N_TYPES:
+            NOT_REACHED();
+        }
+        free(json);
+    }
+}
+
+static void
+json_destroy_object(struct shash *object)
+{
+    struct shash_node *node, *next;
+
+    SHASH_FOR_EACH_SAFE (node, next, object) {
+        struct json *value = node->data;
+
+        json_destroy(value);
+        shash_delete(object, node);
+    }
+    shash_destroy(object);
+    free(object);
+}
+
+static void
+json_destroy_array(struct json_array *array)
+{
+    size_t i;
+
+    for (i = 0; i < array->n; i++) {
+        json_destroy(array->elems[i]);
+    }
+    free(array->elems);
+}
+\f
+static struct json *json_clone_object(const struct shash *object);
+static struct json *json_clone_array(const struct json_array *array);
+
+/* Returns a deep copy of 'json'. */
+struct json *
+json_clone(const struct json *json)
+{
+    switch (json->type) {
+    case JSON_OBJECT:
+        return json_clone_object(json->u.object);
+
+    case JSON_ARRAY:
+        return json_clone_array(&json->u.array);
+
+    case JSON_STRING:
+        return json_string_create(json->u.string);
+
+    case JSON_NULL:
+    case JSON_FALSE:
+    case JSON_TRUE:
+        return json_create(json->type);
+
+    case JSON_INTEGER:
+        return json_integer_create(json->u.integer);
+
+    case JSON_REAL:
+        return json_real_create(json->u.real);
+
+    case JSON_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+static struct json *
+json_clone_object(const struct shash *object)
+{
+    struct shash_node *node;
+    struct json *json;
+
+    json = json_object_create();
+    SHASH_FOR_EACH (node, object) {
+        struct json *value = node->data;
+        json_object_put(json, node->name, json_clone(value));
+    }
+    return json;
+}
+
+static struct json *
+json_clone_array(const struct json_array *array)
+{
+    struct json **elems;
+    size_t i;
+
+    elems = xmalloc(array->n * sizeof *elems);
+    for (i = 0; i < array->n; i++) {
+        elems[i] = json_clone(array->elems[i]);
+    }
+    return json_array_create(elems, array->n);
+}
+\f
+static size_t
+json_hash_object(const struct shash *object, size_t basis)
+{
+    const struct shash_node **nodes;
+    size_t n, i;
+
+    nodes = shash_sort(object);
+    n = shash_count(object);
+    for (i = 0; i < n; i++) {
+        const struct shash_node *node = nodes[i];
+        basis = hash_string(node->name, basis);
+        basis = json_hash(node->data, basis);
+    }
+    return basis;
+}
+
+static size_t
+json_hash_array(const struct json_array *array, size_t basis)
+{
+    size_t i;
+
+    basis = hash_int(array->n, basis);
+    for (i = 0; i < array->n; i++) {
+        basis = json_hash(array->elems[i], basis);
+    }
+    return basis;
+}
+
+size_t
+json_hash(const struct json *json, size_t basis)
+{
+    switch (json->type) {
+    case JSON_OBJECT:
+        return json_hash_object(json->u.object, basis);
+
+    case JSON_ARRAY:
+        return json_hash_array(&json->u.array, basis);
+
+    case JSON_STRING:
+        return hash_string(json->u.string, basis);
+
+    case JSON_NULL:
+    case JSON_FALSE:
+    case JSON_TRUE:
+        return hash_int(json->type << 8, basis);
+
+    case JSON_INTEGER:
+        return hash_int(json->u.integer, basis);
+
+    case JSON_REAL:
+        return hash_double(json->u.real, basis);
+
+    case JSON_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+static bool
+json_equal_object(const struct shash *a, const struct shash *b)
+{
+    struct shash_node *a_node;
+
+    if (shash_count(a) != shash_count(b)) {
+        return false;
+    }
+
+    SHASH_FOR_EACH (a_node, a) {
+        struct shash_node *b_node = shash_find(b, a_node->name);
+        if (!b_node || !json_equal(a_node->data, b_node->data)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+static bool
+json_equal_array(const struct json_array *a, const struct json_array *b)
+{
+    size_t i;
+
+    if (a->n != b->n) {
+        return false;
+    }
+
+    for (i = 0; i < a->n; i++) {
+        if (!json_equal(a->elems[i], b->elems[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool
+json_equal(const struct json *a, const struct json *b)
+{
+    if (a->type != b->type) {
+        return false;
+    }
+
+    switch (a->type) {
+    case JSON_OBJECT:
+        return json_equal_object(a->u.object, b->u.object);
+
+    case JSON_ARRAY:
+        return json_equal_array(&a->u.array, &b->u.array);
+
+    case JSON_STRING:
+        return !strcmp(a->u.string, b->u.string);
+
+    case JSON_NULL:
+    case JSON_FALSE:
+    case JSON_TRUE:
+        return true;
+
+    case JSON_INTEGER:
+        return a->u.integer == b->u.integer;
+
+    case JSON_REAL:
+        return a->u.real == b->u.real;
+
+    case JSON_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+\f
+/* Lexical analysis. */
+
+static void
+json_lex_keyword(struct json_parser *p)
+{
+    struct json_token token;
+    const char *s;
+
+    s = ds_cstr(&p->buffer);
+    if (!strcmp(s, "false")) {
+        token.type = T_FALSE;
+    } else if (!strcmp(s, "true")) {
+        token.type = T_TRUE;
+    } else if (!strcmp(s, "null")) {
+        token.type = T_NULL;
+    } else {
+        json_error(p, "invalid keyword '%s'", s);
+        return;
+    }
+    json_parser_input(p, &token);
+}
+
+static void
+json_lex_number(struct json_parser *p)
+{
+    const char *cp = ds_cstr(&p->buffer);
+    unsigned long long int significand = 0;
+    int sig_digits = 0;
+    bool imprecise = false;
+    bool negative = false;
+    int pow10 = 0;
+
+    /* Leading minus sign. */
+    if (*cp == '-') {
+        negative = true;
+        cp++;
+    }
+
+    /* At least one integer digit, but 0 may not be used as a leading digit for
+     * a longer number. */
+    significand = 0;
+    sig_digits = 0;
+    if (*cp == '0') {
+        cp++;
+        if (isdigit(*cp)) {
+            json_error(p, "leading zeros not allowed");
+            return;
+        }
+    } else if (isdigit(*cp)) {
+        do {
+            if (significand <= ULLONG_MAX / 10) {
+                significand = significand * 10 + (*cp - '0');
+                sig_digits++;
+            } else {
+                pow10++;
+                if (*cp != '0') {
+                    imprecise = true;
+                }
+            }
+            cp++;
+        } while (isdigit(*cp));
+    } else {
+        json_error(p, "'-' must be followed by digit");
+        return;
+    }
+
+    /* Optional fraction. */
+    if (*cp == '.') {
+        cp++;
+        if (!isdigit(*cp)) {
+            json_error(p, "decimal point must be followed by digit");
+            return;
+        }
+        do {
+            if (significand <= ULLONG_MAX / 10) {
+                significand = significand * 10 + (*cp - '0');
+                sig_digits++;
+                pow10--;
+            } else if (*cp != '0') {
+                imprecise = true;
+            }
+            cp++;
+        } while (isdigit(*cp));
+    }
+
+    /* Optional exponent. */
+    if (*cp == 'e' || *cp == 'E') {
+        bool negative_exponent = false;
+        int exponent;
+
+        cp++;
+        if (*cp == '+') {
+            cp++;
+        } else if (*cp == '-') {
+            negative_exponent = true;
+            cp++;
+        }
+
+        if (!isdigit(*cp)) {
+            json_error(p, "exponent must contain at least one digit");
+            return;
+        }
+
+        exponent = 0;
+        do {
+            if (exponent >= INT_MAX / 10) {
+                json_error(p, "exponent outside valid range");
+                return;
+            }
+            exponent = exponent * 10 + (*cp - '0');
+            cp++;
+        } while (isdigit(*cp));
+
+        if (negative_exponent) {
+            pow10 -= exponent;
+        } else {
+            pow10 += exponent;
+        }
+    }
+
+    if (*cp != '\0') {
+        json_error(p, "syntax error in number");
+        return;
+    }
+
+    /* Figure out number.
+     *
+     * We suppress negative zeros as a matter of policy. */
+    if (!significand) {
+        struct json_token token;
+        token.type = T_INTEGER;
+        token.u.integer = 0;
+        json_parser_input(p, &token);
+        return;
+    }
+
+    if (!imprecise) {
+        while (pow10 > 0 && significand < ULLONG_MAX / 10) {
+            significand *= 10;
+            sig_digits++;
+            pow10--;
+        }
+        while (pow10 < 0 && significand % 10 == 0) {
+            significand /= 10;
+            sig_digits--;
+            pow10++;
+        }
+        if (pow10 == 0
+            && significand <= (negative
+                               ? (unsigned long long int) LLONG_MAX + 1
+                               : LLONG_MAX)) {
+            struct json_token token;
+            token.type = T_INTEGER;
+            token.u.integer = negative ? -significand : significand;
+            json_parser_input(p, &token);
+            return;
+        }
+    }
+
+    if (pow10 + sig_digits <= DBL_MAX_10_EXP) {
+        struct json_token token;
+        token.type = T_REAL;
+        token.u.real = significand * pow(10.0, pow10);
+        if (token.u.real <= DBL_MAX) {
+            if (negative && token.u.real) {
+                token.u.real = -token.u.real;
+            }
+            json_parser_input(p, &token);
+            return;
+        }
+    }
+    json_error(p, "number outside valid range");
+}
+
+static bool
+json_lex_4hex(struct json_parser *p, const char *cp, int *valuep)
+{
+    int value, i;
+
+    value = 0;
+    for (i = 0; i < 4; i++) {
+        unsigned char c = *cp++;
+        if (!isxdigit(c)) {
+            json_error(p, "malformed \\u escape");
+            return false;
+        }
+        value = (value << 4) | hexit_value(c);
+    }
+    if (!value) {
+        json_error(p, "null bytes not supported in quoted strings");
+        return false;
+    }
+    *valuep = value;
+    return true;
+}
+
+static const char *
+json_lex_unicode(struct json_parser *p, const char *cp, struct ds *s)
+{
+    int c0, c1;
+
+    if (!json_lex_4hex(p, cp, &c0)) {
+        return NULL;
+    }
+    cp += 4;
+    if (!uc_is_leading_surrogate(c0)) {
+        ds_put_utf8(s, c0);
+        return cp;
+    }
+
+    if (*cp++ != '\\' || *cp++ != 'u') {
+        json_error(p, "malformed escaped surrogate pair");
+        return NULL;
+    }
+
+    if (!json_lex_4hex(p, cp, &c1)) {
+        return NULL;
+    }
+    cp += 4;
+    if (!uc_is_trailing_surrogate(c1)) {
+        json_error(p, "second half of escaped surrogate pair is not "
+                   "trailing surrogate");
+        return NULL;
+    }
+
+    ds_put_utf8(s, utf16_decode_surrogate_pair(c0, c1));
+    return cp;
+}
+
+static void
+json_lex_string(struct json_parser *p)
+{
+    struct json_token token;
+    const char *cp;
+    struct ds s;
+
+    cp = ds_cstr(&p->buffer);
+    if (!strchr(cp, '\\')) {
+        token.type = T_STRING;
+        token.u.string = cp;
+        json_parser_input(p, &token);
+        return;
+    }
+
+    ds_init(&s);
+    ds_reserve(&s, strlen(cp));
+    while (*cp != '\0') {
+        if (*cp != '\\') {
+            ds_put_char(&s, *cp++);
+            continue;
+        }
+
+        cp++;
+        switch (*cp++) {
+        case '"': case '\\': case '/':
+            ds_put_char(&s, cp[-1]);
+            break;
+
+        case 'b':
+            ds_put_char(&s, '\b');
+            break;
+
+        case 'f':
+            ds_put_char(&s, '\f');
+            break;
+
+        case 'n':
+            ds_put_char(&s, '\n');
+            break;
+
+        case 'r':
+            ds_put_char(&s, '\r');
+            break;
+
+        case 't':
+            ds_put_char(&s, '\t');
+            break;
+
+        case 'u':
+            cp = json_lex_unicode(p, cp, &s);
+            if (!cp) {
+                goto exit;
+            }
+            break;
+
+        default:
+            json_error(p, "bad escape \\%c", cp[-1]);
+            goto exit;
+        }
+    }
+
+    token.type = T_STRING;
+    token.u.string = ds_cstr(&s);
+    json_parser_input(p, &token);
+
+exit:
+    ds_destroy(&s);
+    return;
+}
+
+static bool
+json_lex_input(struct json_parser *p, int c)
+{
+    struct json_token token;
+
+    switch (p->lex_state) {
+    case JSON_LEX_START:
+        switch (c) {
+        case ' ': case '\t': case '\n': case '\r':
+            /* Nothing to do. */
+            return true;
+
+        case 'a': case 'b': case 'c': case 'd': case 'e':
+        case 'f': case 'g': case 'h': case 'i': case 'j':
+        case 'k': case 'l': case 'm': case 'n': case 'o':
+        case 'p': case 'q': case 'r': case 's': case 't':
+        case 'u': case 'v': case 'w': case 'x': case 'y':
+        case 'z':
+            p->lex_state = JSON_LEX_KEYWORD;
+            break;
+
+        case '[': case '{': case ']': case '}': case ':': case ',':
+            token.type = c;
+            json_parser_input(p, &token);
+            return true;
+
+        case '-':
+        case '0': case '1': case '2': case '3': case '4':
+        case '5': case '6': case '7': case '8': case '9':
+            p->lex_state = JSON_LEX_NUMBER;
+            break;
+
+        case '"':
+            p->lex_state = JSON_LEX_STRING;
+            return true;
+
+        default:
+            if (isprint(c)) {
+                json_error(p, "invalid character '%c'", c);
+            } else {
+                json_error(p, "invalid character U+%04x", c);
+            }
+            return true;
+        }
+        break;
+
+    case JSON_LEX_KEYWORD:
+        if (!isalpha((unsigned char) c)) {
+            json_lex_keyword(p);
+            return false;
+        }
+        break;
+
+    case JSON_LEX_NUMBER:
+        if (!strchr(".0123456789eE-+", c)) {
+            json_lex_number(p);
+            return false;
+        }
+        break;
+
+    case JSON_LEX_STRING:
+        if (c == '\\') {
+            p->lex_state = JSON_LEX_ESCAPE;
+        } else if (c == '"') {
+            json_lex_string(p);
+            return true;
+        } else if (c < 0x20) {
+            json_error(p, "U+%04X must be escaped in quoted string", c);
+            return true;
+        }
+        break;
+
+    case JSON_LEX_ESCAPE:
+        p->lex_state = JSON_LEX_STRING;
+        break;
+
+    default:
+        abort();
+    }
+    ds_put_char(&p->buffer, c);
+    return true;
+}
+\f
+/* Parsing. */
+
+/* Parses 'string' as a JSON object or array and returns a newly allocated
+ * 'struct json'.  The caller must free the returned structure with
+ * json_destroy() when it is no longer needed.
+ *
+ * 'string' must be encoded in UTF-8.
+ *
+ * If 'string' is valid JSON, then the returned 'struct json' will be either an
+ * object (JSON_OBJECT) or an array (JSON_ARRAY).
+ *
+ * If 'string' is not valid JSON, then the returned 'struct json' will be a
+ * string (JSON_STRING) that describes the particular error encountered during
+ * parsing.  (This is an acceptable means of error reporting because at its top
+ * level JSON must be either an object or an array; a bare string is not
+ * valid.) */
+struct json *
+json_from_string(const char *string)
+{
+    struct json_parser *p = json_parser_create(JSPF_TRAILER);
+    json_parser_feed(p, string, strlen(string));
+    return json_parser_finish(p);
+}
+
+/* Reads the file named 'file_name', parses its contents as a JSON object or
+ * array, and returns a newly allocated 'struct json'.  The caller must free
+ * the returned structure with json_destroy() when it is no longer needed.
+ *
+ * The file must be encoded in UTF-8.
+ *
+ * See json_from_string() for return value semantics.
+ */
+struct json *
+json_from_file(const char *file_name)
+{
+    struct json_parser *p;
+    struct json *json;
+    FILE *stream;
+
+    /* Open file. */
+    stream = fopen(file_name, "r");
+    if (!stream) {
+        return json_string_create_nocopy(
+            xasprintf("error opening \"%s\": %s", file_name, strerror(errno)));
+    }
+
+    /* Read and parse file. */
+    p = json_parser_create(JSPF_TRAILER);
+    for (;;) {
+        char buffer[BUFSIZ];
+        size_t n;
+
+        n = fread(buffer, 1, sizeof buffer, stream);
+        if (!n || json_parser_feed(p, buffer, n) != n) {
+            break;
+        }
+    }
+    json = json_parser_finish(p);
+
+    /* Close file and check for I/O errors. */
+    if (ferror(stream)) {
+        json_destroy(json);
+        json = json_string_create_nocopy(
+            xasprintf("error reading \"%s\": %s", file_name, strerror(errno)));
+    }
+    fclose(stream);
+
+    return json;
+}
+
+struct json_parser *
+json_parser_create(int flags)
+{
+    struct json_parser *p = xzalloc(sizeof *p);
+    p->flags = flags;
+    return p;
+}
+
+size_t
+json_parser_feed(struct json_parser *p, const char *input, size_t n)
+{
+    size_t i;
+    for (i = 0; !p->done && i < n; ) {
+        if (json_lex_input(p, input[i])) {
+            i++;
+        }
+    }
+    return i;
+}
+
+bool
+json_parser_is_done(const struct json_parser *p)
+{
+    return p->done;
+}
+
+struct json *
+json_parser_finish(struct json_parser *p)
+{
+    struct json *json;
+
+    switch (p->lex_state) {
+    case JSON_LEX_START:
+        break;
+
+    case JSON_LEX_STRING:
+    case JSON_LEX_ESCAPE:
+        json_error(p, "unexpected end of input in quoted string");
+        break;
+
+    case JSON_LEX_NUMBER:
+    case JSON_LEX_KEYWORD:
+        json_lex_input(p, ' ');
+        break;
+    }
+
+    if (p->parse_state == JSON_PARSE_START) {
+        json_error(p, "empty input stream");
+    } else if (p->parse_state != JSON_PARSE_END) {
+        json_error(p, "unexpected end of input");
+    }
+
+    if (!p->error) {
+        assert(p->height == 1);
+        assert(p->stack[0].json != NULL);
+        json = p->stack[--p->height].json;
+    } else {
+        json = json_string_create_nocopy(p->error);
+        p->error = NULL;
+    }
+
+    json_parser_abort(p);
+
+    return json;
+}
+
+void
+json_parser_abort(struct json_parser *p)
+{
+    if (p) {
+        ds_destroy(&p->buffer);
+        if (p->height) {
+            json_destroy(p->stack[0].json);
+        }
+        free(p->stack);
+        free(p->member_name);
+        free(p->error);
+        free(p);
+    }
+}
+
+static struct json_parser_node *
+json_parser_top(struct json_parser *p)
+{
+    return &p->stack[p->height - 1];
+}
+
+static void
+json_parser_put_value(struct json_parser *p, struct json *value)
+{
+    struct json_parser_node *node = json_parser_top(p);
+    if (node->json->type == JSON_OBJECT) {
+        json_object_put(node->json, p->member_name, value);
+        free(p->member_name);
+        p->member_name = NULL;
+    } else if (node->json->type == JSON_ARRAY) {
+        json_array_add(node->json, value);
+    } else {
+        NOT_REACHED();
+    }
+}
+
+static struct json_parser_node *
+json_parser_push(struct json_parser *p,
+                 struct json *new_json, enum json_parse_state new_state)
+{
+    if (p->height < JSON_MAX_HEIGHT) {
+        struct json_parser_node *node;
+
+        if (p->height >= p->allocated_height) {
+            p->stack = x2nrealloc(p->stack, &p->allocated_height,
+                                  sizeof *p->stack);
+        }
+
+        if (p->height > 0) {
+            json_parser_put_value(p, new_json);
+        }
+
+        node = &p->stack[p->height++];
+        node->json = new_json;
+        p->parse_state = new_state;
+        return node;
+    } else {
+        json_error(p, "input exceeds maximum nesting depth %d",
+                   JSON_MAX_HEIGHT);
+        return NULL;
+    }
+}
+
+static void
+json_parser_push_object(struct json_parser *p)
+{
+    json_parser_push(p, json_object_create(), JSON_PARSE_OBJECT_INIT);
+}
+
+static void
+json_parser_push_array(struct json_parser *p)
+{
+    json_parser_push(p, json_array_create_empty(), JSON_PARSE_ARRAY_INIT);
+}
+
+static void
+json_parse_value(struct json_parser *p, struct json_token *token,
+                 enum json_parse_state next_state)
+{
+    struct json *value;
+
+    switch (token->type) {
+    case T_FALSE:
+        value = json_boolean_create(false);
+        break;
+
+    case T_NULL:
+        value = json_null_create();
+        break;
+
+    case T_TRUE:
+        value = json_boolean_create(true);
+        break;
+
+    case '{':
+        json_parser_push_object(p);
+        return;
+
+    case '[':
+        json_parser_push_array(p);
+        return;
+
+    case T_INTEGER:
+        value = json_integer_create(token->u.integer);
+        break;
+
+    case T_REAL:
+        value = json_real_create(token->u.real);
+        break;
+
+    case T_STRING:
+        value = json_string_create(token->u.string);
+        break;
+
+    case T_EOF:
+    case '}':
+    case ']':
+    case ':':
+    case ',':
+    default:
+        json_error(p, "syntax error expecting value");
+        return;
+    }
+
+    json_parser_put_value(p, value);
+    p->parse_state = next_state;
+}
+
+static void
+json_parser_pop(struct json_parser *p)
+{
+    struct json_parser_node *node;
+
+    /* Conserve memory. */
+    node = json_parser_top(p);
+    if (node->json->type == JSON_ARRAY) {
+        json_array_trim(node->json);
+    }
+
+    /* Pop off the top-of-stack. */
+    if (p->height == 1) {
+        p->parse_state = JSON_PARSE_END;
+        if (!(p->flags & JSPF_TRAILER)) {
+            p->done = true;
+        }
+    } else {
+        p->height--;
+        node = json_parser_top(p);
+        if (node->json->type == JSON_ARRAY) {
+            p->parse_state = JSON_PARSE_ARRAY_NEXT;
+        } else if (node->json->type == JSON_OBJECT) {
+            p->parse_state = JSON_PARSE_OBJECT_NEXT;
+        } else {
+            NOT_REACHED();
+        }
+    }
+}
+
+static void
+json_parser_input(struct json_parser *p, struct json_token *token)
+{
+    switch (p->parse_state) {
+    case JSON_PARSE_START:
+        if (token->type == '{') {
+            json_parser_push_object(p);
+        } else if (token->type == '[') {
+            json_parser_push_array(p);
+        } else {
+            json_error(p, "syntax error at beginning of input");
+        }
+        break;
+
+    case JSON_PARSE_END:
+        json_error(p, "trailing garbage at end of input");
+        break;
+
+    case JSON_PARSE_OBJECT_INIT:
+        if (token->type == '}') {
+            json_parser_pop(p);
+            break;
+        }
+        /* Fall through. */
+    case JSON_PARSE_OBJECT_NAME:
+        if (token->type == T_STRING) {
+            p->member_name = xstrdup(token->u.string);
+            p->parse_state = JSON_PARSE_OBJECT_COLON;
+        } else {
+            json_error(p, "syntax error parsing object expecting string");
+        }
+        break;
+
+    case JSON_PARSE_OBJECT_COLON:
+        if (token->type == ':') {
+            p->parse_state = JSON_PARSE_OBJECT_VALUE;
+        } else {
+            json_error(p, "syntax error parsing object expecting ':'");
+        }
+        break;
+
+    case JSON_PARSE_OBJECT_VALUE:
+        json_parse_value(p, token, JSON_PARSE_OBJECT_NEXT);
+        break;
+
+    case JSON_PARSE_OBJECT_NEXT:
+        if (token->type == ',') {
+            p->parse_state = JSON_PARSE_OBJECT_NAME;
+        } else if (token->type == '}') {
+            json_parser_pop(p);
+        } else {
+            json_error(p, "syntax error expecting '}' or ','");
+        }
+        break;
+
+    case JSON_PARSE_ARRAY_INIT:
+        if (token->type == ']') {
+            json_parser_pop(p);
+            break;
+        }
+        /* Fall through. */
+    case JSON_PARSE_ARRAY_VALUE:
+        json_parse_value(p, token, JSON_PARSE_ARRAY_NEXT);
+        break;
+
+    case JSON_PARSE_ARRAY_NEXT:
+        if (token->type == ',') {
+            p->parse_state = JSON_PARSE_ARRAY_VALUE;
+        } else if (token->type == ']') {
+            json_parser_pop(p);
+        } else {
+            json_error(p, "syntax error expecting ']' or ','");
+        }
+        break;
+
+    default:
+        abort();
+    }
+
+    p->lex_state = JSON_LEX_START;
+    ds_clear(&p->buffer);
+}
+
+static struct json *
+json_create(enum json_type type)
+{
+    struct json *json = xmalloc(sizeof *json);
+    json->type = type;
+    return json;
+}
+
+static void
+json_error(struct json_parser *p, const char *format, ...)
+{
+    if (!p->error) {
+        va_list args;
+
+        va_start(args, format);
+        p->error = xvasprintf(format, args);
+        va_end(args);
+
+        p->done = true;
+    }
+}
+\f
+#define SPACES_PER_LEVEL 2
+
+struct json_serializer {
+    struct ds ds;
+    int depth;
+    int flags;
+};
+
+static void json_to_ds(const struct json *, struct json_serializer *);
+static void json_object_to_ds(const struct shash *object,
+                              struct json_serializer *);
+static void json_array_to_ds(const struct json_array *,
+                             struct json_serializer *);
+static void json_string_to_ds(const char *string, struct ds *);
+
+/* Converts 'json' to a string in JSON format, encoded in UTF-8, and returns
+ * that string.  The caller is responsible for freeing the returned string,
+ * with free(), when it is no longer needed.
+ *
+ * If 'flags' contains JSSF_PRETTY, the output is pretty-printed with each
+ * nesting level introducing an additional indentation.  Otherwise, the
+ * returned string does not contain any new-line characters.
+ *
+ * If 'flags' contains JSSF_SORT, members of objects in the output are sorted
+ * in bytewise lexicographic order for reproducibility.  Otherwise, members of
+ * objects are output in an indeterminate order.
+ *
+ * The returned string is valid JSON only if 'json' represents an array or an
+ * object, since a bare literal does not satisfy the JSON grammar. */
+char *
+json_to_string(const struct json *json, int flags)
+{
+    struct json_serializer s;
+    ds_init(&s.ds);
+    s.depth = 0;
+    s.flags = flags;
+    json_to_ds(json, &s);
+    return ds_steal_cstr(&s.ds);
+}
+
+static void
+json_to_ds(const struct json *json, struct json_serializer *s)
+{
+    struct ds *ds = &s->ds;
+
+    switch (json->type) {
+    case JSON_NULL:
+        ds_put_cstr(ds, "null");
+        break;
+
+    case JSON_FALSE:
+        ds_put_cstr(ds, "false");
+        break;
+
+    case JSON_TRUE:
+        ds_put_cstr(ds, "true");
+        break;
+
+    case JSON_OBJECT:
+        json_object_to_ds(json->u.object, s);
+        break;
+
+    case JSON_ARRAY:
+        json_array_to_ds(&json->u.array, s);
+        break;
+
+    case JSON_INTEGER:
+        ds_put_format(ds, "%lld", json->u.integer);
+        break;
+
+    case JSON_REAL:
+        ds_put_format(ds, "%.*g", DBL_DIG, json->u.real);
+        break;
+
+    case JSON_STRING:
+        json_string_to_ds(json->u.string, ds);
+        break;
+
+    case JSON_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+static void
+indent_line(struct json_serializer *s)
+{
+    if (s->flags & JSSF_PRETTY) {
+        ds_put_char(&s->ds, '\n');
+        ds_put_char_multiple(&s->ds, ' ', SPACES_PER_LEVEL * s->depth);
+    }
+}
+
+static void
+json_object_member_to_ds(size_t i, const struct shash_node *node,
+                         struct json_serializer *s)
+{
+    struct ds *ds = &s->ds;
+
+    if (i) {
+        ds_put_char(ds, ',');
+        indent_line(s);
+    }
+
+    json_string_to_ds(node->name, ds);
+    ds_put_char(ds, ':');
+    if (s->flags & JSSF_PRETTY) {
+        ds_put_char(ds, ' ');
+    }
+    json_to_ds(node->data, s);
+}
+
+static void
+json_object_to_ds(const struct shash *object, struct json_serializer *s)
+{
+    struct ds *ds = &s->ds;
+
+    ds_put_char(ds, '{');
+
+    s->depth++;
+    indent_line(s);
+
+    if (s->flags & JSSF_SORT) {
+        const struct shash_node **nodes;
+        size_t n, i;
+
+        nodes = shash_sort(object);
+        n = shash_count(object);
+        for (i = 0; i < n; i++) {
+            json_object_member_to_ds(i, nodes[i], s);
+        }
+        free(nodes);
+    } else {
+        struct shash_node *node;
+        size_t i;
+
+        i = 0;
+        SHASH_FOR_EACH (node, object) {
+            json_object_member_to_ds(i++, node, s);
+        }
+    }
+
+    ds_put_char(ds, '}');
+    s->depth--;
+}
+
+static void
+json_array_to_ds(const struct json_array *array, struct json_serializer *s)
+{
+    struct ds *ds = &s->ds;
+    size_t i;
+
+    ds_put_char(ds, '[');
+    s->depth++;
+
+    if (array->n > 0) {
+        indent_line(s);
+
+        for (i = 0; i < array->n; i++) {
+            if (i) {
+                ds_put_char(ds, ',');
+                indent_line(s);
+            }
+            json_to_ds(array->elems[i], s);
+        }
+    }
+
+    s->depth--;
+    ds_put_char(ds, ']');
+}
+
+static void
+json_string_to_ds(const char *string, struct ds *ds)
+{
+    uint8_t c;
+
+    ds_put_char(ds, '"');
+    while ((c = *string++) != '\0') {
+        switch (c) {
+        case '"':
+            ds_put_cstr(ds, "\\\"");
+            break;
+
+        case '\\':
+            ds_put_cstr(ds, "\\\\");
+            break;
+
+        case '\b':
+            ds_put_cstr(ds, "\\b");
+            break;
+
+        case '\f':
+            ds_put_cstr(ds, "\\f");
+            break;
+
+        case '\n':
+            ds_put_cstr(ds, "\\n");
+            break;
+
+        case '\r':
+            ds_put_cstr(ds, "\\r");
+            break;
+
+        case '\t':
+            ds_put_cstr(ds, "\\t");
+            break;
+
+        default:
+            if (c >= 32) {
+                ds_put_char(ds, c);
+            } else {
+                ds_put_format(ds, "\\u%04x", c);
+            }
+            break;
+        }
+    }
+    ds_put_char(ds, '"');
+}
diff --git a/lib/json.h b/lib/json.h
new file mode 100644 (file)
index 0000000..7674015
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef JSON_H
+#define JSON_H 1
+
+/* This is an implementation of JavaScript Object Notation (JSON) as specified
+ * by RFC 4627.  It is intended to fully comply with RFC 4627, with the
+ * following known exceptions and clarifications:
+ *
+ *      - Null bytes (\u0000) are not allowed in strings.
+ *
+ *      - Only UTF-8 encoding is supported (RFC 4627 allows for other Unicode
+ *        encodings).
+ *
+ *      - Names within an object must be unique (RFC 4627 says that they
+ *        "should" be unique).
+ */
+
+#include "shash.h"
+
+/* Type of a JSON value. */
+enum json_type {
+    JSON_NULL,                  /* null */
+    JSON_FALSE,                 /* false */
+    JSON_TRUE,                  /* true */
+    JSON_OBJECT,                /* {"a": b, "c": d, ...} */
+    JSON_ARRAY,                 /* [1, 2, 3, ...] */
+    JSON_INTEGER,               /* 123. */
+    JSON_REAL,                  /* 123.456. */
+    JSON_STRING,                /* "..." */
+    JSON_N_TYPES
+};
+
+const char *json_type_to_string(enum json_type);
+
+/* A JSON array. */
+struct json_array {
+    size_t n, n_allocated;
+    struct json **elems;
+};
+
+/* A JSON value. */
+struct json {
+    enum json_type type;
+    union {
+        struct shash *object;   /* Contains "struct json *"s. */
+        struct json_array array;
+        long long int integer;
+        double real;
+        char *string;
+    } u;
+};
+
+struct json *json_null_create(void);
+struct json *json_boolean_create(bool);
+struct json *json_string_create(const char *);
+struct json *json_string_create_nocopy(char *);
+struct json *json_integer_create(long long int);
+struct json *json_real_create(double);
+
+struct json *json_array_create_empty(void);
+void json_array_add(struct json *, struct json *element);
+void json_array_trim(struct json *);
+struct json *json_array_create(struct json **, size_t n);
+struct json *json_array_create_2(struct json *, struct json *);
+struct json *json_array_create_3(struct json *, struct json *, struct json *);
+
+struct json *json_object_create(void);
+void json_object_put(struct json *, const char *name, struct json *value);
+void json_object_put_string(struct json *,
+                            const char *name, const char *value);
+
+const char *json_string(const struct json *);
+struct json_array *json_array(const struct json *);
+struct shash *json_object(const struct json *);
+bool json_boolean(const struct json *);
+double json_real(const struct json *);
+int64_t json_integer(const struct json *);
+
+struct json *json_clone(const struct json *);
+void json_destroy(struct json *);
+
+size_t json_hash(const struct json *, size_t basis);
+bool json_equal(const struct json *, const struct json *);
+\f
+/* Parsing JSON. */
+enum {
+    JSPF_TRAILER = 1 << 0       /* Check for garbage following input.  */
+};
+
+struct json_parser *json_parser_create(int flags);
+size_t json_parser_feed(struct json_parser *, const char *, size_t);
+bool json_parser_is_done(const struct json_parser *);
+struct json *json_parser_finish(struct json_parser *);
+void json_parser_abort(struct json_parser *);
+
+struct json *json_from_string(const char *string);
+struct json *json_from_file(const char *file_name);
+\f
+/* Serializing JSON. */
+
+enum {
+    JSSF_PRETTY = 1 << 0,       /* Multiple lines with indentation, if true. */
+    JSSF_SORT = 1 << 1          /* Object members in sorted order, if true. */
+};
+char *json_to_string(const struct json *, int flags);
+
+#endif /* json.h */
diff --git a/lib/jsonrpc.c b/lib/jsonrpc.c
new file mode 100644 (file)
index 0000000..bd019f7
--- /dev/null
@@ -0,0 +1,804 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "jsonrpc.h"
+
+#include <errno.h>
+
+#include "byteq.h"
+#include "dynamic-string.h"
+#include "json.h"
+#include "list.h"
+#include "ofpbuf.h"
+#include "poll-loop.h"
+#include "queue.h"
+#include "reconnect.h"
+#include "stream.h"
+#include "timeval.h"
+
+#define THIS_MODULE VLM_jsonrpc
+#include "vlog.h"
+\f
+struct jsonrpc {
+    struct stream *stream;
+    char *name;
+    int status;
+
+    /* Input. */
+    struct byteq input;
+    struct json_parser *parser;
+    struct jsonrpc_msg *received;
+
+    /* Output. */
+    struct ovs_queue output;
+    size_t backlog;
+};
+
+/* Rate limit for error messages. */
+static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
+
+static void jsonrpc_received(struct jsonrpc *);
+static void jsonrpc_cleanup(struct jsonrpc *);
+
+struct jsonrpc *
+jsonrpc_open(struct stream *stream)
+{
+    struct jsonrpc *rpc;
+
+    assert(stream != NULL);
+
+    rpc = xzalloc(sizeof *rpc);
+    rpc->name = xstrdup(stream_get_name(stream));
+    rpc->stream = stream;
+    byteq_init(&rpc->input);
+    queue_init(&rpc->output);
+
+    return rpc;
+}
+
+void
+jsonrpc_close(struct jsonrpc *rpc)
+{
+    if (rpc) {
+        jsonrpc_cleanup(rpc);
+        free(rpc->name);
+        free(rpc);
+    }
+}
+
+void
+jsonrpc_run(struct jsonrpc *rpc)
+{
+    if (rpc->status) {
+        return;
+    }
+
+    while (!queue_is_empty(&rpc->output)) {
+        struct ofpbuf *buf = rpc->output.head;
+        int retval;
+
+        retval = stream_send(rpc->stream, buf->data, buf->size);
+        if (retval >= 0) {
+            rpc->backlog -= retval;
+            ofpbuf_pull(buf, retval);
+            if (!buf->size) {
+                ofpbuf_delete(queue_pop_head(&rpc->output));
+            }
+        } else {
+            if (retval != -EAGAIN) {
+                VLOG_WARN_RL(&rl, "%s: send error: %s",
+                             rpc->name, strerror(-retval));
+                jsonrpc_error(rpc, -retval);
+            }
+            break;
+        }
+    }
+}
+
+void
+jsonrpc_wait(struct jsonrpc *rpc)
+{
+    if (!rpc->status && !queue_is_empty(&rpc->output)) {
+        stream_send_wait(rpc->stream);
+    }
+}
+
+int
+jsonrpc_get_status(const struct jsonrpc *rpc)
+{
+    return rpc->status;
+}
+
+size_t
+jsonrpc_get_backlog(const struct jsonrpc *rpc)
+{
+    return rpc->status ? 0 : rpc->backlog;
+}
+
+const char *
+jsonrpc_get_name(const struct jsonrpc *rpc)
+{
+    return rpc->name;
+}
+
+static void
+jsonrpc_log_msg(const struct jsonrpc *rpc, const char *title,
+                const struct jsonrpc_msg *msg)
+{
+    if (VLOG_IS_DBG_ENABLED()) {
+        struct ds s = DS_EMPTY_INITIALIZER;
+        if (msg->method) {
+            ds_put_format(&s, ", method=\"%s\"", msg->method);
+        }
+        if (msg->params) {
+            ds_put_cstr(&s, ", params=");
+            ds_put_and_free_cstr(&s, json_to_string(msg->params, 0));
+        }
+        if (msg->result) {
+            ds_put_cstr(&s, ", result=");
+            ds_put_and_free_cstr(&s, json_to_string(msg->result, 0));
+        }
+        if (msg->error) {
+            ds_put_cstr(&s, ", error=");
+            ds_put_and_free_cstr(&s, json_to_string(msg->error, 0));
+        }
+        if (msg->id) {
+            ds_put_cstr(&s, ", id=");
+            ds_put_and_free_cstr(&s, json_to_string(msg->id, 0));
+        }
+        VLOG_DBG("%s: %s %s%s", rpc->name, title,
+                 jsonrpc_msg_type_to_string(msg->type), ds_cstr(&s));
+        ds_destroy(&s);
+    }
+}
+
+int
+jsonrpc_send(struct jsonrpc *rpc, struct jsonrpc_msg *msg)
+{
+    struct ofpbuf *buf;
+    struct json *json;
+    size_t length;
+    char *s;
+
+    if (rpc->status) {
+        jsonrpc_msg_destroy(msg);
+        return rpc->status;
+    }
+
+    jsonrpc_log_msg(rpc, "send", msg);
+
+    json = jsonrpc_msg_to_json(msg);
+    s = json_to_string(json, 0);
+    length = strlen(s);
+    json_destroy(json);
+
+    buf = xmalloc(sizeof *buf);
+    ofpbuf_use(buf, s, length);
+    buf->size = length;
+    queue_push_tail(&rpc->output, buf);
+    rpc->backlog += length;
+
+    if (rpc->output.n == 1) {
+        jsonrpc_run(rpc);
+    }
+    return rpc->status;
+}
+
+int
+jsonrpc_recv(struct jsonrpc *rpc, struct jsonrpc_msg **msgp)
+{
+    *msgp = NULL;
+    if (rpc->status) {
+        return rpc->status;
+    }
+
+    while (!rpc->received) {
+        if (byteq_is_empty(&rpc->input)) {
+            size_t chunk;
+            int retval;
+
+            chunk = byteq_headroom(&rpc->input);
+            retval = stream_recv(rpc->stream, byteq_head(&rpc->input), chunk);
+            if (retval < 0) {
+                if (retval == -EAGAIN) {
+                    return EAGAIN;
+                } else {
+                    VLOG_WARN_RL(&rl, "%s: receive error: %s",
+                                 rpc->name, strerror(-retval));
+                    jsonrpc_error(rpc, -retval);
+                    return rpc->status;
+                }
+            } else if (retval == 0) {
+                VLOG_INFO_RL(&rl, "%s: connection closed", rpc->name);
+                jsonrpc_error(rpc, EOF);
+                return EOF;
+            }
+            byteq_advance_head(&rpc->input, retval);
+        } else {
+            size_t n, used;
+
+            if (!rpc->parser) {
+                rpc->parser = json_parser_create(0);
+            }
+            n = byteq_tailroom(&rpc->input);
+            used = json_parser_feed(rpc->parser,
+                                    (char *) byteq_tail(&rpc->input), n);
+            byteq_advance_tail(&rpc->input, used);
+            if (json_parser_is_done(rpc->parser)) {
+                jsonrpc_received(rpc);
+                if (rpc->status) {
+                    return rpc->status;
+                }
+            }
+        }
+    }
+
+    *msgp = rpc->received;
+    rpc->received = NULL;
+    return 0;
+}
+
+void
+jsonrpc_recv_wait(struct jsonrpc *rpc)
+{
+    if (rpc->status || rpc->received || !byteq_is_empty(&rpc->input)) {
+        poll_immediate_wake();
+    } else {
+        stream_recv_wait(rpc->stream);
+    }
+}
+
+int
+jsonrpc_send_block(struct jsonrpc *rpc, struct jsonrpc_msg *msg)
+{
+    int error;
+
+    error = jsonrpc_send(rpc, msg);
+    if (error) {
+        return error;
+    }
+
+    while (!queue_is_empty(&rpc->output) && !rpc->status) {
+        jsonrpc_run(rpc);
+        jsonrpc_wait(rpc);
+        poll_block();
+    }
+    return rpc->status;
+}
+
+int
+jsonrpc_recv_block(struct jsonrpc *rpc, struct jsonrpc_msg **msgp)
+{
+    for (;;) {
+        int error = jsonrpc_recv(rpc, msgp);
+        if (error != EAGAIN) {
+            return error;
+        }
+
+        jsonrpc_run(rpc);
+        jsonrpc_wait(rpc);
+        jsonrpc_recv_wait(rpc);
+        poll_block();
+    }
+}
+
+int
+jsonrpc_transact_block(struct jsonrpc *rpc, struct jsonrpc_msg *request,
+                       struct jsonrpc_msg **replyp)
+{
+    struct jsonrpc_msg *reply = NULL;
+    struct json *id;
+    int error;
+
+    id = json_clone(request->id);
+    error = jsonrpc_send_block(rpc, request);
+    if (!error) {
+        for (;;) {
+            error = jsonrpc_recv_block(rpc, &reply);
+            if (error
+                || (reply->type == JSONRPC_REPLY
+                    && json_equal(id, reply->id))) {
+                break;
+            }
+            jsonrpc_msg_destroy(reply);
+        }
+    }
+    *replyp = error ? NULL : reply;
+    json_destroy(id);
+    return error;
+}
+
+static void
+jsonrpc_received(struct jsonrpc *rpc)
+{
+    struct jsonrpc_msg *msg;
+    struct json *json;
+    char *error;
+
+    json = json_parser_finish(rpc->parser);
+    rpc->parser = NULL;
+    if (json->type == JSON_STRING) {
+        VLOG_WARN_RL(&rl, "%s: error parsing stream: %s",
+                     rpc->name, json_string(json));
+        jsonrpc_error(rpc, EPROTO);
+        json_destroy(json);
+        return;
+    }
+
+    error = jsonrpc_msg_from_json(json, &msg);
+    if (error) {
+        VLOG_WARN_RL(&rl, "%s: received bad JSON-RPC message: %s",
+                     rpc->name, error);
+        free(error);
+        jsonrpc_error(rpc, EPROTO);
+        return;
+    }
+
+    jsonrpc_log_msg(rpc, "received", msg);
+    rpc->received = msg;
+}
+
+void
+jsonrpc_error(struct jsonrpc *rpc, int error)
+{
+    assert(error);
+    if (!rpc->status) {
+        rpc->status = error;
+        jsonrpc_cleanup(rpc);
+    }
+}
+
+static void
+jsonrpc_cleanup(struct jsonrpc *rpc)
+{
+    stream_close(rpc->stream);
+    rpc->stream = NULL;
+
+    json_parser_abort(rpc->parser);
+    rpc->parser = NULL;
+
+    jsonrpc_msg_destroy(rpc->received);
+    rpc->received = NULL;
+
+    queue_clear(&rpc->output);
+    rpc->backlog = 0;
+}
+\f
+static struct jsonrpc_msg *
+jsonrpc_create(enum jsonrpc_msg_type type, const char *method,
+                struct json *params, struct json *result, struct json *error,
+                struct json *id)
+{
+    struct jsonrpc_msg *msg = xmalloc(sizeof *msg);
+    msg->type = type;
+    msg->method = method ? xstrdup(method) : NULL;
+    msg->params = params;
+    msg->result = result;
+    msg->error = error;
+    msg->id = id;
+    return msg;
+}
+
+static struct json *
+jsonrpc_create_id(void)
+{
+    static unsigned int id;
+    return json_integer_create(id++);
+}
+
+struct jsonrpc_msg *
+jsonrpc_create_request(const char *method, struct json *params,
+                       struct json **idp)
+{
+    struct json *id = jsonrpc_create_id();
+    if (idp) {
+        *idp = json_clone(id);
+    }
+    return jsonrpc_create(JSONRPC_REQUEST, method, params, NULL, NULL, id);
+}
+
+struct jsonrpc_msg *
+jsonrpc_create_notify(const char *method, struct json *params)
+{
+    return jsonrpc_create(JSONRPC_NOTIFY, method, params, NULL, NULL, NULL);
+}
+
+struct jsonrpc_msg *
+jsonrpc_create_reply(struct json *result, const struct json *id)
+{
+    return jsonrpc_create(JSONRPC_REPLY, NULL, NULL, result, NULL,
+                           json_clone(id));
+}
+
+struct jsonrpc_msg *
+jsonrpc_create_error(struct json *error, const struct json *id)
+{
+    return jsonrpc_create(JSONRPC_REPLY, NULL, NULL, NULL, error,
+                           json_clone(id));
+}
+
+const char *
+jsonrpc_msg_type_to_string(enum jsonrpc_msg_type type)
+{
+    switch (type) {
+    case JSONRPC_REQUEST:
+        return "request";
+
+    case JSONRPC_NOTIFY:
+        return "notification";
+
+    case JSONRPC_REPLY:
+        return "reply";
+
+    case JSONRPC_ERROR:
+        return "error";
+    }
+    return "(null)";
+}
+
+char *
+jsonrpc_msg_is_valid(const struct jsonrpc_msg *m)
+{
+    const char *type_name;
+    unsigned int pattern;
+
+    if (m->params && m->params->type != JSON_ARRAY) {
+        return xstrdup("\"params\" must be JSON array");
+    }
+
+    switch (m->type) {
+    case JSONRPC_REQUEST:
+        pattern = 0x11001;
+        break;
+
+    case JSONRPC_NOTIFY:
+        pattern = 0x11000;
+        break;
+
+    case JSONRPC_REPLY:
+        pattern = 0x00101;
+        break;
+
+    case JSONRPC_ERROR:
+        pattern = 0x00011;
+        break;
+
+    default:
+        return xasprintf("invalid JSON-RPC message type %d", m->type);
+    }
+
+    type_name = jsonrpc_msg_type_to_string(m->type);
+    if ((m->method != NULL) != ((pattern & 0x10000) != 0)) {
+        return xasprintf("%s must%s have \"method\"",
+                         type_name, (pattern & 0x10000) ? "" : " not");
+
+    }
+    if ((m->params != NULL) != ((pattern & 0x1000) != 0)) {
+        return xasprintf("%s must%s have \"params\"",
+                         type_name, (pattern & 0x1000) ? "" : " not");
+
+    }
+    if ((m->result != NULL) != ((pattern & 0x100) != 0)) {
+        return xasprintf("%s must%s have \"result\"",
+                         type_name, (pattern & 0x100) ? "" : " not");
+
+    }
+    if ((m->error != NULL) != ((pattern & 0x10) != 0)) {
+        return xasprintf("%s must%s have \"error\"",
+                         type_name, (pattern & 0x10) ? "" : " not");
+
+    }
+    if ((m->id != NULL) != ((pattern & 0x1) != 0)) {
+        return xasprintf("%s must%s have \"id\"",
+                         type_name, (pattern & 0x1) ? "" : " not");
+
+    }
+    return NULL;
+}
+
+void
+jsonrpc_msg_destroy(struct jsonrpc_msg *m)
+{
+    if (m) {
+        free(m->method);
+        json_destroy(m->params);
+        json_destroy(m->result);
+        json_destroy(m->error);
+        json_destroy(m->id);
+        free(m);
+    }
+}
+
+static struct json *
+null_from_json_null(struct json *json)
+{
+    if (json && json->type == JSON_NULL) {
+        json_destroy(json);
+        return NULL;
+    }
+    return json;
+}
+
+char *
+jsonrpc_msg_from_json(struct json *json, struct jsonrpc_msg **msgp)
+{
+    struct json *method = NULL;
+    struct jsonrpc_msg *msg = NULL;
+    struct shash *object;
+    char *error;
+
+    if (json->type != JSON_OBJECT) {
+        error = xstrdup("message is not a JSON object");
+        goto exit;
+    }
+    object = json_object(json);
+
+    method = shash_find_and_delete(object, "method");
+    if (method && method->type != JSON_STRING) {
+        error = xstrdup("method is not a JSON string");
+        goto exit;
+    }
+
+    msg = xzalloc(sizeof *msg);
+    msg->method = method ? xstrdup(method->u.string) : NULL;
+    msg->params = null_from_json_null(shash_find_and_delete(object, "params"));
+    msg->result = null_from_json_null(shash_find_and_delete(object, "result"));
+    msg->error = null_from_json_null(shash_find_and_delete(object, "error"));
+    msg->id = null_from_json_null(shash_find_and_delete(object, "id"));
+    msg->type = (msg->result ? JSONRPC_REPLY
+                 : msg->error ? JSONRPC_ERROR
+                 : msg->id ? JSONRPC_REQUEST
+                 : JSONRPC_NOTIFY);
+    if (!shash_is_empty(object)) {
+        error = xasprintf("message has unexpected member \"%s\"",
+                          shash_first(object)->name);
+        goto exit;
+    }
+    error = jsonrpc_msg_is_valid(msg);
+    if (error) {
+        goto exit;
+    }
+
+exit:
+    json_destroy(method);
+    json_destroy(json);
+    if (error) {
+        jsonrpc_msg_destroy(msg);
+        msg = NULL;
+    }
+    *msgp = msg;
+    return error;
+}
+
+struct json *
+jsonrpc_msg_to_json(struct jsonrpc_msg *m)
+{
+    struct json *json = json_object_create();
+
+    if (m->method) {
+        json_object_put(json, "method", json_string_create_nocopy(m->method));
+    }
+
+    if (m->params) {
+        json_object_put(json, "params", m->params);
+    }
+
+    if (m->result) {
+        json_object_put(json, "result", m->result);
+    } else if (m->type == JSONRPC_ERROR) {
+        json_object_put(json, "result", json_null_create());
+    }
+
+    if (m->error) {
+        json_object_put(json, "error", m->error);
+    } else if (m->type == JSONRPC_REPLY) {
+        json_object_put(json, "error", json_null_create());
+    }
+
+    if (m->id) {
+        json_object_put(json, "id", m->id);
+    } else if (m->type == JSONRPC_NOTIFY) {
+        json_object_put(json, "id", json_null_create());
+    }
+
+    free(m);
+
+    return json;
+}
+\f
+/* A JSON-RPC session with reconnection. */
+
+struct jsonrpc_session {
+    struct reconnect *reconnect;
+    struct jsonrpc *rpc;
+    struct stream *stream;
+    unsigned int seqno;
+};
+
+struct jsonrpc_session *
+jsonrpc_session_open(const char *name)
+{
+    struct jsonrpc_session *s;
+
+    s = xmalloc(sizeof *s);
+    s->reconnect = reconnect_create(time_msec());
+    reconnect_set_name(s->reconnect, name);
+    reconnect_enable(s->reconnect, time_msec());
+    s->rpc = NULL;
+    s->stream = NULL;
+    s->seqno = 0;
+
+    return s;
+}
+
+void
+jsonrpc_session_close(struct jsonrpc_session *s)
+{
+    if (s) {
+        jsonrpc_close(s->rpc);
+        reconnect_destroy(s->reconnect);
+        free(s);
+    }
+}
+
+static void
+jsonrpc_session_disconnect(struct jsonrpc_session *s)
+{
+    reconnect_disconnected(s->reconnect, time_msec(), 0);
+    if (s->rpc) {
+        jsonrpc_error(s->rpc, EOF);
+        jsonrpc_close(s->rpc);
+        s->rpc = NULL;
+        s->seqno++;
+    } else if (s->stream) {
+        stream_close(s->stream);
+        s->stream = NULL;
+        s->seqno++;
+    }
+}
+
+static void
+jsonrpc_session_connect(struct jsonrpc_session *s)
+{
+    int error;
+
+    jsonrpc_session_disconnect(s);
+    error = stream_open(reconnect_get_name(s->reconnect), &s->stream);
+    if (error) {
+        reconnect_connect_failed(s->reconnect, time_msec(), error);
+    } else {
+        reconnect_connecting(s->reconnect, time_msec());
+    }
+    s->seqno++;
+}
+
+void
+jsonrpc_session_run(struct jsonrpc_session *s)
+{
+    if (s->rpc) {
+        int error;
+
+        jsonrpc_run(s->rpc);
+        error = jsonrpc_get_status(s->rpc);
+        if (error) {
+            jsonrpc_session_disconnect(s);
+        }
+    } else if (s->stream) {
+        int error = stream_connect(s->stream);
+        if (!error) {
+            reconnect_connected(s->reconnect, time_msec());
+            s->rpc = jsonrpc_open(s->stream);
+            s->stream = NULL;
+        } else if (error != EAGAIN) {
+            reconnect_connect_failed(s->reconnect, time_msec(), error);
+            stream_close(s->stream);
+            s->stream = NULL;
+        }
+    }
+
+    switch (reconnect_run(s->reconnect, time_msec())) {
+    case RECONNECT_CONNECT:
+        jsonrpc_session_connect(s);
+        break;
+
+    case RECONNECT_DISCONNECT:
+        jsonrpc_session_disconnect(s);
+        break;
+
+    case RECONNECT_PROBE:
+        if (s->rpc) {
+            struct json *params;
+            struct jsonrpc_msg *request;
+
+            params = json_array_create_empty();
+            request = jsonrpc_create_request("echo", params, NULL);
+            json_destroy(request->id);
+            request->id = json_string_create("echo");
+            jsonrpc_send(s->rpc, request);
+        }
+        break;
+    }
+}
+
+void
+jsonrpc_session_wait(struct jsonrpc_session *s)
+{
+    if (s->rpc) {
+        jsonrpc_wait(s->rpc);
+    } else if (s->stream) {
+        stream_connect_wait(s->stream);
+    }
+    reconnect_wait(s->reconnect, time_msec());
+}
+
+size_t
+jsonrpc_session_get_backlog(const struct jsonrpc_session *s)
+{
+    return s->rpc ? jsonrpc_get_backlog(s->rpc) : 0;
+}
+
+const char *
+jsonrpc_session_get_name(const struct jsonrpc_session *s)
+{
+    return reconnect_get_name(s->reconnect);
+}
+
+int
+jsonrpc_session_send(struct jsonrpc_session *s, struct jsonrpc_msg *msg)
+{
+    return s->rpc ? jsonrpc_send(s->rpc, msg) : ENOTCONN;
+}
+
+struct jsonrpc_msg *
+jsonrpc_session_recv(struct jsonrpc_session *s)
+{
+    struct jsonrpc_msg *msg = NULL;
+    if (s->rpc) {
+        jsonrpc_recv(s->rpc, &msg);
+        if (msg) {
+            reconnect_received(s->reconnect, time_msec());
+        }
+    }
+    return msg;
+}
+
+void
+jsonrpc_session_recv_wait(struct jsonrpc_session *s)
+{
+    if (s->rpc) {
+        jsonrpc_recv_wait(s->rpc);
+    }
+}
+
+bool
+jsonrpc_session_is_connected(const struct jsonrpc_session *s)
+{
+    return s->rpc != NULL;
+}
+
+unsigned int
+jsonrpc_session_get_seqno(const struct jsonrpc_session *s)
+{
+    return s->seqno;
+}
+
+void
+jsonrpc_session_force_reconnect(struct jsonrpc_session *s)
+{
+    reconnect_force_reconnect(s->reconnect, time_msec());
+}
diff --git a/lib/jsonrpc.h b/lib/jsonrpc.h
new file mode 100644 (file)
index 0000000..93ac2e8
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef JSONRPC_H
+#define JSONRPC_H 1
+
+/* This is an implementation of the JSON-RPC 1.0 specification defined at
+ * http://json-rpc.org/wiki/specification. */
+
+#include <stdbool.h>
+#include <stddef.h>
+
+struct json;
+struct jsonrpc_msg;
+struct stream;
+\f
+/* API for a JSON-RPC stream. */
+
+struct jsonrpc *jsonrpc_open(struct stream *);
+void jsonrpc_close(struct jsonrpc *);
+
+void jsonrpc_run(struct jsonrpc *);
+void jsonrpc_wait(struct jsonrpc *);
+
+void jsonrpc_error(struct jsonrpc *, int error);
+int jsonrpc_get_status(const struct jsonrpc *);
+size_t jsonrpc_get_backlog(const struct jsonrpc *);
+const char *jsonrpc_get_name(const struct jsonrpc *);
+
+int jsonrpc_send(struct jsonrpc *, struct jsonrpc_msg *);
+int jsonrpc_recv(struct jsonrpc *, struct jsonrpc_msg **);
+void jsonrpc_recv_wait(struct jsonrpc *);
+
+int jsonrpc_send_block(struct jsonrpc *, struct jsonrpc_msg *);
+int jsonrpc_recv_block(struct jsonrpc *, struct jsonrpc_msg **);
+int jsonrpc_transact_block(struct jsonrpc *, struct jsonrpc_msg *,
+                           struct jsonrpc_msg **);
+
+/* Messages. */
+enum jsonrpc_msg_type {
+    JSONRPC_REQUEST,           /* Request. */
+    JSONRPC_NOTIFY,            /* Notification. */
+    JSONRPC_REPLY,             /* Successful reply. */
+    JSONRPC_ERROR              /* Error reply. */
+};
+
+struct jsonrpc_msg {
+    enum jsonrpc_msg_type type;
+    char *method;               /* Request or notification only. */
+    struct json *params;        /* Request or notification only. */
+    struct json *result;        /* Successful reply only. */
+    struct json *error;         /* Error reply only. */
+    struct json *id;            /* Request or reply only. */
+};
+
+struct jsonrpc_msg *jsonrpc_create_request(const char *method,
+                                           struct json *params,
+                                           struct json **idp);
+struct jsonrpc_msg *jsonrpc_create_notify(const char *method,
+                                          struct json *params);
+struct jsonrpc_msg *jsonrpc_create_reply(struct json *result,
+                                         const struct json *id);
+struct jsonrpc_msg *jsonrpc_create_error(struct json *error,
+                                         const struct json *id);
+
+const char *jsonrpc_msg_type_to_string(enum jsonrpc_msg_type);
+char *jsonrpc_msg_is_valid(const struct jsonrpc_msg *);
+void jsonrpc_msg_destroy(struct jsonrpc_msg *);
+
+char *jsonrpc_msg_from_json(struct json *, struct jsonrpc_msg **);
+struct json *jsonrpc_msg_to_json(struct jsonrpc_msg *);
+\f
+/* A JSON-RPC session with reconnection. */
+
+struct jsonrpc_session *jsonrpc_session_open(const char *name);
+void jsonrpc_session_close(struct jsonrpc_session *);
+
+void jsonrpc_session_run(struct jsonrpc_session *);
+void jsonrpc_session_wait(struct jsonrpc_session *);
+
+size_t jsonrpc_session_get_backlog(const struct jsonrpc_session *);
+const char *jsonrpc_session_get_name(const struct jsonrpc_session *);
+
+int jsonrpc_session_send(struct jsonrpc_session *, struct jsonrpc_msg *);
+struct jsonrpc_msg *jsonrpc_session_recv(struct jsonrpc_session *);
+void jsonrpc_session_recv_wait(struct jsonrpc_session *);
+
+bool jsonrpc_session_is_connected(const struct jsonrpc_session *);
+unsigned int jsonrpc_session_get_seqno(const struct jsonrpc_session *);
+void jsonrpc_session_force_reconnect(struct jsonrpc_session *);
+
+#endif /* jsonrpc.h */
index ecfa87f..78346ac 100644 (file)
@@ -113,7 +113,7 @@ lswitch_create(struct rconn *rconn, bool learn_macs,
     struct lswitch *sw;
     size_t i;
 
-    sw = xcalloc(1, sizeof *sw);
+    sw = xzalloc(sizeof *sw);
     sw->max_idle = max_idle;
     sw->datapath_id = 0;
     sw->last_features_request = time_now() - 1;
diff --git a/lib/lockfile.c b/lib/lockfile.c
new file mode 100644 (file)
index 0000000..9bb7c6b
--- /dev/null
@@ -0,0 +1,280 @@
+ /* Copyright (c) 2008, 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "lockfile.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "coverage.h"
+#include "hash.h"
+#include "hmap.h"
+#include "timeval.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_lockfile
+#include "vlog.h"
+
+struct lockfile {
+    struct hmap_node hmap_node;
+    char *name;
+    dev_t device;
+    ino_t inode;
+    int fd;
+};
+
+/* Lock table.
+ *
+ * We have to do this stupid dance because POSIX says that closing *any* file
+ * descriptor for a file on which a process holds a lock drops *all* locks on
+ * that file.  That means that we can't afford to open a lockfile more than
+ * once. */
+static struct hmap lock_table = HMAP_INITIALIZER(&lock_table);
+
+static void lockfile_unhash(struct lockfile *);
+static int lockfile_try_lock(const char *name, bool block,
+                             struct lockfile **lockfilep);
+
+/* Returns the name of the lockfile that would be created for locking a file
+ * named 'file_name'.  The caller is responsible for freeing the returned
+ * name, with free(), when it is no longer needed. */
+char *
+lockfile_name(const char *file_name)
+{
+    const char *slash = strrchr(file_name, '/');
+    return (slash
+            ? xasprintf("%.*s/.%s.~lock~",
+                        (int) (slash - file_name), file_name, slash + 1)
+            : xasprintf(".%s.~lock~", file_name));
+}
+
+/* Locks the configuration file against modification by other processes and
+ * re-reads it from disk.
+ *
+ * The 'timeout' specifies the maximum number of milliseconds to wait for the
+ * config file to become free.  Use 0 to avoid waiting or INT_MAX to wait
+ * forever.
+ *
+ * Returns 0 on success, otherwise a positive errno value.  On success,
+ * '*lockfilep' is set to point to a new "struct lockfile *" that may be
+ * unlocked with lockfile_unlock().  On failure, '*lockfilep' is set to
+ * NULL. */
+int
+lockfile_lock(const char *file, int timeout, struct lockfile **lockfilep)
+{
+    /* Only exclusive ("write") locks are supported.  This is not a problem
+     * because the Open vSwitch code that currently uses lock files does so in
+     * stylized ways such that any number of readers may access a file while it
+     * is being written. */
+    long long int start, elapsed;
+    char *lock_name;
+    int error;
+
+    COVERAGE_INC(lockfile_lock);
+
+    lock_name = lockfile_name(file);
+    time_refresh();
+    start = time_msec();
+
+    do {
+        error = lockfile_try_lock(lock_name, timeout > 0, lockfilep);
+        time_refresh();
+        elapsed = time_msec() - start;
+    } while (error == EINTR && (timeout == INT_MAX || elapsed < timeout));
+
+    if (!error) {
+        if (elapsed) {
+            VLOG_WARN("%s: waited %lld ms for lock file",
+                      lock_name, elapsed);
+        }
+    } else if (error == EINTR) {
+        COVERAGE_INC(lockfile_timeout);
+        VLOG_WARN("%s: giving up on lock file after %lld ms",
+                  lock_name, elapsed);
+        error = ETIMEDOUT;
+    } else {
+        COVERAGE_INC(lockfile_error);
+        if (error == EACCES) {
+            error = EAGAIN;
+        }
+        VLOG_WARN("%s: failed to lock file "
+                  "(after %lld ms, with %d-ms timeout): %s",
+                  lock_name, elapsed, timeout, strerror(error));
+    }
+
+    free(lock_name);
+    return error;
+}
+
+/* Unlocks 'lockfile', which must have been created by a call to
+ * lockfile_lock(), and frees 'lockfile'. */
+void
+lockfile_unlock(struct lockfile *lockfile)
+{
+    if (lockfile) {
+        COVERAGE_INC(lockfile_unlock);
+        lockfile_unhash(lockfile);
+        free(lockfile->name);
+        free(lockfile);
+    }
+}
+
+/* Marks all the currently locked lockfiles as no longer locked.  It makes
+ * sense to call this function after fork(), because a child created by fork()
+ * does not hold its parents' locks. */
+void
+lockfile_postfork(void)
+{
+    struct lockfile *lockfile;
+
+    HMAP_FOR_EACH (lockfile, struct lockfile, hmap_node, &lock_table) {
+        if (lockfile->fd >= 0) {
+            VLOG_WARN("%s: child does not inherit lock", lockfile->name);
+            lockfile_unhash(lockfile);
+        }
+    }
+}
+\f
+static uint32_t
+lockfile_hash(dev_t device, ino_t inode)
+{
+    return hash_bytes(&device, sizeof device,
+                      hash_bytes(&inode, sizeof inode, 0));
+}
+
+static struct lockfile *
+lockfile_find(dev_t device, ino_t inode)
+{
+    struct lockfile *lockfile;
+
+    HMAP_FOR_EACH_WITH_HASH (lockfile, struct lockfile, hmap_node,
+                             lockfile_hash(device, inode), &lock_table) {
+        if (lockfile->device == device && lockfile->inode == inode) {
+            return lockfile;
+        }
+    }
+    return NULL;
+}
+
+static void
+lockfile_unhash(struct lockfile *lockfile)
+{
+    if (lockfile->fd >= 0) {
+        close(lockfile->fd);
+        lockfile->fd = -1;
+        hmap_remove(&lock_table, &lockfile->hmap_node);
+    }
+}
+
+static struct lockfile *
+lockfile_register(const char *name, dev_t device, ino_t inode, int fd)
+{
+    struct lockfile *lockfile;
+
+    lockfile = lockfile_find(device, inode);
+    if (lockfile) {
+        VLOG_ERR("%s: lock file disappeared and reappeared!", name);
+        lockfile_unhash(lockfile);
+    }
+
+    lockfile = xmalloc(sizeof *lockfile);
+    lockfile->name = xstrdup(name);
+    lockfile->device = device;
+    lockfile->inode = inode;
+    lockfile->fd = fd;
+    hmap_insert(&lock_table, &lockfile->hmap_node,
+                lockfile_hash(device, inode));
+    return lockfile;
+}
+
+static int
+lockfile_try_lock(const char *name, bool block, struct lockfile **lockfilep)
+{
+    struct flock l;
+    struct stat s;
+    int error;
+    int fd;
+
+    *lockfilep = NULL;
+
+    /* Open the lock file, first creating it if necessary. */
+    for (;;) {
+        /* Check whether we've already got a lock on that file. */
+        if (!stat(name, &s)) {
+            if (lockfile_find(s.st_dev, s.st_ino)) {
+                return EDEADLK;
+            }
+        } else if (errno != ENOENT) {
+            VLOG_WARN("%s: failed to stat lock file: %s",
+                      name, strerror(errno));
+            return errno;
+        }
+
+        /* Try to open an existing lock file. */
+        fd = open(name, O_RDWR);
+        if (fd >= 0) {
+            break;
+        } else if (errno != ENOENT) {
+            VLOG_WARN("%s: failed to open lock file: %s",
+                      name, strerror(errno));
+            return errno;
+        }
+
+        /* Try to create a new lock file. */
+        VLOG_INFO("%s: lock file does not exist, creating", name);
+        fd = open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
+        if (fd >= 0) {
+            break;
+        } else if (errno != EEXIST) {
+            VLOG_WARN("%s: failed to create lock file: %s",
+                      name, strerror(errno));
+            return errno;
+        }
+
+        /* Someone else created the lock file.  Try again. */
+    }
+
+    /* Get the inode and device number for the lock table. */
+    if (fstat(fd, &s)) {
+        VLOG_ERR("%s: failed to fstat lock file: %s", name, strerror(errno));
+        close(fd);
+        return errno;
+    }
+
+    /* Try to lock the file. */
+    memset(&l, 0, sizeof l);
+    l.l_type = F_WRLCK;
+    l.l_whence = SEEK_SET;
+    l.l_start = 0;
+    l.l_len = 0;
+
+    time_disable_restart();
+    error = fcntl(fd, block ? F_SETLKW : F_SETLK, &l) == -1 ? errno : 0;
+    time_enable_restart();
+
+    if (!error) {
+        *lockfilep = lockfile_register(name, s.st_dev, s.st_ino, fd);
+    } else {
+        close(fd);
+    }
+    return error;
+}
+
diff --git a/lib/lockfile.h b/lib/lockfile.h
new file mode 100644 (file)
index 0000000..c52fa21
--- /dev/null
@@ -0,0 +1,26 @@
+/* Copyright (c) 2008, 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LOCKFILE_H
+#define LOCKFILE_H 1
+
+struct lockfile;
+
+char *lockfile_name(const char *file);
+int lockfile_lock(const char *file, int timeout, struct lockfile **);
+void lockfile_unlock(struct lockfile *);
+void lockfile_postfork(void);
+
+#endif /* lib/lockfile.h */
index 61994c2..47d89ef 100644 (file)
@@ -286,7 +286,7 @@ netdev_linux_open(const char *name, int ethertype, struct netdev **netdevp)
     int error;
 
     /* Allocate network device. */
-    netdev = xcalloc(1, sizeof *netdev);
+    netdev = xzalloc(sizeof *netdev);
     netdev_init(&netdev->netdev, name, &netdev_linux_class);
     netdev->netdev_fd = -1;
     netdev->tap_fd = -1;
diff --git a/lib/ovsdb-data.c b/lib/ovsdb-data.c
new file mode 100644 (file)
index 0000000..46298cb
--- /dev/null
@@ -0,0 +1,756 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb-data.h"
+
+#include <assert.h>
+
+#include "hash.h"
+#include "ovsdb-error.h"
+#include "json.h"
+#include "shash.h"
+#include "sort.h"
+
+static struct json *
+wrap_json(const char *name, struct json *wrapped)
+{
+    return json_array_create_2(json_string_create(name), wrapped);
+}
+
+void
+ovsdb_atom_init_default(union ovsdb_atom *atom, enum ovsdb_atomic_type type)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        atom->integer = 0;
+        break;
+
+    case OVSDB_TYPE_REAL:
+        atom->real = 0.0;
+        break;
+
+    case OVSDB_TYPE_BOOLEAN:
+        atom->boolean = false;
+        break;
+
+    case OVSDB_TYPE_STRING:
+        atom->string = xmemdup("", 1);
+        break;
+
+    case OVSDB_TYPE_UUID:
+        uuid_zero(&atom->uuid);
+        break;
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+void
+ovsdb_atom_clone(union ovsdb_atom *new, const union ovsdb_atom *old,
+                 enum ovsdb_atomic_type type)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        new->integer = old->integer;
+        break;
+
+    case OVSDB_TYPE_REAL:
+        new->real = old->real;
+        break;
+
+    case OVSDB_TYPE_BOOLEAN:
+        new->boolean = old->boolean;
+        break;
+
+    case OVSDB_TYPE_STRING:
+        new->string = xstrdup(old->string);
+        break;
+
+    case OVSDB_TYPE_UUID:
+        new->uuid = old->uuid;
+        break;
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+void
+ovsdb_atom_swap(union ovsdb_atom *a, union ovsdb_atom *b)
+{
+    union ovsdb_atom tmp = *a;
+    *a = *b;
+    *b = tmp;
+}
+
+uint32_t
+ovsdb_atom_hash(const union ovsdb_atom *atom, enum ovsdb_atomic_type type,
+                uint32_t basis)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        return hash_int(atom->integer, basis);
+
+    case OVSDB_TYPE_REAL:
+        return hash_double(atom->real, basis);
+
+    case OVSDB_TYPE_BOOLEAN:
+        return hash_boolean(atom->boolean, basis);
+
+    case OVSDB_TYPE_STRING:
+        return hash_string(atom->string, basis);
+
+    case OVSDB_TYPE_UUID:
+        return hash_int(uuid_hash(&atom->uuid), basis);
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+int
+ovsdb_atom_compare_3way(const union ovsdb_atom *a,
+                        const union ovsdb_atom *b,
+                        enum ovsdb_atomic_type type)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        return a->integer < b->integer ? -1 : a->integer > b->integer;
+
+    case OVSDB_TYPE_REAL:
+        return a->real < b->real ? -1 : a->real > b->real;
+
+    case OVSDB_TYPE_BOOLEAN:
+        return a->boolean - b->boolean;
+
+    case OVSDB_TYPE_STRING:
+        return strcmp(a->string, b->string);
+
+    case OVSDB_TYPE_UUID:
+        return uuid_compare_3way(&a->uuid, &b->uuid);
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+static struct ovsdb_error *
+unwrap_json(const struct json *json, const char *name,
+            enum json_type value_type, const struct json **value)
+{
+    if (json->type != JSON_ARRAY
+        || json->u.array.n != 2
+        || json->u.array.elems[0]->type != JSON_STRING
+        || (name && strcmp(json->u.array.elems[0]->u.string, name))
+        || json->u.array.elems[1]->type != value_type)
+    {
+        return ovsdb_syntax_error(json, NULL, "expected [\"%s\", <%s>]", name,
+                                  json_type_to_string(value_type));
+    }
+    *value = json->u.array.elems[1];
+    return NULL;
+}
+
+static struct ovsdb_error *
+parse_json_pair(const struct json *json,
+                const struct json **elem0, const struct json **elem1)
+{
+    if (json->type != JSON_ARRAY || json->u.array.n != 2) {
+        return ovsdb_syntax_error(json, NULL, "expected 2-element array");
+    }
+    *elem0 = json->u.array.elems[0];
+    *elem1 = json->u.array.elems[1];
+    return NULL;
+}
+
+static struct ovsdb_error *
+ovsdb_atom_parse_uuid(struct uuid *uuid, const struct json *json,
+                      const struct ovsdb_symbol_table *symtab)
+    WARN_UNUSED_RESULT;
+
+static struct ovsdb_error *
+ovsdb_atom_parse_uuid(struct uuid *uuid, const struct json *json,
+                      const struct ovsdb_symbol_table *symtab)
+{
+    struct ovsdb_error *error0;
+    const struct json *value;
+
+    error0 = unwrap_json(json, "uuid", JSON_STRING, &value);
+    if (!error0) {
+        const char *uuid_string = json_string(value);
+        if (!uuid_from_string(uuid, uuid_string)) {
+            return ovsdb_syntax_error(json, NULL, "\"%s\" is not a valid UUID",
+                                      uuid_string);
+        }
+    } else if (symtab) {
+        struct ovsdb_error *error1;
+
+        error1 = unwrap_json(json, "named-uuid", JSON_STRING, &value);
+        if (!error1) {
+            const char *name = json_string(value);
+            const struct uuid *named_uuid;
+
+            ovsdb_error_destroy(error0);
+
+            named_uuid = ovsdb_symbol_table_get(symtab, name);
+            if (named_uuid) {
+                *uuid = *named_uuid;
+                return NULL;
+            } else {
+                return ovsdb_syntax_error(json, NULL,
+                                          "unknown named-uuid \"%s\"", name);
+            }
+        }
+        ovsdb_error_destroy(error1);
+    }
+
+    return error0;
+}
+
+struct ovsdb_error *
+ovsdb_atom_from_json(union ovsdb_atom *atom, enum ovsdb_atomic_type type,
+                     const struct json *json,
+                     const struct ovsdb_symbol_table *symtab)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        if (json->type == JSON_INTEGER) {
+            atom->integer = json->u.integer;
+            return NULL;
+        }
+        break;
+
+    case OVSDB_TYPE_REAL:
+        if (json->type == JSON_INTEGER) {
+            atom->real = json->u.integer;
+            return NULL;
+        } else if (json->type == JSON_REAL) {
+            atom->real = json->u.real;
+            return NULL;
+        }
+        break;
+
+    case OVSDB_TYPE_BOOLEAN:
+        if (json->type == JSON_TRUE) {
+            atom->boolean = true;
+            return NULL;
+        } else if (json->type == JSON_FALSE) {
+            atom->boolean = false;
+            return NULL;
+        }
+        break;
+
+    case OVSDB_TYPE_STRING:
+        if (json->type == JSON_STRING) {
+            atom->string = xstrdup(json->u.string);
+            return NULL;
+        }
+        break;
+
+    case OVSDB_TYPE_UUID:
+        return ovsdb_atom_parse_uuid(&atom->uuid, json, symtab);
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+
+    return ovsdb_syntax_error(json, NULL, "expected %s",
+                              ovsdb_atomic_type_to_string(type));
+}
+
+struct json *
+ovsdb_atom_to_json(const union ovsdb_atom *atom, enum ovsdb_atomic_type type)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        return json_integer_create(atom->integer);
+
+    case OVSDB_TYPE_REAL:
+        return json_real_create(atom->real);
+
+    case OVSDB_TYPE_BOOLEAN:
+        return json_boolean_create(atom->boolean);
+
+    case OVSDB_TYPE_STRING:
+        return json_string_create(atom->string);
+
+    case OVSDB_TYPE_UUID:
+        return wrap_json("uuid", json_string_create_nocopy(
+                             xasprintf(UUID_FMT, UUID_ARGS(&atom->uuid))));
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+\f
+static union ovsdb_atom *
+alloc_default_atoms(enum ovsdb_atomic_type type, size_t n)
+{
+    if (type != OVSDB_TYPE_VOID && n) {
+        union ovsdb_atom *atoms;
+        unsigned int i;
+
+        atoms = xmalloc(n * sizeof *atoms);
+        for (i = 0; i < n; i++) {
+            ovsdb_atom_init_default(&atoms[i], type);
+        }
+        return atoms;
+    } else {
+        /* Avoid wasting memory in the n == 0 case, because xmalloc(0) is
+         * treated as xmalloc(1). */
+        return NULL;
+    }
+}
+
+void
+ovsdb_datum_init_default(struct ovsdb_datum *datum,
+                         const struct ovsdb_type *type)
+{
+    datum->n = type->n_min;
+    datum->keys = alloc_default_atoms(type->key_type, datum->n);
+    datum->values = alloc_default_atoms(type->value_type, datum->n);
+}
+
+static union ovsdb_atom *
+clone_atoms(const union ovsdb_atom *old, enum ovsdb_atomic_type type, size_t n)
+{
+    if (type != OVSDB_TYPE_VOID && n) {
+        union ovsdb_atom *new;
+        unsigned int i;
+
+        new = xmalloc(n * sizeof *new);
+        for (i = 0; i < n; i++) {
+            ovsdb_atom_clone(&new[i], &old[i], type);
+        }
+        return new;
+    } else {
+        /* Avoid wasting memory in the n == 0 case, because xmalloc(0) is
+         * treated as xmalloc(1). */
+        return NULL;
+    }
+}
+
+void
+ovsdb_datum_clone(struct ovsdb_datum *new, const struct ovsdb_datum *old,
+                  const struct ovsdb_type *type)
+{
+    unsigned int n = old->n;
+    new->n = n;
+    new->keys = clone_atoms(old->keys, type->key_type, n);
+    new->values = clone_atoms(old->values, type->value_type, n);
+}
+
+static void
+free_data(enum ovsdb_atomic_type type,
+          union ovsdb_atom *atoms, size_t n_atoms)
+{
+    if (ovsdb_atom_needs_destruction(type)) {
+        unsigned int i;
+        for (i = 0; i < n_atoms; i++) {
+            ovsdb_atom_destroy(&atoms[i], type);
+        }
+    }
+    free(atoms);
+}
+
+void
+ovsdb_datum_destroy(struct ovsdb_datum *datum, const struct ovsdb_type *type)
+{
+    free_data(type->key_type, datum->keys, datum->n);
+    free_data(type->value_type, datum->values, datum->n);
+}
+
+void
+ovsdb_datum_swap(struct ovsdb_datum *a, struct ovsdb_datum *b)
+{
+    struct ovsdb_datum tmp = *a;
+    *a = *b;
+    *b = tmp;
+}
+
+struct ovsdb_datum_sort_cbdata {
+    const struct ovsdb_type *type;
+    struct ovsdb_datum *datum;
+};
+
+static int
+ovsdb_datum_sort_compare_cb(size_t a, size_t b, void *cbdata_)
+{
+    struct ovsdb_datum_sort_cbdata *cbdata = cbdata_;
+
+    return ovsdb_atom_compare_3way(&cbdata->datum->keys[a],
+                                   &cbdata->datum->keys[b],
+                                   cbdata->type->key_type);
+}
+
+static void
+ovsdb_datum_sort_swap_cb(size_t a, size_t b, void *cbdata_)
+{
+    struct ovsdb_datum_sort_cbdata *cbdata = cbdata_;
+
+    ovsdb_atom_swap(&cbdata->datum->keys[a], &cbdata->datum->keys[b]);
+    if (cbdata->type->value_type != OVSDB_TYPE_VOID) {
+        ovsdb_atom_swap(&cbdata->datum->values[a], &cbdata->datum->values[b]);
+    }
+}
+
+static struct ovsdb_error *
+ovsdb_datum_sort(struct ovsdb_datum *datum, const struct ovsdb_type *type)
+{
+    if (datum->n < 2) {
+        return NULL;
+    } else {
+        struct ovsdb_datum_sort_cbdata cbdata;
+        size_t i;
+
+        cbdata.type = type;
+        cbdata.datum = datum;
+        sort(datum->n, ovsdb_datum_sort_compare_cb, ovsdb_datum_sort_swap_cb,
+             &cbdata);
+
+        for (i = 0; i < datum->n - 1; i++) {
+            if (ovsdb_atom_equals(&datum->keys[i], &datum->keys[i + 1],
+                                  type->key_type)) {
+                if (ovsdb_type_is_map(type)) {
+                    return ovsdb_error(NULL, "map contains duplicate key");
+                } else {
+                    return ovsdb_error(NULL, "set contains duplicate");
+                }
+            }
+        }
+
+        return NULL;
+    }
+}
+
+struct ovsdb_error *
+ovsdb_datum_from_json(struct ovsdb_datum *datum,
+                      const struct ovsdb_type *type,
+                      const struct json *json,
+                      const struct ovsdb_symbol_table *symtab)
+{
+    struct ovsdb_error *error;
+
+    if (ovsdb_type_is_scalar(type)) {
+        datum->n = 1;
+        datum->keys = xmalloc(sizeof *datum->keys);
+        datum->values = NULL;
+
+        error = ovsdb_atom_from_json(&datum->keys[0], type->key_type,
+                                     json, symtab);
+        if (error) {
+            free(datum->keys);
+        }
+        return error;
+    } else {
+        bool is_map = ovsdb_type_is_map(type);
+        const char *class = is_map ? "map" : "set";
+        const struct json *inner;
+        unsigned int i;
+        size_t n;
+
+        assert(is_map || ovsdb_type_is_set(type));
+
+        error = unwrap_json(json, class, JSON_ARRAY, &inner);
+        if (error) {
+            return error;
+        }
+
+        n = inner->u.array.n;
+        if (n < type->n_min || n > type->n_max) {
+            return ovsdb_syntax_error(json, NULL, "%s must have %u to "
+                                      "%u members but %zu are present",
+                                      class, type->n_min, type->n_max, n);
+        }
+
+        datum->n = 0;
+        datum->keys = xmalloc(n * sizeof *datum->keys);
+        datum->values = is_map ? xmalloc(n * sizeof *datum->values) : NULL;
+        for (i = 0; i < n; i++) {
+            const struct json *element = inner->u.array.elems[i];
+            const struct json *key = NULL;
+            const struct json *value = NULL;
+
+            if (!is_map) {
+                key = element;
+            } else {
+                error = parse_json_pair(element, &key, &value);
+                if (error) {
+                    goto error;
+                }
+            }
+
+            error = ovsdb_atom_from_json(&datum->keys[i], type->key_type,
+                                         key, symtab);
+            if (error) {
+                goto error;
+            }
+
+            if (is_map) {
+                error = ovsdb_atom_from_json(&datum->values[i],
+                                             type->value_type, value, symtab);
+                if (error) {
+                    ovsdb_atom_destroy(&datum->keys[i], type->key_type);
+                    goto error;
+                }
+            }
+
+            datum->n++;
+        }
+
+        error = ovsdb_datum_sort(datum, type);
+        if (error) {
+            goto error;
+        }
+
+        return NULL;
+
+    error:
+        ovsdb_datum_destroy(datum, type);
+        return error;
+    }
+}
+
+struct json *
+ovsdb_datum_to_json(const struct ovsdb_datum *datum,
+                    const struct ovsdb_type *type)
+{
+    /* These tests somewhat tolerate a 'datum' that does not exactly match
+     * 'type', in particular a datum with 'n' not in the allowed range. */
+    if (datum->n == 1 && ovsdb_type_is_scalar(type)) {
+        return ovsdb_atom_to_json(&datum->keys[0], type->key_type);
+    } else if (type->value_type == OVSDB_TYPE_VOID) {
+        struct json **elems;
+        size_t i;
+
+        elems = xmalloc(datum->n * sizeof *elems);
+        for (i = 0; i < datum->n; i++) {
+            elems[i] = ovsdb_atom_to_json(&datum->keys[i], type->key_type);
+        }
+
+        return wrap_json("set", json_array_create(elems, datum->n));
+    } else {
+        struct json **elems;
+        size_t i;
+
+        elems = xmalloc(datum->n * sizeof *elems);
+        for (i = 0; i < datum->n; i++) {
+            elems[i] = json_array_create_2(
+                ovsdb_atom_to_json(&datum->keys[i], type->key_type),
+                ovsdb_atom_to_json(&datum->values[i], type->value_type));
+        }
+
+        return wrap_json("map", json_array_create(elems, datum->n));
+    }
+}
+
+static uint32_t
+hash_atoms(enum ovsdb_atomic_type type, const union ovsdb_atom *atoms,
+           unsigned int n, uint32_t basis)
+{
+    if (type != OVSDB_TYPE_VOID) {
+        unsigned int i;
+
+        for (i = 0; i < n; i++) {
+            basis = ovsdb_atom_hash(&atoms[i], type, basis);
+        }
+    }
+    return basis;
+}
+
+uint32_t
+ovsdb_datum_hash(const struct ovsdb_datum *datum,
+                 const struct ovsdb_type *type, uint32_t basis)
+{
+    basis = hash_atoms(type->key_type, datum->keys, datum->n, basis);
+    basis ^= (type->key_type << 24) | (type->value_type << 16) | datum->n;
+    basis = hash_atoms(type->value_type, datum->values, datum->n, basis);
+    return basis;
+}
+
+static int
+atom_arrays_compare_3way(const union ovsdb_atom *a,
+                  const union ovsdb_atom *b,
+                  enum ovsdb_atomic_type type,
+                  size_t n)
+{
+    unsigned int i;
+
+    for (i = 0; i < n; i++) {
+        int cmp = ovsdb_atom_compare_3way(&a[i], &b[i], type);
+        if (cmp) {
+            return cmp;
+        }
+    }
+
+    return 0;
+}
+
+bool
+ovsdb_datum_equals(const struct ovsdb_datum *a,
+                   const struct ovsdb_datum *b,
+                   const struct ovsdb_type *type)
+{
+    return !ovsdb_datum_compare_3way(a, b, type);
+}
+
+int
+ovsdb_datum_compare_3way(const struct ovsdb_datum *a,
+                         const struct ovsdb_datum *b,
+                         const struct ovsdb_type *type)
+{
+    int cmp;
+
+    if (a->n != b->n) {
+        return a->n < b->n ? -1 : 1;
+    }
+
+    cmp = atom_arrays_compare_3way(a->keys, b->keys, type->key_type, a->n);
+    if (cmp) {
+        return cmp;
+    }
+
+    return (type->value_type == OVSDB_TYPE_VOID ? 0
+            : atom_arrays_compare_3way(a->values, b->values, type->value_type,
+                                       a->n));
+}
+
+static bool
+ovsdb_datum_contains(const struct ovsdb_datum *a, int i,
+                     const struct ovsdb_datum *b,
+                     const struct ovsdb_type *type)
+{
+    int low = 0;
+    int high = b->n;
+    while (low < high) {
+        int j = (low + high) / 2;
+        int cmp = ovsdb_atom_compare_3way(&a->keys[i], &b->keys[j], type->key_type);
+        if (cmp < 0) {
+            high = j;
+        } else if (cmp > 0) {
+            low = j + 1;
+        } else {
+            return (type->value_type == OVSDB_TYPE_VOID
+                    || ovsdb_atom_equals(&a->values[i], &b->values[j],
+                                         type->value_type));
+        }
+    }
+    return false;
+}
+
+/* Returns true if every element in 'a' is also in 'b', false otherwise. */
+bool
+ovsdb_datum_includes_all(const struct ovsdb_datum *a,
+                         const struct ovsdb_datum *b,
+                         const struct ovsdb_type *type)
+{
+    size_t i;
+
+    for (i = 0; i < a->n; i++) {
+        if (!ovsdb_datum_contains(a, i, b, type)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+/* Returns true if no element in 'a' is also in 'b', false otherwise. */
+bool
+ovsdb_datum_excludes_all(const struct ovsdb_datum *a,
+                         const struct ovsdb_datum *b,
+                         const struct ovsdb_type *type)
+{
+    size_t i;
+
+    for (i = 0; i < a->n; i++) {
+        if (ovsdb_datum_contains(a, i, b, type)) {
+            return false;
+        }
+    }
+    return true;
+}
+\f
+struct ovsdb_symbol_table {
+    struct shash sh;
+};
+
+struct ovsdb_symbol_table *
+ovsdb_symbol_table_create(void)
+{
+    struct ovsdb_symbol_table *symtab = xmalloc(sizeof *symtab);
+    shash_init(&symtab->sh);
+    return symtab;
+}
+
+void
+ovsdb_symbol_table_destroy(struct ovsdb_symbol_table *symtab)
+{
+    if (symtab) {
+        struct shash_node *node, *next;
+
+        SHASH_FOR_EACH_SAFE (node, next, &symtab->sh) {
+            free(node->data);
+            shash_delete(&symtab->sh, node);
+        }
+        shash_destroy(&symtab->sh);
+        free(symtab);
+    }
+}
+
+const struct uuid *
+ovsdb_symbol_table_get(const struct ovsdb_symbol_table *symtab,
+                       const char *name)
+{
+    return shash_find_data(&symtab->sh, name);
+}
+
+void
+ovsdb_symbol_table_put(struct ovsdb_symbol_table *symtab, const char *name,
+                       const struct uuid *uuid)
+{
+    struct uuid *entry = shash_find_data(&symtab->sh, name);
+    if (!entry) {
+        shash_add(&symtab->sh, name, xmemdup(uuid, sizeof *uuid));
+    } else {
+        *entry = *uuid;
+    }
+}
diff --git a/lib/ovsdb-data.h b/lib/ovsdb-data.h
new file mode 100644 (file)
index 0000000..35c4e30
--- /dev/null
@@ -0,0 +1,128 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_DATA_H
+#define OVSDB_DATA_H 1
+
+#include <stdlib.h>
+#include "compiler.h"
+#include "ovsdb-types.h"
+
+struct ovsdb_symbol_table;
+
+/* One value of an atomic type (given by enum ovs_atomic_type). */
+union ovsdb_atom {
+    int64_t integer;
+    double real;
+    bool boolean;
+    char *string;
+    struct uuid uuid;
+};
+
+void ovsdb_atom_init_default(union ovsdb_atom *, enum ovsdb_atomic_type);
+void ovsdb_atom_clone(union ovsdb_atom *, const union ovsdb_atom *,
+                      enum ovsdb_atomic_type);
+void ovsdb_atom_swap(union ovsdb_atom *, union ovsdb_atom *);
+
+static inline bool
+ovsdb_atom_needs_destruction(enum ovsdb_atomic_type type)
+{
+    return type == OVSDB_TYPE_STRING;
+}
+
+static inline void
+ovsdb_atom_destroy(union ovsdb_atom *atom, enum ovsdb_atomic_type type)
+{
+    if (type == OVSDB_TYPE_STRING) {
+        free(atom->string);
+    }
+}
+
+uint32_t ovsdb_atom_hash(const union ovsdb_atom *, enum ovsdb_atomic_type,
+                         uint32_t basis);
+
+int ovsdb_atom_compare_3way(const union ovsdb_atom *,
+                            const union ovsdb_atom *,
+                            enum ovsdb_atomic_type);
+
+static inline bool ovsdb_atom_equals(const union ovsdb_atom *a,
+                                     const union ovsdb_atom *b,
+                                     enum ovsdb_atomic_type type)
+{
+    return !ovsdb_atom_compare_3way(a, b, type);
+}
+
+struct ovsdb_error *ovsdb_atom_from_json(union ovsdb_atom *,
+                                         enum ovsdb_atomic_type,
+                                         const struct json *,
+                                         const struct ovsdb_symbol_table *)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_atom_to_json(const union ovsdb_atom *,
+                                enum ovsdb_atomic_type);
+\f
+/* One value of an OVSDB type (given by struct ovsdb_type). */
+struct ovsdb_datum {
+    unsigned int n;             /* Number of 'keys' and 'values'. */
+    union ovsdb_atom *keys;     /* Each of the ovsdb_type's 'key_type'. */
+    union ovsdb_atom *values;   /* Each of the ovsdb_type's 'value_type'. */
+};
+
+void ovsdb_datum_init_default(struct ovsdb_datum *, const struct ovsdb_type *);
+void ovsdb_datum_clone(struct ovsdb_datum *, const struct ovsdb_datum *,
+                       const struct ovsdb_type *);
+void ovsdb_datum_destroy(struct ovsdb_datum *, const struct ovsdb_type *);
+void ovsdb_datum_swap(struct ovsdb_datum *, struct ovsdb_datum *);
+
+struct ovsdb_error *ovsdb_datum_from_json(struct ovsdb_datum *,
+                                          const struct ovsdb_type *,
+                                          const struct json *,
+                                          const struct ovsdb_symbol_table *)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_datum_to_json(const struct ovsdb_datum *,
+                                 const struct ovsdb_type *);
+
+uint32_t ovsdb_datum_hash(const struct ovsdb_datum *,
+                          const struct ovsdb_type *, uint32_t basis);
+int ovsdb_datum_compare_3way(const struct ovsdb_datum *,
+                             const struct ovsdb_datum *,
+                             const struct ovsdb_type *);
+bool ovsdb_datum_equals(const struct ovsdb_datum *,
+                        const struct ovsdb_datum *,
+                        const struct ovsdb_type *);
+bool ovsdb_datum_includes_all(const struct ovsdb_datum *,
+                              const struct ovsdb_datum *,
+                              const struct ovsdb_type *);
+bool ovsdb_datum_excludes_all(const struct ovsdb_datum *,
+                              const struct ovsdb_datum *,
+                              const struct ovsdb_type *);
+
+static inline bool
+ovsdb_datum_conforms_to_type(const struct ovsdb_datum *datum,
+                             const struct ovsdb_type *type)
+{
+    return datum->n >= type->n_min && datum->n <= type->n_max;
+}
+\f
+/* A table mapping from names to data items.  Currently the data items are
+ * always UUIDs; perhaps this will be expanded in the future. */
+
+struct ovsdb_symbol_table *ovsdb_symbol_table_create(void);
+void ovsdb_symbol_table_destroy(struct ovsdb_symbol_table *);
+const struct uuid *ovsdb_symbol_table_get(const struct ovsdb_symbol_table *,
+                                          const char *name);
+void ovsdb_symbol_table_put(struct ovsdb_symbol_table *, const char *name,
+                            const struct uuid *);
+
+#endif /* ovsdb-data.h */
diff --git a/lib/ovsdb-error.c b/lib/ovsdb-error.c
new file mode 100644 (file)
index 0000000..c0eddf2
--- /dev/null
@@ -0,0 +1,221 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb-error.h"
+
+#include <inttypes.h>
+
+#include "backtrace.h"
+#include "dynamic-string.h"
+#include "json.h"
+#include "util.h"
+
+struct ovsdb_error {
+    const char *tag;            /* String for "error" member. */
+    char *details;              /* String for "details" member. */
+    char *syntax;               /* String for "syntax" member. */
+    int errno_;                 /* Unix errno value, 0 if none. */
+};
+
+static struct ovsdb_error *
+ovsdb_error_valist(const char *tag, const char *details, va_list args)
+{
+    struct ovsdb_error *error = xmalloc(sizeof *error);
+    error->tag = tag ? tag : "ovsdb error";
+    error->details = details ? xvasprintf(details, args) : NULL;
+    error->syntax = NULL;
+    error->errno_ = 0;
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_error(const char *tag, const char *details, ...)
+{
+    struct ovsdb_error *error;
+    va_list args;
+
+    va_start(args, details);
+    error = ovsdb_error_valist(tag, details, args);
+    va_end(args);
+
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_io_error(int errno_, const char *details, ...)
+{
+    struct ovsdb_error *error;
+    va_list args;
+
+    va_start(args, details);
+    error = ovsdb_error_valist("I/O error", details, args);
+    va_end(args);
+
+    error->errno_ = errno_;
+
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_syntax_error(const struct json *json, const char *tag,
+                   const char *details, ...)
+{
+    struct ovsdb_error *error;
+    va_list args;
+
+    va_start(args, details);
+    error = ovsdb_error_valist(tag ? tag : "syntax error", details, args);
+    va_end(args);
+
+    if (json) {
+        /* XXX this is much too much information in some cases */
+        error->syntax = json_to_string(json, 0);
+    }
+
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_wrap_error(struct ovsdb_error *error, const char *details, ...)
+{
+    va_list args;
+    char *msg;
+
+    va_start(args, details);
+    msg = xvasprintf(details, args);
+    va_end(args);
+
+    if (error->details) {
+        char *new = xasprintf("%s: %s", msg, error->details);
+        free(error->details);
+        error->details = new;
+        free(msg);
+    } else {
+        error->details = msg;
+    }
+
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_internal_error(const char *file, int line, const char *details, ...)
+{
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    struct backtrace backtrace;
+    struct ovsdb_error *error;
+    va_list args;
+
+    ds_put_format(&ds, "%s:%d:", file, line);
+
+    if (details) {
+        ds_put_char(&ds, ' ');
+        va_start(args, details);
+        ds_put_format_valist(&ds, details, args);
+        va_end(args);
+    }
+
+    backtrace_capture(&backtrace);
+    if (backtrace.n_frames) {
+        int i;
+
+        ds_put_cstr(&ds, " (backtrace:");
+        for (i = 0; i < backtrace.n_frames; i++) {
+            ds_put_format(&ds, " 0x%08"PRIxPTR, backtrace.frames[i]);
+        }
+        ds_put_char(&ds, ')');
+    }
+
+    ds_put_format(&ds, " (%s %s%s)", program_name, VERSION, BUILDNR);
+
+    error = ovsdb_error("internal error", "%s", ds_cstr(&ds));
+
+    ds_destroy(&ds);
+
+    return error;
+}
+
+void
+ovsdb_error_destroy(struct ovsdb_error *error)
+{
+    if (error) {
+        free(error->details);
+        free(error->syntax);
+        free(error);
+    }
+}
+
+struct ovsdb_error *
+ovsdb_error_clone(const struct ovsdb_error *old)
+{
+    if (old) {
+        struct ovsdb_error *new = xmalloc(sizeof *new);
+        new->tag = old->tag;
+        new->details = old->details ? xstrdup(old->details) : NULL;
+        new->syntax = old->syntax ? xstrdup(old->syntax) : NULL;
+        new->errno_ = old->errno_;
+        return new;
+    } else {
+        return NULL;
+    }
+}
+
+static const char *
+ovsdb_errno_string(int error)
+{
+    return error == EOF ? "unexpected end of file" : strerror(error);
+}
+
+struct json *
+ovsdb_error_to_json(const struct ovsdb_error *error)
+{
+    struct json *json = json_object_create();
+    json_object_put_string(json, "error", error->tag);
+    if (error->details) {
+        json_object_put_string(json, "details", error->details);
+    }
+    if (error->syntax) {
+        json_object_put_string(json, "syntax", error->syntax);
+    }
+    if (error->errno_) {
+        json_object_put_string(json, "io-error",
+                               ovsdb_errno_string(error->errno_));
+    }
+    return json;
+}
+
+char *
+ovsdb_error_to_string(const struct ovsdb_error *error)
+{
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    if (error->syntax) {
+        ds_put_format(&ds, "syntax \"%s\": ", error->syntax);
+    }
+    ds_put_cstr(&ds, error->tag);
+    if (error->details) {
+        ds_put_format(&ds, ": %s", error->details);
+    }
+    if (error->errno_) {
+        ds_put_format(&ds, " (%s)", ovsdb_errno_string(error->errno_));
+    }
+    return ds_steal_cstr(&ds);
+}
+
+const char *
+ovsdb_error_get_tag(const struct ovsdb_error *error)
+{
+    return error->tag;
+}
diff --git a/lib/ovsdb-error.h b/lib/ovsdb-error.h
new file mode 100644 (file)
index 0000000..7e2523e
--- /dev/null
@@ -0,0 +1,53 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_ERROR_H
+#define OVSDB_ERROR_H 1
+
+#include "compiler.h"
+
+struct json;
+
+struct ovsdb_error *ovsdb_error(const char *tag, const char *details, ...)
+    PRINTF_FORMAT(2, 3)
+    WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_io_error(int error, const char *details, ...)
+    PRINTF_FORMAT(2, 3)
+    WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_syntax_error(const struct json *, const char *tag,
+                                       const char *details, ...)
+    PRINTF_FORMAT(3, 4)
+    WARN_UNUSED_RESULT;
+
+struct ovsdb_error *ovsdb_wrap_error(struct ovsdb_error *error,
+                                     const char *details, ...)
+    PRINTF_FORMAT(2, 3);
+
+struct ovsdb_error *ovsdb_internal_error(const char *file, int line,
+                                         const char *details, ...)
+    PRINTF_FORMAT(3, 4)
+    WARN_UNUSED_RESULT;
+#define OVSDB_BUG(MSG) ovsdb_internal_error(__FILE__, __LINE__, "%s", MSG)
+
+void ovsdb_error_destroy(struct ovsdb_error *);
+struct ovsdb_error *ovsdb_error_clone(const struct ovsdb_error *)
+    WARN_UNUSED_RESULT;
+
+char *ovsdb_error_to_string(const struct ovsdb_error *);
+struct json *ovsdb_error_to_json(const struct ovsdb_error *);
+
+const char *ovsdb_error_get_tag(const struct ovsdb_error *);
+
+#endif /* ovsdb-error.h */
diff --git a/lib/ovsdb-idl-provider.h b/lib/ovsdb-idl-provider.h
new file mode 100644 (file)
index 0000000..76197e8
--- /dev/null
@@ -0,0 +1,71 @@
+/* Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_IDL_PROVIDER_H
+#define OVSDB_IDL_PROVIDER_H 1
+
+#include "hmap.h"
+#include "list.h"
+#include "ovsdb-idl.h"
+#include "ovsdb-types.h"
+#include "shash.h"
+#include "uuid.h"
+
+struct ovsdb_idl_row {
+    struct hmap_node hmap_node; /* In struct ovsdb_idl_table's 'rows'. */
+    struct uuid uuid;           /* Row "_uuid" field. */
+    struct list src_arcs;       /* Forward arcs (ovsdb_idl_arc.src_node). */
+    struct list dst_arcs;       /* Backward arcs (ovsdb_idl_arc.dst_node). */
+    struct ovsdb_idl_table *table; /* Containing table. */
+    struct ovsdb_datum *fields;    /* Row data, or null if orphaned. */
+};
+
+struct ovsdb_idl_column {
+    char *name;
+    struct ovsdb_type type;
+};
+
+struct ovsdb_idl_table_class {
+    char *name;
+    const struct ovsdb_idl_column *columns;
+    size_t n_columns;
+    size_t allocation_size;
+    void (*parse)(struct ovsdb_idl_row *);
+    void (*unparse)(struct ovsdb_idl_row *);
+};
+
+struct ovsdb_idl_table {
+    const struct ovsdb_idl_table_class *class;
+    struct shash columns;    /* Contains "const struct ovsdb_idl_column *"s. */
+    struct hmap rows;        /* Contains "struct ovsdb_idl_row"s. */
+    struct ovsdb_idl *idl;   /* Containing idl. */
+};
+
+struct ovsdb_idl_class {
+    const struct ovsdb_idl_table_class *tables;
+    size_t n_tables;
+};
+
+struct ovsdb_idl_row *ovsdb_idl_get_row_arc(
+    struct ovsdb_idl_row *src,
+    struct ovsdb_idl_table_class *dst_table,
+    const struct uuid *dst_uuid);
+
+struct ovsdb_idl_row *ovsdb_idl_first_row(
+    const struct ovsdb_idl *, const struct ovsdb_idl_table_class *);
+
+struct ovsdb_idl_row *ovsdb_idl_next_row(const struct ovsdb_idl_row *);
+
+#endif /* ovsdb-idl-provider.h */
diff --git a/lib/ovsdb-idl.c b/lib/ovsdb-idl.c
new file mode 100644 (file)
index 0000000..26b5942
--- /dev/null
@@ -0,0 +1,691 @@
+/* Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb-idl.h"
+
+#include <assert.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#include "json.h"
+#include "jsonrpc.h"
+#include "ovsdb-data.h"
+#include "ovsdb-error.h"
+#include "ovsdb-idl-provider.h"
+#include "shash.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_ovsdb_idl
+#include "vlog.h"
+
+/* An arc from one idl_row to another.  When row A contains a UUID that
+ * references row B, this is represented by an arc from A (the source) to B
+ * (the destination).
+ *
+ * Arcs from a row to itself are omitted, that is, src and dst are always
+ * different.
+ *
+ * Arcs are never duplicated, that is, even if there are multiple references
+ * from A to B, there is only a single arc from A to B.
+ *
+ * Arcs are directed: an arc from A to B is the converse of an an arc from B to
+ * A.  Both an arc and its converse may both be present, if each row refers
+ * to the other circularly.
+ *
+ * The source and destination row may be in the same table or in different
+ * tables.
+ */
+struct ovsdb_idl_arc {
+    struct list src_node;       /* In src->src_arcs list. */
+    struct list dst_node;       /* In dst->dst_arcs list. */
+    struct ovsdb_idl_row *src;  /* Source row. */
+    struct ovsdb_idl_row *dst;  /* Destination row. */
+};
+
+struct ovsdb_idl {
+    struct jsonrpc_session *session;
+    struct shash tables;
+    struct json *monitor_request_id;
+    unsigned int last_monitor_request_seqno;
+    unsigned int change_seqno;
+};
+
+static struct vlog_rate_limit syntax_rl = VLOG_RATE_LIMIT_INIT(1, 5);
+static struct vlog_rate_limit semantic_rl = VLOG_RATE_LIMIT_INIT(1, 5);
+
+static void ovsdb_idl_clear(struct ovsdb_idl *);
+static void ovsdb_idl_send_monitor_request(struct ovsdb_idl *);
+static void ovsdb_idl_parse_update(struct ovsdb_idl *, const struct json *);
+static struct ovsdb_error *ovsdb_idl_parse_update__(struct ovsdb_idl *,
+                                                    const struct json *);
+static void ovsdb_idl_process_update(struct ovsdb_idl_table *,
+                                     const struct uuid *,
+                                     const struct json *old,
+                                     const struct json *new);
+static void ovsdb_idl_insert_row(struct ovsdb_idl_row *, const struct json *);
+static void ovsdb_idl_delete_row(struct ovsdb_idl_row *);
+static void ovsdb_idl_modify_row(struct ovsdb_idl_row *, const struct json *);
+
+static bool ovsdb_idl_row_is_orphan(const struct ovsdb_idl_row *);
+static struct ovsdb_idl_row *ovsdb_idl_row_create(struct ovsdb_idl_table *,
+                                                  const struct uuid *);
+static void ovsdb_idl_row_destroy(struct ovsdb_idl_row *);
+
+static void ovsdb_idl_row_clear_fields(struct ovsdb_idl_row *);
+
+struct ovsdb_idl *
+ovsdb_idl_create(const char *remote, const struct ovsdb_idl_class *class)
+{
+    struct ovsdb_idl *idl;
+    size_t i;
+
+    idl = xzalloc(sizeof *idl);
+    idl->session = jsonrpc_session_open(remote);
+    shash_init(&idl->tables);
+    for (i = 0; i < class->n_tables; i++) {
+        const struct ovsdb_idl_table_class *tc = &class->tables[i];
+        struct ovsdb_idl_table *table;
+        size_t j;
+
+        table = xmalloc(sizeof *table);
+        assert(!shash_find(&idl->tables, tc->name));
+        shash_add(&idl->tables, tc->name, table);
+        table->class = tc;
+        shash_init(&table->columns);
+        for (j = 0; j < tc->n_columns; j++) {
+            const struct ovsdb_idl_column *column = &tc->columns[j];
+
+            assert(!shash_find(&table->columns, column->name));
+            shash_add(&table->columns, column->name, column);
+        }
+        hmap_init(&table->rows);
+        table->idl = idl;
+    }
+    idl->last_monitor_request_seqno = UINT_MAX;
+
+    return idl;
+}
+
+void
+ovsdb_idl_destroy(struct ovsdb_idl *idl)
+{
+    if (idl) {
+        struct shash_node *node;
+
+        ovsdb_idl_clear(idl);
+        jsonrpc_session_close(idl->session);
+
+        SHASH_FOR_EACH (node, &idl->tables) {
+            struct ovsdb_idl_table *table = node->data;
+
+            shash_destroy(&table->columns);
+            hmap_destroy(&table->rows);
+        }
+        shash_destroy(&idl->tables);
+        json_destroy(idl->monitor_request_id);
+        free(idl);
+    }
+}
+
+static void
+ovsdb_idl_clear(struct ovsdb_idl *idl)
+{
+    struct shash_node *node;
+    bool changed = false;
+
+    SHASH_FOR_EACH (node, &idl->tables) {
+        struct ovsdb_idl_table *table = node->data;
+        struct ovsdb_idl_row *row, *next_row;
+
+        if (hmap_is_empty(&table->rows)) {
+            continue;
+        }
+
+        changed = true;
+        HMAP_FOR_EACH_SAFE (row, next_row, struct ovsdb_idl_row, hmap_node,
+                            &table->rows) {
+            struct ovsdb_idl_arc *arc, *next_arc;
+
+            if (!ovsdb_idl_row_is_orphan(row)) {
+                (row->table->class->unparse)(row);
+                ovsdb_idl_row_clear_fields(row);
+            }
+            hmap_remove(&table->rows, &row->hmap_node);
+            LIST_FOR_EACH_SAFE (arc, next_arc, struct ovsdb_idl_arc, src_node,
+                                &row->src_arcs) {
+                free(arc);
+            }
+            /* No need to do anything with dst_arcs: some node has those arcs
+             * as forward arcs and will destroy them itself. */
+
+            free(row);
+        }
+    }
+
+    if (changed) {
+        idl->change_seqno++;
+    }
+}
+
+void
+ovsdb_idl_run(struct ovsdb_idl *idl)
+{
+    int i;
+
+    jsonrpc_session_run(idl->session);
+    for (i = 0; jsonrpc_session_is_connected(idl->session) && i < 50; i++) {
+        struct jsonrpc_msg *msg, *reply;
+        unsigned int seqno;
+
+        seqno = jsonrpc_session_get_seqno(idl->session);
+        if (idl->last_monitor_request_seqno != seqno) {
+            idl->last_monitor_request_seqno = seqno;
+            ovsdb_idl_send_monitor_request(idl);
+            break;
+        }
+
+        msg = jsonrpc_session_recv(idl->session);
+        if (!msg) {
+            break;
+        }
+
+        reply = NULL;
+        if (msg->type == JSONRPC_REQUEST && !strcmp(msg->method, "echo")) {
+            reply = jsonrpc_create_reply(json_clone(msg->params), msg->id);
+        } else if (msg->type == JSONRPC_NOTIFY
+                   && !strcmp(msg->method, "update")
+                   && msg->params->type == JSON_ARRAY
+                   && msg->params->u.array.n == 2
+                   && msg->params->u.array.elems[0]->type == JSON_NULL) {
+            ovsdb_idl_parse_update(idl, msg->params->u.array.elems[1]);
+        } else if (msg->type == JSONRPC_REPLY
+                   && idl->monitor_request_id
+                   && json_equal(idl->monitor_request_id, msg->id)) {
+            json_destroy(idl->monitor_request_id);
+            idl->monitor_request_id = NULL;
+            ovsdb_idl_clear(idl);
+            ovsdb_idl_parse_update(idl, msg->result);
+        } else if (msg->type == JSONRPC_REPLY
+                   && msg->id && msg->id->type == JSON_STRING
+                   && !strcmp(msg->id->u.string, "echo")) {
+            /* It's a reply to our echo request.  Ignore it. */
+        } else {
+            VLOG_WARN("%s: received unexpected %s message",
+                      jsonrpc_session_get_name(idl->session),
+                      jsonrpc_msg_type_to_string(msg->type));
+            jsonrpc_session_force_reconnect(idl->session);
+        }
+        if (reply) {
+            jsonrpc_session_send(idl->session, reply);
+        }
+        jsonrpc_msg_destroy(msg);
+    }
+}
+
+void
+ovsdb_idl_wait(struct ovsdb_idl *idl)
+{
+    jsonrpc_session_wait(idl->session);
+    jsonrpc_session_recv_wait(idl->session);
+}
+
+unsigned int
+ovsdb_idl_get_seqno(const struct ovsdb_idl *idl)
+{
+    return idl->change_seqno;
+}
+
+void
+ovsdb_idl_force_reconnect(struct ovsdb_idl *idl)
+{
+    jsonrpc_session_force_reconnect(idl->session);
+}
+\f
+static void
+ovsdb_idl_send_monitor_request(struct ovsdb_idl *idl)
+{
+    struct json *monitor_requests;
+    const struct shash_node *node;
+    struct jsonrpc_msg *msg;
+
+    monitor_requests = json_object_create();
+    SHASH_FOR_EACH (node, &idl->tables) {
+        const struct ovsdb_idl_table *table = node->data;
+        const struct ovsdb_idl_table_class *tc = table->class;
+        struct json *monitor_request, *columns;
+        size_t i;
+
+        monitor_request = json_object_create();
+        columns = json_array_create_empty();
+        for (i = 0; i < tc->n_columns; i++) {
+            const struct ovsdb_idl_column *column = &tc->columns[i];
+            json_array_add(columns, json_string_create(column->name));
+        }
+        json_object_put(monitor_request, "columns", columns);
+        json_object_put(monitor_requests, tc->name, monitor_request);
+    }
+
+    json_destroy(idl->monitor_request_id);
+    msg = jsonrpc_create_request(
+        "monitor", json_array_create_2(json_null_create(), monitor_requests),
+        &idl->monitor_request_id);
+    jsonrpc_session_send(idl->session, msg);
+}
+
+static void
+ovsdb_idl_parse_update(struct ovsdb_idl *idl, const struct json *table_updates)
+{
+    struct ovsdb_error *error;
+
+    idl->change_seqno++;
+
+    error = ovsdb_idl_parse_update__(idl, table_updates);
+    if (error) {
+        if (!VLOG_DROP_WARN(&syntax_rl)) {
+            char *s = ovsdb_error_to_string(error);
+            VLOG_WARN_RL(&syntax_rl, "%s", s);
+            free(s);
+        }
+        ovsdb_error_destroy(error);
+    }
+}
+
+static struct ovsdb_error *
+ovsdb_idl_parse_update__(struct ovsdb_idl *idl,
+                         const struct json *table_updates)
+{
+    const struct shash_node *tables_node;
+
+    if (table_updates->type != JSON_OBJECT) {
+        return ovsdb_syntax_error(table_updates, NULL,
+                                  "<table-updates> is not an object");
+    }
+    SHASH_FOR_EACH (tables_node, json_object(table_updates)) {
+        const struct json *table_update = tables_node->data;
+        const struct shash_node *table_node;
+        struct ovsdb_idl_table *table;
+
+        table = shash_find_data(&idl->tables, tables_node->name);
+        if (!table) {
+            return ovsdb_syntax_error(
+                table_updates, NULL,
+                "<table-updates> includes unknown table \"%s\"",
+                tables_node->name);
+        }
+
+        if (table_update->type != JSON_OBJECT) {
+            return ovsdb_syntax_error(table_update, NULL,
+                                      "<table-update> for table \"%s\" is "
+                                      "not an object", table->class->name);
+        }
+        SHASH_FOR_EACH (table_node, json_object(table_update)) {
+            const struct json *row_update = table_node->data;
+            const struct json *old_json, *new_json;
+            struct uuid uuid;
+
+            if (!uuid_from_string(&uuid, table_node->name)) {
+                return ovsdb_syntax_error(table_update, NULL,
+                                          "<table-update> for table \"%s\" "
+                                          "contains bad UUID "
+                                          "\"%s\" as member name",
+                                          table->class->name,
+                                          table_node->name);
+            }
+            if (row_update->type != JSON_OBJECT) {
+                return ovsdb_syntax_error(row_update, NULL,
+                                          "<table-update> for table \"%s\" "
+                                          "contains <row-update> for %s that "
+                                          "is not an object",
+                                          table->class->name,
+                                          table_node->name);
+            }
+
+            old_json = shash_find_data(json_object(row_update), "old");
+            new_json = shash_find_data(json_object(row_update), "new");
+            if (old_json && old_json->type != JSON_OBJECT) {
+                return ovsdb_syntax_error(old_json, NULL,
+                                          "\"old\" <row> is not object");
+            } else if (new_json && new_json->type != JSON_OBJECT) {
+                return ovsdb_syntax_error(new_json, NULL,
+                                          "\"new\" <row> is not object");
+            } else if ((old_json != NULL) + (new_json != NULL)
+                       != shash_count(json_object(row_update))) {
+                return ovsdb_syntax_error(row_update, NULL,
+                                          "<row-update> contains unexpected "
+                                          "member");
+            } else if (!old_json && !new_json) {
+                return ovsdb_syntax_error(row_update, NULL,
+                                          "<row-update> missing \"old\" "
+                                          "and \"new\" members");
+            }
+
+            ovsdb_idl_process_update(table, &uuid, old_json, new_json);
+        }
+    }
+
+    return NULL;
+}
+
+static struct ovsdb_idl_row *
+ovsdb_idl_get_row(struct ovsdb_idl_table *table, const struct uuid *uuid)
+{
+    struct ovsdb_idl_row *row;
+
+    HMAP_FOR_EACH_WITH_HASH (row, struct ovsdb_idl_row, hmap_node,
+                             uuid_hash(uuid), &table->rows) {
+        if (uuid_equals(&row->uuid, uuid)) {
+            return row;
+        }
+    }
+    return NULL;
+}
+
+static void
+ovsdb_idl_process_update(struct ovsdb_idl_table *table,
+                         const struct uuid *uuid, const struct json *old,
+                         const struct json *new)
+{
+    struct ovsdb_idl_row *row;
+
+    row = ovsdb_idl_get_row(table, uuid);
+    if (!new) {
+        /* Delete row. */
+        if (row && !ovsdb_idl_row_is_orphan(row)) {
+            /* XXX perhaps we should check the 'old' values? */
+            ovsdb_idl_delete_row(row);
+        } else {
+            VLOG_WARN_RL(&semantic_rl, "cannot delete missing row "UUID_FMT" "
+                         "from table %s",
+                         UUID_ARGS(uuid), table->class->name);
+        }
+    } else if (!old) {
+        /* Insert row. */
+        if (!row) {
+            ovsdb_idl_insert_row(ovsdb_idl_row_create(table, uuid), new);
+        } else if (ovsdb_idl_row_is_orphan(row)) {
+            ovsdb_idl_insert_row(row, new);
+        } else {
+            VLOG_WARN_RL(&semantic_rl, "cannot add existing row "UUID_FMT" to "
+                         "table %s", UUID_ARGS(uuid), table->class->name);
+            ovsdb_idl_modify_row(row, new);
+        }
+    } else {
+        /* Modify row. */
+        if (row) {
+            /* XXX perhaps we should check the 'old' values? */
+            if (!ovsdb_idl_row_is_orphan(row)) {
+                ovsdb_idl_modify_row(row, new);
+            } else {
+                VLOG_WARN_RL(&semantic_rl, "cannot modify missing but "
+                             "referenced row "UUID_FMT" in table %s",
+                             UUID_ARGS(uuid), table->class->name);
+                ovsdb_idl_insert_row(row, new);
+            }
+        } else {
+            VLOG_WARN_RL(&semantic_rl, "cannot modify missing row "UUID_FMT" "
+                         "in table %s", UUID_ARGS(uuid), table->class->name);
+            ovsdb_idl_insert_row(ovsdb_idl_row_create(table, uuid), new);
+        }
+    }
+}
+
+static void
+ovsdb_idl_row_update(struct ovsdb_idl_row *row, const struct json *row_json)
+{
+    struct ovsdb_idl_table *table = row->table;
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, json_object(row_json)) {
+        const char *column_name = node->name;
+        const struct ovsdb_idl_column *column;
+        struct ovsdb_datum datum;
+        struct ovsdb_error *error;
+
+        column = shash_find_data(&table->columns, column_name);
+        if (!column) {
+            VLOG_WARN_RL(&syntax_rl, "unknown column %s updating row "UUID_FMT,
+                         column_name, UUID_ARGS(&row->uuid));
+            continue;
+        }
+
+        error = ovsdb_datum_from_json(&datum, &column->type, node->data, NULL);
+        if (!error) {
+            ovsdb_datum_swap(&row->fields[column - table->class->columns],
+                             &datum);
+            ovsdb_datum_destroy(&datum, &column->type);
+        } else {
+            char *s = ovsdb_error_to_string(error);
+            VLOG_WARN_RL(&syntax_rl, "error parsing column %s in row "UUID_FMT
+                         " in table %s: %s", column_name,
+                         UUID_ARGS(&row->uuid), table->class->name, s);
+            free(s);
+            ovsdb_error_destroy(error);
+        }
+    }
+}
+
+static bool
+ovsdb_idl_row_is_orphan(const struct ovsdb_idl_row *row)
+{
+    return !row->fields;
+}
+
+static void
+ovsdb_idl_row_clear_fields(struct ovsdb_idl_row *row)
+{
+    if (!ovsdb_idl_row_is_orphan(row)) {
+        const struct ovsdb_idl_table_class *class = row->table->class;
+        size_t i;
+
+        for (i = 0; i < class->n_columns; i++) {
+            ovsdb_datum_destroy(&row->fields[i], &class->columns[i].type);
+        }
+        row->fields = NULL;
+    }
+}
+
+static void
+ovsdb_idl_row_clear_arcs(struct ovsdb_idl_row *row, bool destroy_dsts)
+{
+    struct ovsdb_idl_arc *arc, *next;
+
+    /* Delete all forward arcs.  If 'destroy_dsts', destroy any orphaned rows
+     * that this causes to be unreferenced. */
+    LIST_FOR_EACH_SAFE (arc, next, struct ovsdb_idl_arc, src_node,
+                        &row->src_arcs) {
+        list_remove(&arc->dst_node);
+        if (destroy_dsts
+            && ovsdb_idl_row_is_orphan(arc->dst)
+            && list_is_empty(&arc->dst->dst_arcs)) {
+            ovsdb_idl_row_destroy(arc->dst);
+        }
+        free(arc);
+    }
+    list_init(&row->src_arcs);
+}
+
+/* Force nodes that reference 'row' to reparse. */
+static void
+ovsdb_idl_row_reparse_backrefs(struct ovsdb_idl_row *row, bool destroy_dsts)
+{
+    struct ovsdb_idl_arc *arc, *next;
+
+    /* This is trickier than it looks.  ovsdb_idl_row_clear_arcs() will destroy
+     * 'arc', so we need to use the "safe" variant of list traversal.  However,
+     * calling ref->table->class->parse will add an arc equivalent to 'arc' to
+     * row->arcs.  That could be a problem for traversal, but it adds it at the
+     * beginning of the list to prevent us from stumbling upon it again.
+     *
+     * (If duplicate arcs were possible then we would need to make sure that
+     * 'next' didn't also point into 'arc''s destination, but we forbid
+     * duplicate arcs.) */
+    LIST_FOR_EACH_SAFE (arc, next, struct ovsdb_idl_arc, dst_node,
+                        &row->dst_arcs) {
+        struct ovsdb_idl_row *ref = arc->src;
+
+        (ref->table->class->unparse)(ref);
+        ovsdb_idl_row_clear_arcs(ref, destroy_dsts);
+        (ref->table->class->parse)(ref);
+    }
+}
+
+static struct ovsdb_idl_row *
+ovsdb_idl_row_create(struct ovsdb_idl_table *table, const struct uuid *uuid)
+{
+    struct ovsdb_idl_row *row = xmalloc(table->class->allocation_size);
+    hmap_insert(&table->rows, &row->hmap_node, uuid_hash(uuid));
+    row->uuid = *uuid;
+    list_init(&row->src_arcs);
+    list_init(&row->dst_arcs);
+    row->table = table;
+    row->fields = NULL;
+    return row;
+}
+
+static void
+ovsdb_idl_row_destroy(struct ovsdb_idl_row *row)
+{
+    if (row) {
+        ovsdb_idl_row_clear_fields(row);
+        hmap_remove(&row->table->rows, &row->hmap_node);
+        free(row);
+    }
+}
+
+static void
+ovsdb_idl_insert_row(struct ovsdb_idl_row *row, const struct json *row_json)
+{
+    const struct ovsdb_idl_table_class *class = row->table->class;
+    size_t i;
+
+    assert(!row->fields);
+    row->fields = xmalloc(class->n_columns * sizeof *row->fields);
+    for (i = 0; i < class->n_columns; i++) {
+        ovsdb_datum_init_default(&row->fields[i], &class->columns[i].type);
+    }
+    ovsdb_idl_row_update(row, row_json);
+    (class->parse)(row);
+
+    ovsdb_idl_row_reparse_backrefs(row, false);
+}
+
+static void
+ovsdb_idl_delete_row(struct ovsdb_idl_row *row)
+{
+    (row->table->class->unparse)(row);
+    ovsdb_idl_row_clear_arcs(row, true);
+    ovsdb_idl_row_clear_fields(row);
+    if (list_is_empty(&row->dst_arcs)) {
+        ovsdb_idl_row_destroy(row);
+    } else {
+        ovsdb_idl_row_reparse_backrefs(row, true);
+    }
+}
+
+static void
+ovsdb_idl_modify_row(struct ovsdb_idl_row *row, const struct json *row_json)
+{
+    (row->table->class->unparse)(row);
+    ovsdb_idl_row_clear_arcs(row, true);
+    ovsdb_idl_row_update(row, row_json);
+    (row->table->class->parse)(row);
+}
+
+static bool
+may_add_arc(const struct ovsdb_idl_row *src, const struct ovsdb_idl_row *dst)
+{
+    const struct ovsdb_idl_arc *arc;
+
+    /* No self-arcs. */
+    if (src == dst) {
+        return false;
+    }
+
+    /* No duplicate arcs.
+     *
+     * We only need to test whether the first arc in dst->dst_arcs originates
+     * at 'src', since we add all of the arcs from a given source in a clump
+     * (in a single call to a row's ->parse function) and new arcs are always
+     * added at the front of the dst_arcs list. */
+    if (list_is_empty(&dst->dst_arcs)) {
+        return true;
+    }
+    arc = CONTAINER_OF(dst->dst_arcs.next, struct ovsdb_idl_arc, dst_node);
+    return arc->src != src;
+}
+
+struct ovsdb_idl_row *
+ovsdb_idl_get_row_arc(struct ovsdb_idl_row *src,
+                      struct ovsdb_idl_table_class *dst_table_class,
+                      const struct uuid *dst_uuid)
+{
+    struct ovsdb_idl *idl = src->table->idl;
+    struct ovsdb_idl_arc *arc;
+    struct ovsdb_idl_row *dst;
+
+    dst = ovsdb_idl_get_row(src->table, dst_uuid);
+    if (!dst) {
+        struct ovsdb_idl_table *dst_table;
+        dst_table = shash_find_data(&idl->tables, dst_table_class->name);
+        dst = ovsdb_idl_row_create(dst_table, dst_uuid);
+    }
+
+    /* Add a new arc, if it wouldn't be a self-arc or a duplicate arc. */
+    if (may_add_arc(src, dst)) {
+        /* The arc *must* be added at the front of the dst_arcs list.  See
+         * ovsdb_idl_row_reparse_backrefs() for details. */
+        arc = xmalloc(sizeof *arc);
+        list_push_front(&src->src_arcs, &arc->src_node);
+        list_push_front(&dst->dst_arcs, &arc->dst_node);
+        arc->src = src;
+        arc->dst = dst;
+    }
+
+    return !ovsdb_idl_row_is_orphan(dst) ? dst : NULL;
+}
+
+static struct ovsdb_idl_row *
+next_real_row(struct ovsdb_idl_table *table, struct hmap_node *node)
+{
+    for (; node; node = hmap_next(&table->rows, node)) {
+        struct ovsdb_idl_row *row;
+
+        row = CONTAINER_OF(node, struct ovsdb_idl_row, hmap_node);
+        if (!ovsdb_idl_row_is_orphan(row)) {
+            return row;
+        }
+    }
+    return NULL;
+}
+
+struct ovsdb_idl_row *
+ovsdb_idl_first_row(const struct ovsdb_idl *idl,
+                    const struct ovsdb_idl_table_class *table_class)
+{
+    struct ovsdb_idl_table *table;
+
+    table = shash_find_data(&idl->tables, table_class->name);
+    return next_real_row(table, hmap_first(&table->rows));
+}
+
+struct ovsdb_idl_row *
+ovsdb_idl_next_row(const struct ovsdb_idl_row *row)
+{
+    struct ovsdb_idl_table *table = row->table;
+
+    return next_real_row(table, hmap_next(&table->rows, &row->hmap_node));
+}
diff --git a/lib/ovsdb-idl.h b/lib/ovsdb-idl.h
new file mode 100644 (file)
index 0000000..7e95bb1
--- /dev/null
@@ -0,0 +1,31 @@
+/* Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_IDL_H
+#define OVSDB_IDL_H 1
+
+struct ovsdb_idl_class;
+
+struct ovsdb_idl *ovsdb_idl_create(const char *remote,
+                                   const struct ovsdb_idl_class *);
+void ovsdb_idl_destroy(struct ovsdb_idl *);
+
+void ovsdb_idl_run(struct ovsdb_idl *);
+void ovsdb_idl_wait(struct ovsdb_idl *);
+
+unsigned int ovsdb_idl_get_seqno(const struct ovsdb_idl *);
+void ovsdb_idl_force_reconnect(struct ovsdb_idl *);
+
+#endif /* ovsdb-idl.h */
diff --git a/lib/ovsdb-parser.c b/lib/ovsdb-parser.c
new file mode 100644 (file)
index 0000000..2a4c3d9
--- /dev/null
@@ -0,0 +1,166 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb-parser.h"
+
+#include <ctype.h>
+#include <stdarg.h>
+
+#include "ovsdb-error.h"
+
+void
+ovsdb_parser_init(struct ovsdb_parser *parser, const struct json *json,
+                  const char *name, ...)
+{
+    va_list args;
+
+    va_start(args, name);
+    parser->name = xvasprintf(name, args);
+    va_end(args);
+
+    svec_init(&parser->used);
+    parser->error = NULL;
+
+    parser->json = (json && json->type == JSON_OBJECT ? json : NULL);
+    if (!parser->json) {
+        ovsdb_parser_raise_error(parser, "Object expected.");
+    }
+}
+
+bool
+ovsdb_parser_is_id(const char *string)
+{
+    unsigned char c;
+
+    c = *string;
+    if (!isalpha(c) && c != '_') {
+        return false;
+    }
+
+    for (;;) {
+        c = *++string;
+        if (c == '\0') {
+            return true;
+        } else if (!isalpha(c) && !isdigit(c) && c != '_') {
+            return false;
+        }
+    }
+}
+
+const struct json *
+ovsdb_parser_member(struct ovsdb_parser *parser, const char *name,
+                    enum ovsdb_parser_types types)
+{
+    struct json *value;
+
+    if (!parser->json) {
+        return NULL;
+    }
+
+    value = shash_find_data(json_object(parser->json), name);
+    if (!value) {
+        if (!(types & OP_OPTIONAL)) {
+            ovsdb_parser_raise_error(parser,
+                                     "Required '%s' member is missing.", name);
+        }
+        return NULL;
+    }
+
+    if ((value->type >= 0 && value->type < JSON_N_TYPES
+         && types & (1u << value->type))
+        || (types & OP_ID && value->type == JSON_STRING
+            && ovsdb_parser_is_id(value->u.string)))
+    {
+        svec_add(&parser->used, name);
+        return value;
+    } else {
+        ovsdb_parser_raise_error(parser, "Type mismatch for member '%s'.",
+                                 name);
+        return NULL;
+    }
+}
+
+void
+ovsdb_parser_raise_error(struct ovsdb_parser *parser, const char *format, ...)
+{
+    if (!parser->error) {
+        struct ovsdb_error *error;
+        va_list args;
+        char *message;
+
+        va_start(args, format);
+        message = xvasprintf(format, args);
+        va_end(args);
+
+        error = ovsdb_syntax_error(parser->json, NULL, "Parsing %s failed: %s",
+                                   parser->name, message);
+        free(message);
+
+        parser->error = error;
+    }
+}
+
+struct ovsdb_error *
+ovsdb_parser_get_error(const struct ovsdb_parser *parser)
+{
+    return parser->error ? ovsdb_error_clone(parser->error) : NULL;
+}
+
+bool
+ovsdb_parser_has_error(const struct ovsdb_parser *parser)
+{
+    return parser->error != NULL;
+}
+
+struct ovsdb_error *
+ovsdb_parser_finish(struct ovsdb_parser *parser)
+{
+    if (!parser->error) {
+        const struct shash *object = json_object(parser->json);
+        size_t n_unused;
+
+        /* XXX this method of detecting unused members can be made cheaper */
+        svec_sort_unique(&parser->used);
+        n_unused = shash_count(object) - parser->used.n;
+        if (n_unused) {
+            struct shash_node *node;
+
+            SHASH_FOR_EACH (node, object) {
+                if (!svec_contains(&parser->used, node->name)) {
+                    if (n_unused > 1) {
+                        ovsdb_parser_raise_error(
+                            parser,
+                            "Member '%s' and %zu other member%s "
+                            "are present but not allowed here.",
+                            node->name, n_unused - 1, n_unused > 2 ? "s" : "");
+                    } else {
+                        ovsdb_parser_raise_error(
+                            parser,
+                            "Member '%s' is present but not allowed here.",
+                            node->name);
+                    }
+                    break;
+                }
+            }
+        }
+    }
+
+    free(parser->name);
+    svec_destroy(&parser->used);
+
+    return parser->error;
+}
diff --git a/lib/ovsdb-parser.h b/lib/ovsdb-parser.h
new file mode 100644 (file)
index 0000000..6efa0a7
--- /dev/null
@@ -0,0 +1,76 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_PARSER_H
+#define OVSDB_PARSER_H 1
+
+#include <stdbool.h>
+#include "compiler.h"
+#include "json.h"
+#include "svec.h"
+#include "util.h"
+
+struct ovsdb_parser {
+    char *name;                 /* Used only in error messages. */
+    struct svec used;           /* Already-parsed names from 'object'. */
+    const struct json *json;    /* JSON object being parsed. */
+    struct ovsdb_error *error;  /* Error signaled, if any. */
+};
+
+/* Check that the JSON types make the bitwise tricks below work OK. */
+BUILD_ASSERT_DECL(JSON_NULL >= 0 && JSON_NULL < 10);
+BUILD_ASSERT_DECL(JSON_FALSE >= 0 && JSON_FALSE < 10);
+BUILD_ASSERT_DECL(JSON_TRUE >= 0 && JSON_TRUE < 10);
+BUILD_ASSERT_DECL(JSON_OBJECT >= 0 && JSON_OBJECT < 10);
+BUILD_ASSERT_DECL(JSON_ARRAY >= 0 && JSON_ARRAY < 10);
+BUILD_ASSERT_DECL(JSON_INTEGER >= 0 && JSON_INTEGER < 10);
+BUILD_ASSERT_DECL(JSON_REAL >= 0 && JSON_REAL < 10);
+BUILD_ASSERT_DECL(JSON_STRING >= 0 && JSON_STRING < 10);
+BUILD_ASSERT_DECL(JSON_N_TYPES == 8);
+
+enum ovsdb_parser_types {
+    OP_NULL = 1 << JSON_NULL,             /* null */
+    OP_FALSE = 1 << JSON_FALSE,           /* false */
+    OP_TRUE = 1 << JSON_TRUE,             /* true */
+    OP_OBJECT = 1 << JSON_OBJECT,         /* {"a": b, "c": d, ...} */
+    OP_ARRAY = 1 << JSON_ARRAY,           /* [1, 2, 3, ...] */
+    OP_INTEGER = 1 << JSON_INTEGER,       /* 123. */
+    OP_NONINTEGER = 1 << JSON_REAL,       /* 123.456. */
+    OP_STRING = 1 << JSON_STRING,         /* "..." */
+
+    OP_BOOLEAN = OP_FALSE | OP_TRUE,
+    OP_NUMBER = OP_INTEGER | OP_NONINTEGER,
+
+    OP_ID = 1 << JSON_N_TYPES,            /* "[_a-zA-Z][_a-zA-Z0-9]*" */
+    OP_OPTIONAL = 1 << (JSON_N_TYPES + 1) /* no value at all */
+};
+
+void ovsdb_parser_init(struct ovsdb_parser *, const struct json *,
+                       const char *name, ...)
+    PRINTF_FORMAT(3, 4);
+const struct json *ovsdb_parser_member(struct ovsdb_parser *, const char *name,
+                                       enum ovsdb_parser_types);
+
+void ovsdb_parser_raise_error(struct ovsdb_parser *parser,
+                              const char *format, ...)
+    PRINTF_FORMAT(2, 3);
+bool ovsdb_parser_has_error(const struct ovsdb_parser *);
+struct ovsdb_error *ovsdb_parser_get_error(const struct ovsdb_parser *);
+struct ovsdb_error *ovsdb_parser_finish(struct ovsdb_parser *)
+    WARN_UNUSED_RESULT;
+
+bool ovsdb_parser_is_id(const char *string);
+
+#endif /* ovsdb-parser.h */
diff --git a/lib/ovsdb-types.c b/lib/ovsdb-types.c
new file mode 100644 (file)
index 0000000..07982e3
--- /dev/null
@@ -0,0 +1,251 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb-types.h"
+
+#include <limits.h>
+
+#include "dynamic-string.h"
+#include "json.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+
+const struct ovsdb_type ovsdb_type_integer =
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_INTEGER);
+const struct ovsdb_type ovsdb_type_real =
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_REAL);
+const struct ovsdb_type ovsdb_type_boolean =
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_BOOLEAN);
+const struct ovsdb_type ovsdb_type_string =
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_STRING);
+const struct ovsdb_type ovsdb_type_uuid =
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_UUID);
+
+const char *
+ovsdb_atomic_type_to_string(enum ovsdb_atomic_type type)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        return "void";
+
+    case OVSDB_TYPE_INTEGER:
+        return "integer";
+
+    case OVSDB_TYPE_REAL:
+        return "real";
+
+    case OVSDB_TYPE_BOOLEAN:
+        return "boolean";
+
+    case OVSDB_TYPE_STRING:
+        return "string";
+
+    case OVSDB_TYPE_UUID:
+        return "uuid";
+
+    case OVSDB_N_TYPES:
+    default:
+        return "<invalid>";
+    }
+}
+
+struct json *
+ovsdb_atomic_type_to_json(enum ovsdb_atomic_type type)
+{
+    return json_string_create(ovsdb_atomic_type_to_string(type));
+}
+
+bool
+ovsdb_type_is_valid(const struct ovsdb_type *type)
+{
+    return (type->key_type != OVSDB_TYPE_VOID
+            && ovsdb_atomic_type_is_valid(type->key_type)
+            && ovsdb_atomic_type_is_valid(type->value_type)
+            && type->n_min <= type->n_max
+            && (type->value_type == OVSDB_TYPE_VOID
+                || ovsdb_atomic_type_is_valid_key(type->key_type)));
+}
+
+bool
+ovsdb_atomic_type_from_string(const char *string, enum ovsdb_atomic_type *type)
+{
+    if (!strcmp(string, "integer")) {
+        *type = OVSDB_TYPE_INTEGER;
+    } else if (!strcmp(string, "real")) {
+        *type = OVSDB_TYPE_REAL;
+    } else if (!strcmp(string, "boolean")) {
+        *type = OVSDB_TYPE_BOOLEAN;
+    } else if (!strcmp(string, "string")) {
+        *type = OVSDB_TYPE_STRING;
+    } else if (!strcmp(string, "uuid")) {
+        *type = OVSDB_TYPE_UUID;
+    } else {
+        return false;
+    }
+    return true;
+}
+
+struct ovsdb_error *
+ovsdb_atomic_type_from_json(enum ovsdb_atomic_type *type,
+                            const struct json *json)
+{
+    if (json->type == JSON_STRING) {
+        if (ovsdb_atomic_type_from_string(json_string(json), type)) {
+            return NULL;
+        } else {
+            *type = OVSDB_TYPE_VOID;
+            return ovsdb_syntax_error(json, NULL,
+                                      "\"%s\" is not an atomic-type",
+                                      json_string(json));
+        }
+    } else {
+        *type = OVSDB_TYPE_VOID;
+        return ovsdb_syntax_error(json, NULL, "atomic-type expected");
+    }
+}
+
+static struct ovsdb_error *
+n_from_json(const struct json *json, unsigned int *n)
+{
+    if (!json) {
+        return NULL;
+    } else if (json->type == JSON_INTEGER
+               && json->u.integer >= 0 && json->u.integer < UINT_MAX) {
+        *n = json->u.integer;
+        return NULL;
+    } else {
+        return ovsdb_syntax_error(json, NULL, "bad min or max value");
+    }
+}
+
+char *
+ovsdb_type_to_english(const struct ovsdb_type *type)
+{
+    const char *key = ovsdb_atomic_type_to_string(type->key_type);
+    const char *value = ovsdb_atomic_type_to_string(type->value_type);
+    if (ovsdb_type_is_scalar(type)) {
+        return xstrdup(key);
+    } else {
+        struct ds s = DS_EMPTY_INITIALIZER;
+        ds_put_cstr(&s, ovsdb_type_is_set(type) ? "set" : "map");
+        if (type->n_max == UINT_MAX) {
+            if (type->n_min) {
+                ds_put_format(&s, " of %u or more", type->n_min);
+            } else {
+                ds_put_cstr(&s, " of");
+            }
+        } else if (type->n_min) {
+            ds_put_format(&s, " of %u to %u", type->n_min, type->n_max);
+        } else {
+            ds_put_format(&s, " of up to %u", type->n_max);
+        }
+        if (ovsdb_type_is_set(type)) {
+            ds_put_format(&s, " %ss", key);
+        } else {
+            ds_put_format(&s, " (%s, %s) pairs", key, value);
+        }
+        return ds_cstr(&s);
+    }
+}
+
+struct ovsdb_error *
+ovsdb_type_from_json(struct ovsdb_type *type, const struct json *json)
+{
+    type->value_type = OVSDB_TYPE_VOID;
+    type->n_min = 1;
+    type->n_max = 1;
+
+    if (json->type == JSON_STRING) {
+        return ovsdb_atomic_type_from_json(&type->key_type, json);
+    } else if (json->type == JSON_OBJECT) {
+        const struct json *key, *value, *min, *max;
+        struct ovsdb_error *error;
+        struct ovsdb_parser parser;
+
+        ovsdb_parser_init(&parser, json, "ovsdb type");
+        key = ovsdb_parser_member(&parser, "key", OP_STRING);
+        value = ovsdb_parser_member(&parser, "value", OP_STRING | OP_OPTIONAL);
+        min = ovsdb_parser_member(&parser, "min", OP_INTEGER | OP_OPTIONAL);
+        max = ovsdb_parser_member(&parser, "max",
+                                  OP_INTEGER | OP_STRING | OP_OPTIONAL);
+        error = ovsdb_parser_finish(&parser);
+        if (error) {
+            return error;
+        }
+
+        error = ovsdb_atomic_type_from_json(&type->key_type, key);
+        if (error) {
+            return error;
+        }
+
+        if (value) {
+            error = ovsdb_atomic_type_from_json(&type->value_type, value);
+            if (error) {
+                return error;
+            }
+        }
+
+        error = n_from_json(min, &type->n_min);
+        if (error) {
+            return error;
+        }
+
+        if (max && max->type == JSON_STRING
+            && !strcmp(max->u.string, "unlimited")) {
+            type->n_max = UINT_MAX;
+        } else {
+            error = n_from_json(max, &type->n_max);
+            if (error) {
+                return error;
+            }
+        }
+
+        if (!ovsdb_type_is_valid(type)) {
+            return ovsdb_syntax_error(json, NULL,
+                                      "ovsdb type fails constraint checks");
+        }
+
+        return NULL;
+    } else {
+        return ovsdb_syntax_error(json, NULL, "ovsdb type expected");
+    }
+}
+
+struct json *
+ovsdb_type_to_json(const struct ovsdb_type *type)
+{
+    if (ovsdb_type_is_scalar(type)) {
+        return ovsdb_atomic_type_to_json(type->key_type);
+    } else {
+        struct json *json = json_object_create();
+        json_object_put(json, "key",
+                        ovsdb_atomic_type_to_json(type->key_type));
+        if (type->value_type != OVSDB_TYPE_VOID) {
+            json_object_put(json, "value",
+                            ovsdb_atomic_type_to_json(type->value_type));
+        }
+        if (type->n_min != 1) {
+            json_object_put(json, "min", json_integer_create(type->n_min));
+        }
+        if (type->n_max == UINT_MAX) {
+            json_object_put_string(json, "max", "unlimited");
+        } else if (type->n_max != 1) {
+            json_object_put(json, "max", json_integer_create(type->n_max));
+        }
+        return json;
+    }
+}
diff --git a/lib/ovsdb-types.h b/lib/ovsdb-types.h
new file mode 100644 (file)
index 0000000..78d76c9
--- /dev/null
@@ -0,0 +1,126 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_TYPES_H
+#define OVSDB_TYPES_H 1
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "compiler.h"
+#include "uuid.h"
+
+struct json;
+
+/* An atomic type: one that OVSDB regards as a single unit of data. */
+enum ovsdb_atomic_type {
+    OVSDB_TYPE_VOID,            /* No value. */
+    OVSDB_TYPE_INTEGER,         /* Signed 64-bit integer. */
+    OVSDB_TYPE_REAL,            /* IEEE 754 double-precision floating point. */
+    OVSDB_TYPE_BOOLEAN,         /* True or false. */
+    OVSDB_TYPE_STRING,          /* UTF-8 string. */
+    OVSDB_TYPE_UUID,            /* RFC 4122 UUID referencing a table row. */
+    OVSDB_N_TYPES
+};
+
+static inline bool ovsdb_atomic_type_is_valid(enum ovsdb_atomic_type);
+static inline bool ovsdb_atomic_type_is_valid_key(enum ovsdb_atomic_type);
+bool ovsdb_atomic_type_from_string(const char *, enum ovsdb_atomic_type *);
+struct ovsdb_error *ovsdb_atomic_type_from_json(enum ovsdb_atomic_type *,
+                                                const struct json *);
+const char *ovsdb_atomic_type_to_string(enum ovsdb_atomic_type);
+struct json *ovsdb_atomic_type_to_json(enum ovsdb_atomic_type);
+\f
+/* An OVSDB type.  One of:
+ *
+ *      - An atomic type.
+ *
+ *      - A set of atomic types.
+ *
+ *      - A map from one atomic type to another.
+ */
+struct ovsdb_type {
+    enum ovsdb_atomic_type key_type;
+    enum ovsdb_atomic_type value_type;
+    unsigned int n_min;
+    unsigned int n_max;         /* UINT_MAX stands in for "unlimited". */
+};
+
+#define OVSDB_TYPE_SCALAR_INITIALIZER(KEY_TYPE) \
+        { KEY_TYPE, OVSDB_TYPE_VOID, 1, 1 }
+
+extern const struct ovsdb_type ovsdb_type_integer;
+extern const struct ovsdb_type ovsdb_type_real;
+extern const struct ovsdb_type ovsdb_type_boolean;
+extern const struct ovsdb_type ovsdb_type_string;
+extern const struct ovsdb_type ovsdb_type_uuid;
+
+bool ovsdb_type_is_valid(const struct ovsdb_type *);
+
+static inline bool ovsdb_type_is_scalar(const struct ovsdb_type *);
+static inline bool ovsdb_type_is_optional(const struct ovsdb_type *);
+static inline bool ovsdb_type_is_composite(const struct ovsdb_type *);
+static inline bool ovsdb_type_is_set(const struct ovsdb_type *);
+static inline bool ovsdb_type_is_map(const struct ovsdb_type *);
+
+char *ovsdb_type_to_english(const struct ovsdb_type *);
+
+struct ovsdb_error *ovsdb_type_from_json(struct ovsdb_type *,
+                                         const struct json *)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_type_to_json(const struct ovsdb_type *);
+\f
+/* Inline function implementations. */
+
+static inline bool
+ovsdb_atomic_type_is_valid(enum ovsdb_atomic_type atomic_type)
+{
+    return atomic_type >= 0 && atomic_type < OVSDB_N_TYPES;
+}
+
+static inline bool
+ovsdb_atomic_type_is_valid_key(enum ovsdb_atomic_type atomic_type)
+{
+    /* XXX should we disallow reals or booleans as keys? */
+    return ovsdb_atomic_type_is_valid(atomic_type);
+}
+
+static inline bool ovsdb_type_is_scalar(const struct ovsdb_type *type)
+{
+    return (type->value_type == OVSDB_TYPE_VOID
+            && type->n_min == 1 && type->n_max == 1);
+}
+
+static inline bool ovsdb_type_is_optional(const struct ovsdb_type *type)
+{
+    return type->n_min == 0;
+}
+
+static inline bool ovsdb_type_is_composite(const struct ovsdb_type *type)
+{
+    return type->n_max > 1;
+}
+
+static inline bool ovsdb_type_is_set(const struct ovsdb_type *type)
+{
+    return (type->value_type == OVSDB_TYPE_VOID
+            && (type->n_min != 1 || type->n_max != 1));
+}
+
+static inline bool ovsdb_type_is_map(const struct ovsdb_type *type)
+{
+    return type->value_type != OVSDB_TYPE_VOID;
+}
+
+#endif /* ovsdb-types.h */
index 945b5c4..32bbc13 100644 (file)
@@ -254,7 +254,7 @@ poll_cancel(struct poll_waiter *pw)
 static struct poll_waiter *
 new_waiter(int fd, short int events)
 {
-    struct poll_waiter *waiter = xcalloc(1, sizeof *waiter);
+    struct poll_waiter *waiter = xzalloc(sizeof *waiter);
     assert(fd >= 0);
     waiter->fd = fd;
     waiter->events = events;
index 1fe3c12..0c7f424 100644 (file)
@@ -161,7 +161,7 @@ process_register(const char *name, pid_t pid)
 
     assert(sigchld_is_blocked());
 
-    p = xcalloc(1, sizeof *p);
+    p = xzalloc(sizeof *p);
     p->pid = pid;
     slash = strrchr(name, '/');
     p->name = xstrdup(slash ? slash + 1 : name);
index e94d20c..879f7a2 100644 (file)
@@ -17,6 +17,8 @@
 #ifndef QUEUE_H
 #define QUEUE_H 1
 
+#include <stdbool.h>
+
 /* Packet queue. */
 struct ovs_queue {
     int n;                      /* Number of queued packets. */
@@ -31,4 +33,9 @@ void queue_advance_head(struct ovs_queue *, struct ofpbuf *next);
 void queue_push_tail(struct ovs_queue *, struct ofpbuf *);
 struct ofpbuf *queue_pop_head(struct ovs_queue *);
 
+static inline bool queue_is_empty(const struct ovs_queue *q)
+{
+    return q->n == 0;
+}
+
 #endif /* queue.h */
index 2cbe43e..b6e958e 100644 (file)
@@ -176,7 +176,7 @@ rconn_new_from_vconn(const char *name, struct vconn *vconn)
 struct rconn *
 rconn_create(int probe_interval, int max_backoff)
 {
-    struct rconn *rc = xcalloc(1, sizeof *rc);
+    struct rconn *rc = xzalloc(sizeof *rc);
 
     rc->state = S_VOID;
     rc->state_entered = time_now();
diff --git a/lib/reconnect.c b/lib/reconnect.c
new file mode 100644 (file)
index 0000000..fadeeb8
--- /dev/null
@@ -0,0 +1,523 @@
+/*
+ * Copyright (c) 2008, 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "reconnect.h"
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "poll-loop.h"
+
+#define THIS_MODULE VLM_reconnect
+#include "vlog.h"
+
+#define STATES                                  \
+    STATE(VOID, 1 << 0)                         \
+    STATE(BACKOFF, 1 << 1)                      \
+    STATE(CONNECTING, 1 << 2)                   \
+    STATE(ACTIVE, 1 << 3)                       \
+    STATE(IDLE, 1 << 4)                         \
+    STATE(RECONNECT, 1 << 5)
+enum state {
+#define STATE(NAME, VALUE) S_##NAME = VALUE,
+    STATES
+#undef STATE
+};
+
+static bool
+is_connected_state(enum state state)
+{
+    return (state & (S_ACTIVE | S_IDLE)) != 0;
+}
+
+struct reconnect {
+    /* Configuration. */
+    char *name;
+    int min_backoff;
+    int max_backoff;
+    int probe_interval;
+
+    /* State. */
+    enum state state;
+    long long int state_entered;
+    int backoff;
+    long long int last_received;
+    long long int last_connected;
+
+    /* These values are simply for statistics reporting, not otherwise used
+     * directly by anything internal. */
+    long long int creation_time;
+    unsigned int n_attempted_connections, n_successful_connections;
+    unsigned int total_connected_duration;
+    unsigned int seqno;
+};
+
+static void reconnect_transition__(struct reconnect *, long long int now,
+                                   enum state state);
+static long long int reconnect_deadline__(const struct reconnect *);
+
+static const char *
+reconnect_state_name__(enum state state)
+{
+    switch (state) {
+#define STATE(NAME, VALUE) case S_##NAME: return #NAME;
+        STATES
+#undef STATE
+    }
+    return "***ERROR***";
+}
+
+/* Creates and returns a new reconnect FSM with default settings.  The FSM is
+ * initially disabled.  The caller will likely want to call reconnect_enable()
+ * and reconnect_set_name() on the returned object. */
+struct reconnect *
+reconnect_create(long long int now)
+{
+    struct reconnect *fsm = xzalloc(sizeof *fsm);
+
+    fsm->name = xstrdup("void");
+    fsm->min_backoff = 1000;
+    fsm->max_backoff = 8000;
+    fsm->probe_interval = 5000;
+
+    fsm->state = S_VOID;
+    fsm->state_entered = now;
+    fsm->backoff = 0;
+    fsm->last_received = now;
+    fsm->last_connected = now;
+    fsm->creation_time = now;
+
+    return fsm;
+}
+
+/* Frees 'fsm'. */
+void
+reconnect_destroy(struct reconnect *fsm)
+{
+    if (fsm) {
+        free(fsm->name);
+        free(fsm);
+    }
+}
+
+/* Returns 'fsm''s name. */
+const char *
+reconnect_get_name(const struct reconnect *fsm)
+{
+    return fsm->name;
+}
+
+/* Sets 'fsm''s name to 'name'.  If 'name' is null, then "void" is used
+ * instead.
+ *
+ * The name set for 'fsm' is used in log messages. */
+void
+reconnect_set_name(struct reconnect *fsm, const char *name)
+{
+    free(fsm->name);
+    fsm->name = xstrdup(name ? name : "void");
+}
+
+/* Return the minimum number of milliseconds to back off between consecutive
+ * connection attempts.  The default is 1000 ms. */
+int
+reconnect_get_min_backoff(const struct reconnect *fsm)
+{
+    return fsm->min_backoff;
+}
+
+/* Return the maximum number of milliseconds to back off between consecutive
+ * connection attempts.  The default is 8000 ms. */
+int
+reconnect_get_max_backoff(const struct reconnect *fsm)
+{
+    return fsm->max_backoff;
+}
+
+/* Returns the "probe interval" for 'fsm' in milliseconds.  If this is zero, it
+ * disables the connection keepalive feature.  If it is nonzero, then if the
+ * interval passes while 'fsm' is connected and without reconnect_received()
+ * being called for 'fsm', reconnect_run() returns RECONNECT_PROBE.  If the
+ * interval passes again without reconnect_received() being called,
+ * reconnect_run() returns RECONNECT_DISCONNECT for 'fsm'. */
+int
+reconnect_get_probe_interval(const struct reconnect *fsm)
+{
+    return fsm->probe_interval;
+}
+
+/* Configures the backoff parameters for 'fsm'.  'min_backoff' is the minimum
+ * number of milliseconds, and 'max_backoff' is the maximum, between connection
+ * attempts.
+ *
+ * 'min_backoff' must be at least 1000, and 'max_backoff' must be greater than
+ * or equal to 'min_backoff'. */
+void
+reconnect_set_backoff(struct reconnect *fsm, int min_backoff, int max_backoff)
+{
+    fsm->min_backoff = MAX(min_backoff, 1000);
+    fsm->max_backoff = max_backoff ? MAX(max_backoff, 1000) : 8000;
+    if (fsm->min_backoff > fsm->max_backoff) {
+        fsm->max_backoff = fsm->min_backoff;
+    }
+
+    if (fsm->state == S_BACKOFF && fsm->backoff > max_backoff) {
+        fsm->backoff = max_backoff;
+    }
+}
+
+/* Sets the "probe interval" for 'fsm' to 'probe_interval', in milliseconds.
+ * If this is zero, it disables the connection keepalive feature.  If it is
+ * nonzero, then if the interval passes while 'fsm' is connected and without
+ * reconnect_received() being called for 'fsm', reconnect_run() returns
+ * RECONNECT_PROBE.  If the interval passes again without reconnect_received()
+ * being called, reconnect_run() returns RECONNECT_DISCONNECT for 'fsm'.
+ *
+ * If 'probe_interval' is nonzero, then it will be forced to a value of at
+ * least 1000 ms. */
+void
+reconnect_set_probe_interval(struct reconnect *fsm, int probe_interval)
+{
+    fsm->probe_interval = probe_interval ? MAX(1000, probe_interval) : 0;
+}
+
+/* Returns true if 'fsm' has been enabled with reconnect_enable().  Calling
+ * another function that indicates a change in connection state, such as
+ * reconnect_disconnected() or reconnect_force_reconnect(), will also enable
+ * a reconnect FSM. */
+bool
+reconnect_is_enabled(const struct reconnect *fsm)
+{
+    return fsm->state != S_VOID;
+}
+
+/* If 'fsm' is disabled (the default for newly created FSMs), enables it, so
+ * that the next call to reconnect_run() for 'fsm' will return
+ * RECONNECT_CONNECT.
+ *
+ * If 'fsm' is not disabled, this function has no effect. */
+void
+reconnect_enable(struct reconnect *fsm, long long int now)
+{
+    if (fsm->state == S_VOID) {
+        reconnect_transition__(fsm, now, S_BACKOFF);
+        fsm->backoff = 0;
+    }
+}
+
+/* Disables 'fsm'.  Until 'fsm' is enabled again, reconnect_run() will always
+ * return 0. */
+void
+reconnect_disable(struct reconnect *fsm, long long int now)
+{
+    if (fsm->state != S_VOID) {
+        reconnect_transition__(fsm, now, S_VOID);
+    }
+}
+
+/* If 'fsm' is enabled and currently connected (or attempting to connect),
+ * forces reconnect_run() for 'fsm' to return RECONNECT_DISCONNECT the next
+ * time it is called, which should cause the client to drop the connection (or
+ * attempt), back off, and then reconnect. */
+void
+reconnect_force_reconnect(struct reconnect *fsm, long long int now)
+{
+    if (fsm->state & (S_CONNECTING | S_ACTIVE | S_IDLE)) {
+        reconnect_transition__(fsm, now, S_RECONNECT);
+    }
+}
+
+/* Tell 'fsm' that the connection dropped or that a connection attempt failed.
+ * 'error' specifies the reason: a positive value represents an errno value,
+ * EOF indicates that the connection was closed by the peer (e.g. read()
+ * returned 0), and 0 indicates no specific error.
+ *
+ * The FSM will back off, then reconnect. */
+void
+reconnect_disconnected(struct reconnect *fsm, long long int now, int error)
+{
+    if (fsm->state != S_BACKOFF) {
+        /* Report what happened. */
+        if (fsm->state & (S_ACTIVE | S_IDLE)) {
+            if (error > 0) {
+                VLOG_WARN("%s: connection dropped (%s)",
+                          fsm->name, strerror(error));
+            } else if (error == EOF) {
+                VLOG_INFO("%s: connection closed by peer", fsm->name);
+            } else {
+                VLOG_INFO("%s: connection dropped", fsm->name);
+            }
+        } else {
+            if (error > 0) {
+                VLOG_WARN("%s: connection attempt failed (%s)",
+                          fsm->name, strerror(error));
+            } else {
+                VLOG_INFO("%s: connection attempt timed out", fsm->name);
+            }
+        }
+
+        /* Back off. */
+        if (fsm->state & (S_ACTIVE | S_IDLE)
+            && fsm->last_received - fsm->last_connected >= fsm->backoff) {
+            fsm->backoff = fsm->min_backoff;
+        } else {
+            if (fsm->backoff < fsm->min_backoff) {
+                fsm->backoff = fsm->min_backoff;
+            } else if (fsm->backoff >= fsm->max_backoff / 2) {
+                fsm->backoff = fsm->max_backoff;
+            } else {
+                fsm->backoff *= 2;
+            }
+            VLOG_INFO("%s: waiting %.3g seconds before reconnect\n",
+                      fsm->name, fsm->backoff / 1000.0);
+        }
+        reconnect_transition__(fsm, now, S_BACKOFF);
+    }
+}
+
+/* Tell 'fsm' that a connection attempt is in progress.
+ *
+ * The FSM will start a timer, after which the connection attempt will be
+ * aborted (by returning RECONNECT_DISCONNECT from reconect_run()).  */
+void
+reconnect_connecting(struct reconnect *fsm, long long int now)
+{
+    if (fsm->state != S_CONNECTING) {
+        VLOG_INFO("%s: connecting...", fsm->name);
+        reconnect_transition__(fsm, now, S_CONNECTING);
+    }
+}
+
+/* Tell 'fsm' that the connection was successful.
+ *
+ * The FSM will start the probe interval timer, which is reset by
+ * reconnect_received().  If the timer expires, a probe will be sent (by
+ * returning RECONNECT_PROBE from reconnect_run()).  If the timer expires
+ * again without being reset, the connection will be aborted (by returning
+ * RECONNECT_DISCONNECT from reconnect_run()). */
+void
+reconnect_connected(struct reconnect *fsm, long long int now)
+{
+    if (!is_connected_state(fsm->state)) {
+        reconnect_connecting(fsm, now);
+
+        VLOG_INFO("%s: connected", fsm->name);
+        reconnect_transition__(fsm, now, S_ACTIVE);
+        fsm->last_connected = now;
+    }
+}
+
+/* Tell 'fsm' that the connection attempt failed.
+ *
+ * The FSM will back off and attempt to reconnect. */
+void
+reconnect_connect_failed(struct reconnect *fsm, long long int now, int error)
+{
+    reconnect_connecting(fsm, now);
+    reconnect_disconnected(fsm, now, error);
+}
+
+/* Tell 'fsm' that some data was received.  This resets the probe interval
+ * timer, so that the connection is known not to be idle. */
+void
+reconnect_received(struct reconnect *fsm, long long int now)
+{
+    if (fsm->state != S_ACTIVE) {
+        reconnect_transition__(fsm, now, S_ACTIVE);
+    }
+    fsm->last_received = now;
+}
+
+static void
+reconnect_transition__(struct reconnect *fsm, long long int now,
+                       enum state state)
+{
+    if (fsm->state == S_CONNECTING) {
+        fsm->n_attempted_connections++;
+        if (state == S_ACTIVE) {
+            fsm->n_successful_connections++;
+        }
+    }
+    if (is_connected_state(fsm->state) != is_connected_state(state)) {
+        if (is_connected_state(fsm->state)) {
+            fsm->total_connected_duration += now - fsm->last_connected;
+        }
+        fsm->seqno++;
+    }
+
+    VLOG_DBG("%s: entering %s", fsm->name, reconnect_state_name__(state));
+    fsm->state = state;
+    fsm->state_entered = now;
+}
+
+static long long int
+reconnect_deadline__(const struct reconnect *fsm)
+{
+    assert(fsm->state_entered != LLONG_MIN);
+    switch (fsm->state) {
+    case S_VOID:
+        return LLONG_MAX;
+
+    case S_BACKOFF:
+        return fsm->state_entered + fsm->backoff;
+
+    case S_CONNECTING:
+        return fsm->state_entered + MAX(1000, fsm->backoff);
+
+    case S_ACTIVE:
+        if (fsm->probe_interval) {
+            long long int base = MAX(fsm->last_received, fsm->state_entered);
+            return base + fsm->probe_interval;
+        }
+        return LLONG_MAX;
+
+    case S_IDLE:
+        return fsm->state_entered + fsm->probe_interval;
+
+    case S_RECONNECT:
+        return fsm->state_entered;
+    }
+
+    NOT_REACHED();
+}
+
+/* Assesses whether any action should be taken on 'fsm'.  The return value is
+ * one of:
+ *
+ *     - 0: The client need not take any action.
+ *
+ *     - RECONNECT_CONNECT: The client should start a connection attempt and
+ *       indicate this by calling reconnect_connecting().  If the connection
+ *       attempt has definitely succeeded, it should call
+ *       reconnect_connected().  If the connection attempt has definitely
+ *       failed, it should call reconnect_connect_failed().
+ *
+ *       The FSM is smart enough to back off correctly after successful
+ *       connections that quickly abort, so it is OK to call
+ *       reconnect_connected() after a low-level successful connection
+ *       (e.g. connect()) even if the connection might soon abort due to a
+ *       failure at a high-level (e.g. SSL negotiation failure).
+ *
+ *     - RECONNECT_DISCONNECT: The client should abort the current connection
+ *       or connection attempt and call reconnect_disconnected() or
+ *       reconnect_connect_failed() to indicate it.
+ *
+ *     - RECONNECT_PROBE: The client should send some kind of request to the
+ *       peer that will elicit a response, to ensure that the connection is
+ *       indeed in working order.  (This will only be returned if the "probe
+ *       interval" is nonzero--see reconnect_set_probe_interval()).
+ */
+enum reconnect_action
+reconnect_run(struct reconnect *fsm, long long int now)
+{
+    if (now >= reconnect_deadline__(fsm)) {
+        switch (fsm->state) {
+        case S_VOID:
+            return 0;
+
+        case S_BACKOFF:
+            return RECONNECT_CONNECT;
+
+        case S_CONNECTING:
+            return RECONNECT_DISCONNECT;
+
+        case S_ACTIVE:
+            VLOG_DBG("%s: idle %lld ms, sending inactivity probe", fsm->name,
+                     now - MAX(fsm->last_received, fsm->state_entered));
+            reconnect_transition__(fsm, now, S_IDLE);
+            return RECONNECT_PROBE;
+
+        case S_IDLE:
+            VLOG_ERR("%s: no response to inactivity probe after %.3g "
+                     "seconds, disconnecting",
+                     fsm->name, (now - fsm->state_entered) / 1000.0);
+            return RECONNECT_DISCONNECT;
+
+        case S_RECONNECT:
+            return RECONNECT_DISCONNECT;
+        }
+
+        NOT_REACHED();
+    } else {
+        return fsm->state == S_CONNECTING ? RECONNECT_CONNECT : 0;
+    }
+}
+
+/* Causes the next call to poll_block() to wake up when reconnect_run() should
+ * be called on 'fsm'. */
+void
+reconnect_wait(struct reconnect *fsm, long long int now)
+{
+    int timeout = reconnect_timeout(fsm, now);
+    if (timeout >= 0) {
+        poll_timer_wait(timeout);
+    }
+}
+
+/* Returns the number of milliseconds after which reconnect_run() should be
+ * called on 'fsm' if nothing else notable happens in the meantime, or a
+ * negative number if this is currently unnecessary. */
+int
+reconnect_timeout(struct reconnect *fsm, long long int now)
+{
+    long long int deadline = reconnect_deadline__(fsm);
+    if (deadline != LLONG_MAX) {
+        long long int remaining = deadline - now;
+        return MAX(0, MIN(INT_MAX, remaining));
+    }
+    return -1;
+}
+
+/* Returns true if 'fsm' is currently believed to be connected, that is, if
+ * reconnect_connected() was called more recently than any call to
+ * reconnect_connect_failed() or reconnect_disconnected() or
+ * reconnect_disable(), and false otherwise.  */
+bool
+reconnect_is_connected(const struct reconnect *fsm)
+{
+    return is_connected_state(fsm->state);
+}
+
+/* Returns the number of milliseconds for which 'fsm' has been continuously
+ * connected to its peer.  (If 'fsm' is not currently connected, this is 0.) */
+unsigned int
+reconnect_get_connection_duration(const struct reconnect *fsm,
+                                  long long int now)
+{
+    return reconnect_is_connected(fsm) ? now - fsm->last_connected : 0;
+}
+
+/* Copies various statistics for 'fsm' into '*stats'. */
+void
+reconnect_get_stats(const struct reconnect *fsm, long long int now,
+                    struct reconnect_stats *stats)
+{
+    stats->creation_time = fsm->creation_time;
+    stats->last_received = fsm->last_received;
+    stats->last_connected = fsm->last_connected;
+    stats->backoff = fsm->backoff;
+    stats->seqno = fsm->seqno;
+    stats->is_connected = reconnect_is_connected(fsm);
+    stats->current_connection_duration
+        = reconnect_get_connection_duration(fsm, now);
+    stats->total_connected_duration = (stats->current_connection_duration
+                                       + fsm->total_connected_duration);
+    stats->n_attempted_connections = fsm->n_attempted_connections;
+    stats->n_successful_connections = fsm->n_successful_connections;
+    stats->state = reconnect_state_name__(fsm->state);
+    stats->state_elapsed = now - fsm->state_entered;
+}
diff --git a/lib/reconnect.h b/lib/reconnect.h
new file mode 100644 (file)
index 0000000..3442c07
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RECONNECT_H
+#define RECONNECT_H 1
+
+/* This library implements a finite-state machine for connecting and
+ * reconnecting to a network resource with exponential backoff.  It also
+ * provides optional support for detecting a connection on which the peer is no
+ * longer responding.
+ *
+ * The library does not implement anything networking related, only an FSM for
+ * networking code to use.
+ *
+ * Many "reconnect" functions take a "now" argument.  This makes testing easier
+ * since there is no hidden state.  When not testing, just pass the return
+ * value of time_msec() from timeval.h.  (Perhaps this design should be
+ * revisited later.) */
+
+#include <stdbool.h>
+
+struct reconnect *reconnect_create(long long int now);
+void reconnect_destroy(struct reconnect *);
+
+const char *reconnect_get_name(const struct reconnect *);
+void reconnect_set_name(struct reconnect *, const char *name);
+
+int reconnect_get_min_backoff(const struct reconnect *);
+int reconnect_get_max_backoff(const struct reconnect *);
+int reconnect_get_probe_interval(const struct reconnect *);
+
+void reconnect_set_backoff(struct reconnect *,
+                           int min_backoff, int max_backoff);
+void reconnect_set_probe_interval(struct reconnect *, int probe_interval);
+
+bool reconnect_is_enabled(const struct reconnect *);
+void reconnect_enable(struct reconnect *, long long int now);
+void reconnect_disable(struct reconnect *, long long int now);
+
+void reconnect_force_reconnect(struct reconnect *, long long int now);
+
+bool reconnect_is_connected(const struct reconnect *);
+unsigned int reconnect_get_connection_duration(const struct reconnect *,
+                                               long long int now);
+
+void reconnect_disconnected(struct reconnect *, long long int now, int error);
+void reconnect_connecting(struct reconnect *, long long int now);
+void reconnect_connected(struct reconnect *, long long int now);
+void reconnect_connect_failed(struct reconnect *, long long int now,
+                              int error);
+void reconnect_received(struct reconnect *, long long int now);
+
+enum reconnect_action {
+    RECONNECT_CONNECT = 1,
+    RECONNECT_DISCONNECT,
+    RECONNECT_PROBE,
+};
+enum reconnect_action reconnect_run(struct reconnect *, long long int now);
+void reconnect_wait(struct reconnect *, long long int now);
+int reconnect_timeout(struct reconnect *, long long int now);
+
+struct reconnect_stats {
+    /* All times and durations in this structure are in milliseconds. */
+    long long int creation_time;  /* Time reconnect_create() called. */
+    long long int last_received; /* Last call to reconnect_received(). */
+    long long int last_connected; /* Last call to reconnect_connected(). */
+    int backoff;                  /* Current backoff duration.  */
+
+    unsigned int seqno;         /* # of connections + # of disconnections. */
+
+    bool is_connected;          /* Currently connected? */
+    unsigned int current_connection_duration; /* Time of current connection. */
+    unsigned int total_connected_duration;    /* Sum of all connections. */
+    unsigned int n_attempted_connections;
+    unsigned int n_successful_connections;
+
+    /* These should only be provided to a human user for debugging purposes.
+     * The client should not attempt to interpret them. */
+    const char *state;            /* FSM state. */
+    unsigned int state_elapsed;   /* Time since FSM state entered. */
+};
+
+void reconnect_get_stats(const struct reconnect *, long long int now,
+                         struct reconnect_stats *);
+
+#endif /* reconnect.h */
index f73f2d6..205b82f 100644 (file)
@@ -31,7 +31,9 @@
 
 #include <config.h>
 #include "sha1.h"
+#include <ctype.h>
 #include <string.h>
+#include "util.h"
 
 /* a bit faster & bigger, if defined */
 #define UNROLL_LOOPS
@@ -279,3 +281,32 @@ sha1_bytes(const void *data, size_t n, uint8_t digest[SHA1_DIGEST_SIZE])
     sha1_update(&ctx, data, n);
     sha1_final(&ctx, digest);
 }
+
+void
+sha1_to_hex(const uint8_t digest[SHA1_DIGEST_SIZE],
+            char hex[SHA1_HEX_DIGEST_LEN + 1])
+{
+    int i;
+
+    for (i = 0; i < SHA1_DIGEST_SIZE; i++) {
+        *hex++ = "0123456789abcdef"[digest[i] >> 4];
+        *hex++ = "0123456789abcdef"[digest[i] & 15];
+    }
+    *hex = '\0';
+}
+
+bool
+sha1_from_hex(uint8_t digest[SHA1_DIGEST_SIZE], const char *hex)
+{
+    int i;
+
+    for (i = 0; i < SHA1_DIGEST_SIZE; i++) {
+        if (!isxdigit(hex[0]) || !isxdigit(hex[1])) {
+            return false;
+        }
+        digest[i] = (hexit_value(hex[0]) << 4) | hexit_value(hex[1]);
+        hex += 2;
+    }
+    return true;
+}
+
index 75d3533..9a37277 100644 (file)
 #ifndef SHA1_H
 #define SHA1_H
 
+#include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
 
-/* Size of the SHA1 digest. */
-#define SHA1_DIGEST_SIZE 20
+#define SHA1_DIGEST_SIZE 20     /* Size of the SHA1 digest. */
+#define SHA1_HEX_DIGEST_LEN 40  /* Length of SHA1 digest as hex in ASCII. */
 
 /* SHA1 context structure. */
 struct sha1_ctx {
@@ -48,4 +49,18 @@ void sha1_update(struct sha1_ctx *, const void *, size_t);
 void sha1_final(struct sha1_ctx *, uint8_t digest[SHA1_DIGEST_SIZE]);
 void sha1_bytes(const void *, size_t, uint8_t digest[SHA1_DIGEST_SIZE]);
 
+#define SHA1_FMT \
+        "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" \
+        "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+#define SHA1_ARGS(DIGEST) \
+    ((DIGEST)[0]), ((DIGEST)[1]), ((DIGEST)[2]), ((DIGEST)[3]), \
+    ((DIGEST)[4]), ((DIGEST)[5]), ((DIGEST)[6]), ((DIGEST)[7]), \
+    ((DIGEST)[8]), ((DIGEST)[9]), ((DIGEST)[10]), ((DIGEST)[11]), \
+    ((DIGEST)[12]), ((DIGEST)[13]), ((DIGEST)[14]), ((DIGEST)[15]), \
+    ((DIGEST)[16]), ((DIGEST)[17]), ((DIGEST)[18]), ((DIGEST)[19])
+
+void sha1_to_hex(const uint8_t digest[SHA1_DIGEST_SIZE],
+                 char hex[SHA1_HEX_DIGEST_LEN + 1]);
+bool sha1_from_hex(uint8_t digest[SHA1_DIGEST_SIZE], const char *hex);
+
 #endif  /* sha1.h */
index da33fe8..e6cb6b0 100644 (file)
@@ -58,14 +58,20 @@ shash_is_empty(const struct shash *shash)
     return hmap_is_empty(&shash->map);
 }
 
+size_t
+shash_count(const struct shash *shash)
+{
+    return hmap_count(&shash->map);
+}
+
 /* It is the caller's responsibility to avoid duplicate names, if that is
  * desirable. */
 struct shash_node *
-shash_add(struct shash *sh, const char *name, void *data)
+shash_add(struct shash *sh, const char *name, const void *data)
 {
     struct shash_node *node = xmalloc(sizeof *node);
     node->name = xstrdup(name);
-    node->data = data;
+    node->data = (void *) data;
     hmap_insert(&sh->map, &node->node, hash_name(name));
     return node;
 }
@@ -100,6 +106,19 @@ shash_find_data(const struct shash *sh, const char *name)
     return node ? node->data : NULL;
 }
 
+void *
+shash_find_and_delete(struct shash *sh, const char *name)
+{
+    struct shash_node *node = shash_find(sh, name);
+    if (node) {
+        void *data = node->data;
+        shash_delete(sh, node);
+        return data;
+    } else {
+        return NULL;
+    }
+}
+
 struct shash_node *
 shash_first(const struct shash *shash)
 {
@@ -107,3 +126,34 @@ shash_first(const struct shash *shash)
     return node ? CONTAINER_OF(node, struct shash_node, node) : NULL;
 }
 
+static int
+compare_nodes_by_name(const void *a_, const void *b_)
+{
+    const struct shash_node *const *a = a_;
+    const struct shash_node *const *b = b_;
+    return strcmp((*a)->name, (*b)->name);
+}
+
+const struct shash_node **
+shash_sort(const struct shash *sh)
+{
+    if (shash_is_empty(sh)) {
+        return NULL;
+    } else {
+        const struct shash_node **nodes;
+        struct shash_node *node;
+        size_t i, n;
+
+        n = shash_count(sh);
+        nodes = xmalloc(n * sizeof *nodes);
+        i = 0;
+        SHASH_FOR_EACH (node, sh) {
+            nodes[i++] = node;
+        }
+        assert(i == n);
+
+        qsort(nodes, n, sizeof *nodes, compare_nodes_by_name);
+
+        return nodes;
+    }
+}
index 5794a20..236d865 100644 (file)
@@ -42,10 +42,13 @@ void shash_init(struct shash *);
 void shash_destroy(struct shash *);
 void shash_clear(struct shash *);
 bool shash_is_empty(const struct shash *);
-struct shash_node *shash_add(struct shash *, const char *, void *);
+size_t shash_count(const struct shash *);
+struct shash_node *shash_add(struct shash *, const char *, const void *);
 void shash_delete(struct shash *, struct shash_node *);
 struct shash_node *shash_find(const struct shash *, const char *);
 void *shash_find_data(const struct shash *, const char *);
+void *shash_find_and_delete(struct shash *, const char *);
 struct shash_node *shash_first(const struct shash *);
+const struct shash_node **shash_sort(const struct shash *);
 
 #endif /* shash.h */
diff --git a/lib/sort.c b/lib/sort.c
new file mode 100644 (file)
index 0000000..017b0a9
--- /dev/null
@@ -0,0 +1,70 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "sort.h"
+
+#include "random.h"
+
+static size_t
+partition(size_t p, size_t r,
+          int (*compare)(size_t a, size_t b, void *aux),
+          void (*swap)(size_t a, size_t b, void *aux),
+          void *aux)
+{
+    size_t x = r - 1;
+    size_t i, j;
+
+    i = p;
+    for (j = p; j < x; j++) {
+        if (compare(j, x, aux) <= 0) {
+            swap(i++, j, aux);
+        }
+    }
+    swap(i, x, aux);
+    return i;
+}
+
+static void
+quicksort(size_t p, size_t r,
+          int (*compare)(size_t a, size_t b, void *aux),
+          void (*swap)(size_t a, size_t b, void *aux),
+          void *aux)
+{
+    size_t i, q;
+
+    if (r - p < 2) {
+        return;
+    }
+
+    i = random_range(r - p) + p;
+    if (r - 1 != i) {
+        swap(r - 1, i, aux);
+    }
+
+    q = partition(p, r, compare, swap, aux);
+    quicksort(p, q, compare, swap, aux);
+    quicksort(q, r, compare, swap, aux);
+}
+
+void
+sort(size_t count,
+     int (*compare)(size_t a, size_t b, void *aux),
+     void (*swap)(size_t a, size_t b, void *aux),
+     void *aux)
+{
+    quicksort(0, count, compare, swap, aux);
+}
diff --git a/lib/sort.h b/lib/sort.h
new file mode 100644 (file)
index 0000000..c952f44
--- /dev/null
@@ -0,0 +1,26 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SORT_H
+#define SORT_H 1
+
+#include <stddef.h>
+
+void sort(size_t count,
+          int (*compare)(size_t a, size_t b, void *aux),
+          void (*swap)(size_t a, size_t b, void *aux),
+          void *aux);
+
+#endif /* sort.h */
index 87230bd..cf1b2f9 100644 (file)
--- a/lib/stp.c
+++ b/lib/stp.c
@@ -214,7 +214,7 @@ stp_create(const char *name, stp_identifier bridge_id,
     struct stp *stp;
     struct stp_port *p;
 
-    stp = xcalloc(1, sizeof *stp);
+    stp = xzalloc(sizeof *stp);
     stp->name = xstrdup(name);
     stp->bridge_id = bridge_id;
     if (!(stp->bridge_id >> 48)) {
diff --git a/lib/stream-fd.c b/lib/stream-fd.c
new file mode 100644 (file)
index 0000000..46aa8e7
--- /dev/null
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2008, 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "stream-fd.h"
+#include <assert.h>
+#include <errno.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include "fatal-signal.h"
+#include "leak-checker.h"
+#include "poll-loop.h"
+#include "socket-util.h"
+#include "util.h"
+#include "stream-provider.h"
+#include "stream.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_stream_fd
+
+/* Active file descriptor stream. */
+
+struct stream_fd
+{
+    struct stream stream;
+    int fd;
+    char *unlink_path;
+};
+
+static struct stream_class stream_fd_class;
+
+static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 25);
+
+static void maybe_unlink_and_free(char *path);
+
+/* Creates a new stream named 'name' that will send and receive data on 'fd'
+ * and stores a pointer to the stream in '*streamp'.  Initial connection status
+ * 'connect_status' is interpreted as described for stream_init().
+ *
+ * When '*streamp' is closed, then 'unlink_path' (if nonnull) will be passed to
+ * fatal_signal_unlink_file_now() and then freed with free().
+ *
+ * Returns 0 if successful, otherwise a positive errno value.  (The current
+ * implementation never fails.) */
+int
+new_fd_stream(const char *name, int fd, int connect_status,
+              char *unlink_path, struct stream **streamp)
+{
+    struct stream_fd *s;
+
+    s = xmalloc(sizeof *s);
+    stream_init(&s->stream, &stream_fd_class, connect_status, name);
+    s->fd = fd;
+    s->unlink_path = unlink_path;
+    *streamp = &s->stream;
+    return 0;
+}
+
+static struct stream_fd *
+stream_fd_cast(struct stream *stream)
+{
+    stream_assert_class(stream, &stream_fd_class);
+    return CONTAINER_OF(stream, struct stream_fd, stream);
+}
+
+static void
+fd_close(struct stream *stream)
+{
+    struct stream_fd *s = stream_fd_cast(stream);
+    close(s->fd);
+    maybe_unlink_and_free(s->unlink_path);
+    free(s);
+}
+
+static int
+fd_connect(struct stream *stream)
+{
+    struct stream_fd *s = stream_fd_cast(stream);
+    return check_connection_completion(s->fd);
+}
+
+static ssize_t
+fd_recv(struct stream *stream, void *buffer, size_t n)
+{
+    struct stream_fd *s = stream_fd_cast(stream);
+    ssize_t retval = read(s->fd, buffer, n);
+    return retval >= 0 ? retval : -errno;
+}
+
+static ssize_t
+fd_send(struct stream *stream, const void *buffer, size_t n)
+{
+    struct stream_fd *s = stream_fd_cast(stream);
+    ssize_t retval = write(s->fd, buffer, n);
+    return (retval > 0 ? retval
+            : retval == 0 ? -EAGAIN
+            : -errno);
+}
+
+static void
+fd_wait(struct stream *stream, enum stream_wait_type wait)
+{
+    struct stream_fd *s = stream_fd_cast(stream);
+    switch (wait) {
+    case STREAM_CONNECT:
+    case STREAM_SEND:
+        poll_fd_wait(s->fd, POLLOUT);
+        break;
+
+    case STREAM_RECV:
+        poll_fd_wait(s->fd, POLLIN);
+        break;
+
+    default:
+        NOT_REACHED();
+    }
+}
+
+static struct stream_class stream_fd_class = {
+    "fd",                       /* name */
+    NULL,                       /* open */
+    fd_close,                   /* close */
+    fd_connect,                 /* connect */
+    fd_recv,                    /* recv */
+    fd_send,                    /* send */
+    fd_wait,                    /* wait */
+};
+\f
+/* Passive file descriptor stream. */
+
+struct fd_pstream
+{
+    struct pstream pstream;
+    int fd;
+    int (*accept_cb)(int fd, const struct sockaddr *, size_t sa_len,
+                     struct stream **);
+    char *unlink_path;
+};
+
+static struct pstream_class fd_pstream_class;
+
+static struct fd_pstream *
+fd_pstream_cast(struct pstream *pstream)
+{
+    pstream_assert_class(pstream, &fd_pstream_class);
+    return CONTAINER_OF(pstream, struct fd_pstream, pstream);
+}
+
+/* Creates a new pstream named 'name' that will accept new socket connections
+ * on 'fd' and stores a pointer to the stream in '*pstreamp'.
+ *
+ * When a connection has been accepted, 'accept_cb' will be called with the new
+ * socket fd 'fd' and the remote address of the connection 'sa' and 'sa_len'.
+ * accept_cb must return 0 if the connection is successful, in which case it
+ * must initialize '*streamp' to the new stream, or a positive errno value on
+ * error.  In either case accept_cb takes ownership of the 'fd' passed in.
+ *
+ * When '*pstreamp' is closed, then 'unlink_path' (if nonnull) will be passed
+ * to fatal_signal_unlink_file_now() and freed with free().
+ *
+ * Returns 0 if successful, otherwise a positive errno value.  (The current
+ * implementation never fails.) */
+int
+new_fd_pstream(const char *name, int fd,
+               int (*accept_cb)(int fd, const struct sockaddr *sa,
+                                size_t sa_len, struct stream **streamp),
+               char *unlink_path, struct pstream **pstreamp)
+{
+    struct fd_pstream *ps = xmalloc(sizeof *ps);
+    pstream_init(&ps->pstream, &fd_pstream_class, name);
+    ps->fd = fd;
+    ps->accept_cb = accept_cb;
+    ps->unlink_path = unlink_path;
+    *pstreamp = &ps->pstream;
+    return 0;
+}
+
+static void
+pfd_close(struct pstream *pstream)
+{
+    struct fd_pstream *ps = fd_pstream_cast(pstream);
+    close(ps->fd);
+    maybe_unlink_and_free(ps->unlink_path);
+    free(ps);
+}
+
+static int
+pfd_accept(struct pstream *pstream, struct stream **new_streamp)
+{
+    struct fd_pstream *ps = fd_pstream_cast(pstream);
+    struct sockaddr_storage ss;
+    socklen_t ss_len = sizeof ss;
+    int new_fd;
+    int retval;
+
+    new_fd = accept(ps->fd, (struct sockaddr *) &ss, &ss_len);
+    if (new_fd < 0) {
+        int retval = errno;
+        if (retval != EAGAIN) {
+            VLOG_DBG_RL(&rl, "accept: %s", strerror(retval));
+        }
+        return retval;
+    }
+
+    retval = set_nonblocking(new_fd);
+    if (retval) {
+        close(new_fd);
+        return retval;
+    }
+
+    return ps->accept_cb(new_fd, (const struct sockaddr *) &ss, ss_len,
+                         new_streamp);
+}
+
+static void
+pfd_wait(struct pstream *pstream)
+{
+    struct fd_pstream *ps = fd_pstream_cast(pstream);
+    poll_fd_wait(ps->fd, POLLIN);
+}
+
+static struct pstream_class fd_pstream_class = {
+    "pstream",
+    NULL,
+    pfd_close,
+    pfd_accept,
+    pfd_wait
+};
+\f
+/* Helper functions. */
+static void
+maybe_unlink_and_free(char *path)
+{
+    if (path) {
+        fatal_signal_unlink_file_now(path);
+        free(path);
+    }
+}
diff --git a/lib/stream-fd.h b/lib/stream-fd.h
new file mode 100644 (file)
index 0000000..d2a34eb
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2008, 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef STREAM_FD_H
+#define STREAM_FD_H 1
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+struct stream;
+struct pstream;
+struct sockaddr;
+
+int new_fd_stream(const char *name, int fd, int connect_status,
+                      char *unlink_path, struct stream **streamp);
+int new_fd_pstream(const char *name, int fd,
+                   int (*accept_cb)(int fd, const struct sockaddr *,
+                                    size_t sa_len, struct stream **),
+                   char *unlink_path,
+                   struct pstream **pstreamp);
+
+#endif /* stream-fd.h */
diff --git a/lib/stream-provider.h b/lib/stream-provider.h
new file mode 100644 (file)
index 0000000..6beaab7
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef STREAM_PROVIDER_H
+#define STREAM_PROVIDER_H 1
+
+#include <assert.h>
+#include <sys/types.h>
+#include "stream.h"
+
+/* Active stream connection. */
+
+/* Active stream connection.
+ *
+ * This structure should be treated as opaque by implementation. */
+struct stream {
+    struct stream_class *class;
+    int state;
+    int error;
+    uint32_t remote_ip;
+    uint16_t remote_port;
+    uint32_t local_ip;
+    uint16_t local_port;
+    char *name;
+};
+
+void stream_init(struct stream *, struct stream_class *, int connect_status,
+                 const char *name);
+void stream_set_remote_ip(struct stream *, uint32_t remote_ip);
+void stream_set_remote_port(struct stream *, uint16_t remote_port);
+void stream_set_local_ip(struct stream *, uint32_t local_ip);
+void stream_set_local_port(struct stream *, uint16_t local_port);
+static inline void stream_assert_class(const struct stream *stream,
+                                       const struct stream_class *class)
+{
+    assert(stream->class == class);
+}
+
+struct stream_class {
+    /* Prefix for connection names, e.g. "tcp", "ssl", "unix". */
+    const char *name;
+
+    /* Attempts to connect to a peer.  'name' is the full connection name
+     * provided by the user, e.g. "tcp:1.2.3.4".  This name is useful for error
+     * messages but must not be modified.
+     *
+     * 'suffix' is a copy of 'name' following the colon and may be modified.
+     *
+     * Returns 0 if successful, otherwise a positive errno value.  If
+     * successful, stores a pointer to the new connection in '*streamp'.
+     *
+     * The open function must not block waiting for a connection to complete.
+     * If the connection cannot be completed immediately, it should return
+     * EAGAIN (not EINPROGRESS, as returned by the connect system call) and
+     * continue the connection in the background. */
+    int (*open)(const char *name, char *suffix, struct stream **streamp);
+
+    /* Closes 'stream' and frees associated memory. */
+    void (*close)(struct stream *stream);
+
+    /* Tries to complete the connection on 'stream'.  If 'stream''s connection
+     * is complete, returns 0 if the connection was successful or a positive
+     * errno value if it failed.  If the connection is still in progress,
+     * returns EAGAIN.
+     *
+     * The connect function must not block waiting for the connection to
+     * complete; instead, it should return EAGAIN immediately. */
+    int (*connect)(struct stream *stream);
+
+    /* Tries to receive up to 'n' bytes from 'stream' into 'buffer', and
+     * returns:
+     *
+     *     - If successful, the number of bytes received (between 1 and 'n').
+     *
+     *     - On error, a negative errno value.
+     *
+     *     - 0, if the connection has been closed in the normal fashion.
+     *
+     * The recv function will not be passed a zero 'n'.
+     *
+     * The recv function must not block waiting for data to arrive.  If no data
+     * have been received, it should return -EAGAIN immediately. */
+    ssize_t (*recv)(struct stream *stream, void *buffer, size_t n);
+
+    /* Tries to send up to 'n' bytes of 'buffer' on 'stream', and returns:
+     *
+     *     - If successful, the number of bytes sent (between 1 and 'n').
+     *
+     *     - On error, a negative errno value.
+     *
+     *     - Never returns 0.
+     *
+     * The send function will not be passed a zero 'n'.
+     *
+     * The send function must not block.  If no bytes can be immediately
+     * accepted for transmission, it should return -EAGAIN immediately. */
+    ssize_t (*send)(struct stream *stream, const void *buffer, size_t n);
+
+    /* Arranges for the poll loop to wake up when 'stream' is ready to take an
+     * action of the given 'type'. */
+    void (*wait)(struct stream *stream, enum stream_wait_type type);
+};
+\f
+/* Passive listener for incoming stream connections.
+ *
+ * This structure should be treated as opaque by stream implementations. */
+struct pstream {
+    struct pstream_class *class;
+    char *name;
+};
+
+void pstream_init(struct pstream *, struct pstream_class *, const char *name);
+static inline void pstream_assert_class(const struct pstream *pstream,
+                                        const struct pstream_class *class)
+{
+    assert(pstream->class == class);
+}
+
+struct pstream_class {
+    /* Prefix for connection names, e.g. "ptcp", "pssl", "punix". */
+    const char *name;
+
+    /* Attempts to start listening for stream connections.  'name' is the full
+     * connection name provided by the user, e.g. "ptcp:1234".  This name is
+     * useful for error messages but must not be modified.
+     *
+     * 'suffix' is a copy of 'name' following the colon and may be modified.
+     *
+     * Returns 0 if successful, otherwise a positive errno value.  If
+     * successful, stores a pointer to the new connection in '*pstreamp'.
+     *
+     * The listen function must not block.  If the connection cannot be
+     * completed immediately, it should return EAGAIN (not EINPROGRESS, as
+     * returned by the connect system call) and continue the connection in the
+     * background. */
+    int (*listen)(const char *name, char *suffix, struct pstream **pstreamp);
+
+    /* Closes 'pstream' and frees associated memory. */
+    void (*close)(struct pstream *pstream);
+
+    /* Tries to accept a new connection on 'pstream'.  If successful, stores
+     * the new connection in '*new_streamp' and returns 0.  Otherwise, returns
+     * a positive errno value.
+     *
+     * The accept function must not block waiting for a connection.  If no
+     * connection is ready to be accepted, it should return EAGAIN. */
+    int (*accept)(struct pstream *pstream, struct stream **new_streamp);
+
+    /* Arranges for the poll loop to wake up when a connection is ready to be
+     * accepted on 'pstream'. */
+    void (*wait)(struct pstream *pstream);
+};
+
+/* Active and passive stream classes. */
+extern struct stream_class tcp_stream_class;
+extern struct pstream_class ptcp_pstream_class;
+extern struct stream_class unix_stream_class;
+extern struct pstream_class punix_pstream_class;
+
+#endif /* stream-provider.h */
diff --git a/lib/stream-tcp.c b/lib/stream-tcp.c
new file mode 100644 (file)
index 0000000..947be9f
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2008, 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "stream.h"
+#include <errno.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "packets.h"
+#include "socket-util.h"
+#include "util.h"
+#include "stream-provider.h"
+#include "stream-fd.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_stream_tcp
+
+/* Active TCP. */
+
+static int
+new_tcp_stream(const char *name, int fd, int connect_status,
+              const struct sockaddr_in *remote, struct stream **streamp)
+{
+    struct sockaddr_in local;
+    socklen_t local_len = sizeof local;
+    int on = 1;
+    int retval;
+
+    /* Get the local IP and port information */
+    retval = getsockname(fd, (struct sockaddr *)&local, &local_len);
+    if (retval) {
+        memset(&local, 0, sizeof local);
+    }
+
+    retval = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof on);
+    if (retval) {
+        VLOG_ERR("%s: setsockopt(TCP_NODELAY): %s", name, strerror(errno));
+        close(fd);
+        return errno;
+    }
+
+    retval = new_fd_stream(name, fd, connect_status, NULL, streamp);
+    if (!retval) {
+        struct stream *stream = *streamp;
+        stream_set_remote_ip(stream, remote->sin_addr.s_addr);
+        stream_set_remote_port(stream, remote->sin_port);
+        stream_set_local_ip(stream, local.sin_addr.s_addr);
+        stream_set_local_port(stream, local.sin_port);
+    }
+    return retval;
+}
+
+static int
+tcp_open(const char *name, char *suffix, struct stream **streamp)
+{
+    struct sockaddr_in sin;
+    int fd, error;
+
+    error = inet_open_active(SOCK_STREAM, suffix, 0, &sin, &fd);
+    if (fd >= 0) {
+        return new_tcp_stream(name, fd, error, &sin, streamp);
+    } else {
+        VLOG_ERR("%s: connect: %s", name, strerror(error));
+        return error;
+    }
+}
+
+struct stream_class tcp_stream_class = {
+    "tcp",                      /* name */
+    tcp_open,                   /* open */
+    NULL,                       /* close */
+    NULL,                       /* connect */
+    NULL,                       /* recv */
+    NULL,                       /* send */
+    NULL,                       /* wait */
+};
+\f
+/* Passive TCP. */
+
+static int ptcp_accept(int fd, const struct sockaddr *sa, size_t sa_len,
+                       struct stream **streamp);
+
+static int
+ptcp_open(const char *name UNUSED, char *suffix, struct pstream **pstreamp)
+{
+    int fd;
+
+    fd = inet_open_passive(SOCK_STREAM, suffix, 0);
+    if (fd < 0) {
+        return -fd;
+    } else {
+        return new_fd_pstream("ptcp", fd, ptcp_accept, NULL, pstreamp);
+    }
+}
+
+static int
+ptcp_accept(int fd, const struct sockaddr *sa, size_t sa_len,
+            struct stream **streamp)
+{
+    const struct sockaddr_in *sin = (const struct sockaddr_in *) sa;
+    char name[128];
+
+    if (sa_len == sizeof(struct sockaddr_in) && sin->sin_family == AF_INET) {
+        sprintf(name, "tcp:"IP_FMT, IP_ARGS(&sin->sin_addr));
+        sprintf(strchr(name, '\0'), ":%"PRIu16, ntohs(sin->sin_port));
+    } else {
+        strcpy(name, "tcp");
+    }
+    return new_tcp_stream(name, fd, 0, sin, streamp);
+}
+
+struct pstream_class ptcp_pstream_class = {
+    "ptcp",
+    ptcp_open,
+    NULL,
+    NULL,
+    NULL
+};
+
diff --git a/lib/stream-unix.c b/lib/stream-unix.c
new file mode 100644 (file)
index 0000000..a5dfd55
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2008, 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "stream.h"
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <netdb.h>
+#include <poll.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "packets.h"
+#include "poll-loop.h"
+#include "socket-util.h"
+#include "util.h"
+#include "stream-provider.h"
+#include "stream-fd.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_stream_unix
+
+/* Active UNIX socket. */
+
+/* Number of unix sockets created so far, to ensure binding path uniqueness. */
+static int n_unix_sockets;
+
+static int
+unix_open(const char *name, char *suffix, struct stream **streamp)
+{
+    const char *connect_path = suffix;
+    char *bind_path;
+    int fd;
+
+    bind_path = xasprintf("/tmp/stream-unix.%ld.%d",
+                          (long int) getpid(), n_unix_sockets++);
+    fd = make_unix_socket(SOCK_STREAM, true, false, bind_path, connect_path);
+    if (fd < 0) {
+        VLOG_ERR("%s: connection to %s failed: %s",
+                 bind_path, connect_path, strerror(-fd));
+        free(bind_path);
+        return -fd;
+    }
+
+    return new_fd_stream(name, fd, check_connection_completion(fd),
+                         bind_path, streamp);
+}
+
+struct stream_class unix_stream_class = {
+    "unix",                     /* name */
+    unix_open,                  /* open */
+    NULL,                       /* close */
+    NULL,                       /* connect */
+    NULL,                       /* recv */
+    NULL,                       /* send */
+    NULL,                       /* wait */
+};
+\f
+/* Passive UNIX socket. */
+
+static int punix_accept(int fd, const struct sockaddr *sa, size_t sa_len,
+                        struct stream **streamp);
+
+static int
+punix_open(const char *name UNUSED, char *suffix, struct pstream **pstreamp)
+{
+    int fd, error;
+
+    fd = make_unix_socket(SOCK_STREAM, true, true, suffix, NULL);
+    if (fd < 0) {
+        VLOG_ERR("%s: binding failed: %s", suffix, strerror(errno));
+        return errno;
+    }
+
+    error = set_nonblocking(fd);
+    if (error) {
+        close(fd);
+        return error;
+    }
+
+    if (listen(fd, 10) < 0) {
+        error = errno;
+        VLOG_ERR("%s: listen: %s", name, strerror(error));
+        close(fd);
+        return error;
+    }
+
+    return new_fd_pstream("punix", fd, punix_accept,
+                          xstrdup(suffix), pstreamp);
+}
+
+static int
+punix_accept(int fd, const struct sockaddr *sa, size_t sa_len,
+             struct stream **streamp)
+{
+    const struct sockaddr_un *sun = (const struct sockaddr_un *) sa;
+    int name_len = get_unix_name_len(sa_len);
+    char name[128];
+
+    if (name_len > 0) {
+        snprintf(name, sizeof name, "unix:%.*s", name_len, sun->sun_path);
+    } else {
+        strcpy(name, "unix");
+    }
+    return new_fd_stream(name, fd, 0, NULL, streamp);
+}
+
+struct pstream_class punix_pstream_class = {
+    "punix",
+    punix_open,
+    NULL,
+    NULL,
+    NULL
+};
+
diff --git a/lib/stream.c b/lib/stream.c
new file mode 100644 (file)
index 0000000..23b25f2
--- /dev/null
@@ -0,0 +1,511 @@
+/*
+ * Copyright (c) 2008, 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "stream-provider.h"
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <string.h>
+#include "coverage.h"
+#include "dynamic-string.h"
+#include "flow.h"
+#include "ofp-print.h"
+#include "ofpbuf.h"
+#include "openflow/nicira-ext.h"
+#include "openflow/openflow.h"
+#include "packets.h"
+#include "poll-loop.h"
+#include "random.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_stream
+#include "vlog.h"
+
+/* State of an active stream.*/
+enum stream_state {
+    SCS_CONNECTING,             /* Underlying stream is not connected. */
+    SCS_CONNECTED,              /* Connection established. */
+    SCS_DISCONNECTED            /* Connection failed or connection closed. */
+};
+
+static struct stream_class *stream_classes[] = {
+    &tcp_stream_class,
+    &unix_stream_class,
+};
+
+static struct pstream_class *pstream_classes[] = {
+    &ptcp_pstream_class,
+    &punix_pstream_class,
+};
+
+/* Check the validity of the stream class structures. */
+static void
+check_stream_classes(void)
+{
+#ifndef NDEBUG
+    size_t i;
+
+    for (i = 0; i < ARRAY_SIZE(stream_classes); i++) {
+        struct stream_class *class = stream_classes[i];
+        assert(class->name != NULL);
+        assert(class->open != NULL);
+        if (class->close || class->recv || class->send || class->wait) {
+            assert(class->close != NULL);
+            assert(class->recv != NULL);
+            assert(class->send != NULL);
+            assert(class->wait != NULL);
+        } else {
+            /* This class delegates to another one. */
+        }
+    }
+
+    for (i = 0; i < ARRAY_SIZE(pstream_classes); i++) {
+        struct pstream_class *class = pstream_classes[i];
+        assert(class->name != NULL);
+        assert(class->listen != NULL);
+        if (class->close || class->accept || class->wait) {
+            assert(class->close != NULL);
+            assert(class->accept != NULL);
+            assert(class->wait != NULL);
+        } else {
+            /* This class delegates to another one. */
+        }
+    }
+#endif
+}
+
+/* Prints information on active (if 'active') and passive (if 'passive')
+ * connection methods supported by the stream. */
+void
+stream_usage(const char *name, bool active, bool passive)
+{
+    /* Really this should be implemented via callbacks into the stream
+     * providers, but that seems too heavy-weight to bother with at the
+     * moment. */
+
+    printf("\n");
+    if (active) {
+        printf("Active %s connection methods:\n", name);
+        printf("  tcp:IP:PORT             "
+               "PORT at remote IP\n");
+        printf("  unix:FILE               "
+               "Unix domain socket named FILE\n");
+    }
+
+    if (passive) {
+        printf("Passive %s connection methods:\n", name);
+        printf("  ptcp:PORT[:IP]          "
+               "listen to TCP PORT on IP\n");
+        printf("  punix:FILE              "
+               "listen on Unix domain socket FILE\n");
+    }
+}
+
+/* Attempts to connect a stream to a remote peer.  'name' is a connection name
+ * in the form "TYPE:ARGS", where TYPE is an active stream class's name and
+ * ARGS are stream class-specific.
+ *
+ * Returns 0 if successful, otherwise a positive errno value.  If successful,
+ * stores a pointer to the new connection in '*streamp', otherwise a null
+ * pointer.  */
+int
+stream_open(const char *name, struct stream **streamp)
+{
+    size_t prefix_len;
+    size_t i;
+
+    COVERAGE_INC(stream_open);
+    check_stream_classes();
+
+    *streamp = NULL;
+    prefix_len = strcspn(name, ":");
+    if (prefix_len == strlen(name)) {
+        return EAFNOSUPPORT;
+    }
+    for (i = 0; i < ARRAY_SIZE(stream_classes); i++) {
+        struct stream_class *class = stream_classes[i];
+        if (strlen(class->name) == prefix_len
+            && !memcmp(class->name, name, prefix_len)) {
+            struct stream *stream;
+            char *suffix_copy = xstrdup(name + prefix_len + 1);
+            int retval = class->open(name, suffix_copy, &stream);
+            free(suffix_copy);
+            if (!retval) {
+                assert(stream->state != SCS_CONNECTING
+                       || stream->class->connect);
+                *streamp = stream;
+            }
+            return retval;
+        }
+    }
+    return EAFNOSUPPORT;
+}
+
+int
+stream_open_block(const char *name, struct stream **streamp)
+{
+    struct stream *stream;
+    int error;
+
+    error = stream_open(name, &stream);
+    while (error == EAGAIN) {
+        stream_connect_wait(stream);
+        poll_block();
+        error = stream_connect(stream);
+        assert(error != EINPROGRESS);
+    }
+    if (error) {
+        stream_close(stream);
+        *streamp = NULL;
+    } else {
+        *streamp = stream;
+    }
+    return error;
+}
+
+/* Closes 'stream'. */
+void
+stream_close(struct stream *stream)
+{
+    if (stream != NULL) {
+        char *name = stream->name;
+        (stream->class->close)(stream);
+        free(name);
+    }
+}
+
+/* Returns the name of 'stream', that is, the string passed to
+ * stream_open(). */
+const char *
+stream_get_name(const struct stream *stream)
+{
+    return stream ? stream->name : "(null)";
+}
+
+/* Returns the IP address of the peer, or 0 if the peer is not connected over
+ * an IP-based protocol or if its IP address is not yet known. */
+uint32_t
+stream_get_remote_ip(const struct stream *stream)
+{
+    return stream->remote_ip;
+}
+
+/* Returns the transport port of the peer, or 0 if the connection does not
+ * contain a port or if the port is not yet known. */
+uint16_t
+stream_get_remote_port(const struct stream *stream)
+{
+    return stream->remote_port;
+}
+
+/* Returns the IP address used to connect to the peer, or 0 if the connection
+ * is not an IP-based protocol or if its IP address is not yet known. */
+uint32_t
+stream_get_local_ip(const struct stream *stream)
+{
+    return stream->local_ip;
+}
+
+/* Returns the transport port used to connect to the peer, or 0 if the
+ * connection does not contain a port or if the port is not yet known. */
+uint16_t
+stream_get_local_port(const struct stream *stream)
+{
+    return stream->local_port;
+}
+
+static void
+scs_connecting(struct stream *stream)
+{
+    int retval = (stream->class->connect)(stream);
+    assert(retval != EINPROGRESS);
+    if (!retval) {
+        stream->state = SCS_CONNECTED;
+    } else if (retval != EAGAIN) {
+        stream->state = SCS_DISCONNECTED;
+        stream->error = retval;
+    }
+}
+
+/* Tries to complete the connection on 'stream', which must be an active
+ * stream.  If 'stream''s connection is complete, returns 0 if the connection
+ * was successful or a positive errno value if it failed.  If the
+ * connection is still in progress, returns EAGAIN. */
+int
+stream_connect(struct stream *stream)
+{
+    enum stream_state last_state;
+
+    do {
+        last_state = stream->state;
+        switch (stream->state) {
+        case SCS_CONNECTING:
+            scs_connecting(stream);
+            break;
+
+        case SCS_CONNECTED:
+            return 0;
+
+        case SCS_DISCONNECTED:
+            return stream->error;
+
+        default:
+            NOT_REACHED();
+        }
+    } while (stream->state != last_state);
+
+    return EAGAIN;
+}
+
+/* Tries to receive up to 'n' bytes from 'stream' into 'buffer', and returns:
+ *
+ *     - If successful, the number of bytes received (between 1 and 'n').
+ *
+ *     - On error, a negative errno value.
+ *
+ *     - 0, if the connection has been closed in the normal fashion, or if 'n'
+ *       is zero.
+ *
+ * The recv function will not block waiting for a packet to arrive.  If no
+ * data have been received, it returns -EAGAIN immediately. */
+int
+stream_recv(struct stream *stream, void *buffer, size_t n)
+{
+    int retval = stream_connect(stream);
+    return (retval ? -retval
+            : n == 0 ? 0
+            : (stream->class->recv)(stream, buffer, n));
+}
+
+/* Tries to send up to 'n' bytes of 'buffer' on 'stream', and returns:
+ *
+ *     - If successful, the number of bytes sent (between 1 and 'n').  0 is
+ *       only a valid return value if 'n' is 0.
+ *
+ *     - On error, a negative errno value.
+ *
+ * The send function will not block.  If no bytes can be immediately accepted
+ * for transmission, it returns -EAGAIN immediately. */
+int
+stream_send(struct stream *stream, const void *buffer, size_t n)
+{
+    int retval = stream_connect(stream);
+    return (retval ? -retval
+            : n == 0 ? 0
+            : (stream->class->send)(stream, buffer, n));
+}
+
+void
+stream_wait(struct stream *stream, enum stream_wait_type wait)
+{
+    assert(wait == STREAM_CONNECT || wait == STREAM_RECV
+           || wait == STREAM_SEND);
+
+    switch (stream->state) {
+    case SCS_CONNECTING:
+        wait = STREAM_CONNECT;
+        break;
+
+    case SCS_DISCONNECTED:
+        poll_immediate_wake();
+        return;
+    }
+    (stream->class->wait)(stream, wait);
+}
+
+void
+stream_connect_wait(struct stream *stream)
+{
+    stream_wait(stream, STREAM_CONNECT);
+}
+
+void
+stream_recv_wait(struct stream *stream)
+{
+    stream_wait(stream, STREAM_RECV);
+}
+
+void
+stream_send_wait(struct stream *stream)
+{
+    stream_wait(stream, STREAM_SEND);
+}
+
+/* Attempts to start listening for remote stream connections.  'name' is a
+ * connection name in the form "TYPE:ARGS", where TYPE is an passive stream
+ * class's name and ARGS are stream class-specific.
+ *
+ * Returns 0 if successful, otherwise a positive errno value.  If successful,
+ * stores a pointer to the new connection in '*pstreamp', otherwise a null
+ * pointer.  */
+int
+pstream_open(const char *name, struct pstream **pstreamp)
+{
+    size_t prefix_len;
+    size_t i;
+
+    check_stream_classes();
+
+    *pstreamp = NULL;
+    prefix_len = strcspn(name, ":");
+    if (prefix_len == strlen(name)) {
+        return EAFNOSUPPORT;
+    }
+    for (i = 0; i < ARRAY_SIZE(pstream_classes); i++) {
+        struct pstream_class *class = pstream_classes[i];
+        if (strlen(class->name) == prefix_len
+            && !memcmp(class->name, name, prefix_len)) {
+            char *suffix_copy = xstrdup(name + prefix_len + 1);
+            int retval = class->listen(name, suffix_copy, pstreamp);
+            free(suffix_copy);
+            if (retval) {
+                *pstreamp = NULL;
+            }
+            return retval;
+        }
+    }
+    return EAFNOSUPPORT;
+}
+
+/* Returns the name that was used to open 'pstream'.  The caller must not
+ * modify or free the name. */
+const char *
+pstream_get_name(const struct pstream *pstream)
+{
+    return pstream->name;
+}
+
+/* Closes 'pstream'. */
+void
+pstream_close(struct pstream *pstream)
+{
+    if (pstream != NULL) {
+        char *name = pstream->name;
+        (pstream->class->close)(pstream);
+        free(name);
+    }
+}
+
+/* Tries to accept a new connection on 'pstream'.  If successful, stores the
+ * new connection in '*new_stream' and returns 0.  Otherwise, returns a
+ * positive errno value.
+ *
+ * pstream_accept() will not block waiting for a connection.  If no connection
+ * is ready to be accepted, it returns EAGAIN immediately. */
+int
+pstream_accept(struct pstream *pstream, struct stream **new_stream)
+{
+    int retval = (pstream->class->accept)(pstream, new_stream);
+    if (retval) {
+        *new_stream = NULL;
+    } else {
+        assert((*new_stream)->state != SCS_CONNECTING
+               || (*new_stream)->class->connect);
+    }
+    return retval;
+}
+
+/* Tries to accept a new connection on 'pstream'.  If successful, stores the
+ * new connection in '*new_stream' and returns 0.  Otherwise, returns a
+ * positive errno value.
+ *
+ * pstream_accept_block() blocks until a connection is ready or until an error
+ * occurs.  It will not return EAGAIN. */
+int
+pstream_accept_block(struct pstream *pstream, struct stream **new_stream)
+{
+    int error;
+
+    while ((error = pstream_accept(pstream, new_stream)) == EAGAIN) {
+        pstream_wait(pstream);
+        poll_block();
+    }
+    if (error) {
+        *new_stream = NULL;
+    }
+    return error;
+}
+
+void
+pstream_wait(struct pstream *pstream)
+{
+    (pstream->class->wait)(pstream);
+}
+\f
+/* Initializes 'stream' as a new stream named 'name', implemented via 'class'.
+ * The initial connection status, supplied as 'connect_status', is interpreted
+ * as follows:
+ *
+ *      - 0: 'stream' is connected.  Its 'send' and 'recv' functions may be
+ *        called in the normal fashion.
+ *
+ *      - EAGAIN: 'stream' is trying to complete a connection.  Its 'connect'
+ *        function should be called to complete the connection.
+ *
+ *      - Other positive errno values indicate that the connection failed with
+ *        the specified error.
+ *
+ * After calling this function, stream_close() must be used to destroy
+ * 'stream', otherwise resources will be leaked.
+ *
+ * The caller retains ownership of 'name'. */
+void
+stream_init(struct stream *stream, struct stream_class *class,
+            int connect_status, const char *name)
+{
+    stream->class = class;
+    stream->state = (connect_status == EAGAIN ? SCS_CONNECTING
+                    : !connect_status ? SCS_CONNECTED
+                    : SCS_DISCONNECTED);
+    stream->error = connect_status;
+    stream->name = xstrdup(name);
+}
+
+void
+stream_set_remote_ip(struct stream *stream, uint32_t ip)
+{
+    stream->remote_ip = ip;
+}
+
+void
+stream_set_remote_port(struct stream *stream, uint16_t port)
+{
+    stream->remote_port = port;
+}
+
+void
+stream_set_local_ip(struct stream *stream, uint32_t ip)
+{
+    stream->local_ip = ip;
+}
+
+void
+stream_set_local_port(struct stream *stream, uint16_t port)
+{
+    stream->local_port = port;
+}
+
+void
+pstream_init(struct pstream *pstream, struct pstream_class *class,
+            const char *name)
+{
+    pstream->class = class;
+    pstream->name = xstrdup(name);
+}
diff --git a/lib/stream.h b/lib/stream.h
new file mode 100644 (file)
index 0000000..7a62a5a
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef STREAM_H
+#define STREAM_H 1
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "flow.h"
+
+struct pstream;
+struct stream;
+
+void stream_usage(const char *name, bool active, bool passive);
+
+/* Bidirectional byte streams. */
+int stream_open(const char *name, struct stream **);
+int stream_open_block(const char *name, struct stream **);
+void stream_close(struct stream *);
+const char *stream_get_name(const struct stream *);
+uint32_t stream_get_remote_ip(const struct stream *);
+uint16_t stream_get_remote_port(const struct stream *);
+uint32_t stream_get_local_ip(const struct stream *);
+uint16_t stream_get_local_port(const struct stream *);
+int stream_connect(struct stream *);
+int stream_recv(struct stream *, void *buffer, size_t n);
+int stream_send(struct stream *, const void *buffer, size_t n);
+
+enum stream_wait_type {
+    STREAM_CONNECT,
+    STREAM_RECV,
+    STREAM_SEND
+};
+void stream_wait(struct stream *, enum stream_wait_type);
+void stream_connect_wait(struct stream *);
+void stream_recv_wait(struct stream *);
+void stream_send_wait(struct stream *);
+
+/* Passive streams: listeners for incoming stream connections. */
+int pstream_open(const char *name, struct pstream **);
+const char *pstream_get_name(const struct pstream *);
+void pstream_close(struct pstream *);
+int pstream_accept(struct pstream *, struct stream **);
+int pstream_accept_block(struct pstream *, struct stream **);
+void pstream_wait(struct pstream *);
+
+#endif /* stream.h */
index 9d0f313..bc3df23 100644 (file)
@@ -59,6 +59,12 @@ svec_clear(struct svec *svec)
     svec->n = 0;
 }
 
+bool
+svec_is_empty(const struct svec *svec)
+{
+    return svec->n == 0;
+}
+
 void
 svec_add(struct svec *svec, const char *name)
 {
index ac22662..2a93139 100644 (file)
@@ -32,6 +32,7 @@ void svec_init(struct svec *);
 void svec_clone(struct svec *, const struct svec *);
 void svec_destroy(struct svec *);
 void svec_clear(struct svec *);
+bool svec_is_empty(const struct svec *);
 void svec_add(struct svec *, const char *);
 void svec_add_nocopy(struct svec *, char *);
 void svec_del(struct svec *, const char *);
index 8ad8d06..5e42387 100644 (file)
@@ -44,6 +44,7 @@ static struct timeval now;
 static time_t deadline = TIME_MIN;
 
 static void set_up_timer(void);
+static void set_up_signal(int flags);
 static void sigalrm_handler(int);
 static void refresh_if_ticked(void);
 static time_t time_add(time_t, time_t);
@@ -51,13 +52,11 @@ static void block_sigalrm(sigset_t *);
 static void unblock_sigalrm(const sigset_t *);
 static void log_poll_interval(long long int last_wakeup,
                               const struct rusage *last_rusage);
-static long long int timeval_to_msec(const struct timeval *);
 
 /* Initializes the timetracking module. */
 void
 time_init(void)
 {
-    struct sigaction sa;
     if (inited) {
         return;
     }
@@ -68,17 +67,49 @@ time_init(void)
     gettimeofday(&now, NULL);
     tick = false;
 
-    /* Set up signal handler. */
+    set_up_signal(SA_RESTART);
+    set_up_timer();
+}
+
+static void
+set_up_signal(int flags)
+{
+    struct sigaction sa;
+
     memset(&sa, 0, sizeof sa);
     sa.sa_handler = sigalrm_handler;
     sigemptyset(&sa.sa_mask);
-    sa.sa_flags = SA_RESTART;
+    sa.sa_flags = flags;
     if (sigaction(SIGALRM, &sa, NULL)) {
         ovs_fatal(errno, "sigaction(SIGALRM) failed");
     }
+}
 
-    /* Set up periodic signal. */
-    set_up_timer();
+/* Remove SA_RESTART from the flags for SIGALRM, so that any system call that
+ * is interrupted by the periodic timer interrupt will return EINTR instead of
+ * continuing after the signal handler returns.
+ *
+ * time_disable_restart() and time_enable_restart() may be usefully wrapped
+ * around function calls that might otherwise block forever unless interrupted
+ * by a signal, e.g.:
+ *
+ *   time_disable_restart();
+ *   fcntl(fd, F_SETLKW, &lock);
+ *   time_enable_restart();
+ */
+void
+time_disable_restart(void)
+{
+    set_up_signal(0);
+}
+
+/* Add SA_RESTART to the flags for SIGALRM, so that any system call that
+ * is interrupted by the periodic timer interrupt will continue after the
+ * signal handler returns instead of returning EINTR. */
+void
+time_enable_restart(void)
+{
+    set_up_signal(SA_RESTART);
 }
 
 static void
@@ -255,7 +286,7 @@ unblock_sigalrm(const sigset_t *oldsigs)
     }
 }
 
-static long long int
+long long int
 timeval_to_msec(const struct timeval *tv)
 {
     return (long long int) tv->tv_sec * 1000 + tv->tv_usec / 1000;
index 8567d75..5ba903e 100644 (file)
@@ -41,6 +41,8 @@ BUILD_ASSERT_DECL(TYPE_IS_SIGNED(time_t));
 #define TIME_UPDATE_INTERVAL 100
 
 void time_init(void);
+void time_disable_restart(void);
+void time_enable_restart(void);
 void time_postfork(void);
 void time_refresh(void);
 time_t time_now(void);
@@ -49,4 +51,6 @@ void time_timeval(struct timeval *);
 void time_alarm(unsigned int secs);
 int time_poll(struct pollfd *, int n_pollfds, int timeout);
 
+long long int timeval_to_msec(const struct timeval *);
+
 #endif /* timeval.h */
diff --git a/lib/unicode.c b/lib/unicode.c
new file mode 100644 (file)
index 0000000..69ebcfc
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "unicode.h"
+
+/* Returns the unicode code point corresponding to leading surrogate 'leading'
+ * and trailing surrogate 'trailing'.  The return value will not make any
+ * sense if 'leading' or 'trailing' are not in the correct ranges for leading
+ * or trailing surrogates. */
+int
+utf16_decode_surrogate_pair(int leading, int trailing)
+{
+    /*
+     *  Leading surrogate:         110110wwwwxxxxxx
+     * Trailing surrogate:         110111xxxxxxxxxx
+     *         Code point: 000uuuuuxxxxxxxxxxxxxxxx
+     */
+    int w = (leading >> 6) & 0xf;
+    int u = w + 1;
+    int x0 = leading & 0x3f;
+    int x1 = trailing & 0x3ff;
+    return (u << 16) | (x0 << 10) | x1;
+}
diff --git a/lib/unicode.h b/lib/unicode.h
new file mode 100644 (file)
index 0000000..0f20bdc
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef UNICODE_H
+#define UNICODE_H 1
+
+#include <stdbool.h>
+
+/* Returns true if 'c' is a Unicode code point, otherwise false. */
+static inline bool
+uc_is_code_point(int c)
+{
+    return c >= 0 && c <= 0x10ffff;
+}
+
+/* Returns true if 'c' is a Unicode code point for a leading surrogate. */
+static inline bool
+uc_is_leading_surrogate(int c)
+{
+    return c >= 0xd800 && c <= 0xdbff;
+}
+
+/* Returns true if 'c' is a Unicode code point for a trailing surrogate. */
+static inline bool
+uc_is_trailing_surrogate(int c)
+{
+    return c >= 0xdc00 && c <= 0xdfff;
+}
+
+/* Returns true if 'c' is a Unicode code point for a leading or trailing
+ * surrogate. */
+static inline bool
+uc_is_surrogate(int c)
+{
+    return c >= 0xd800 && c <= 0xdfff;
+}
+
+int utf16_decode_surrogate_pair(int leading, int trailing);
+
+#endif /* unicode.h */
index 164a7db..6378439 100644 (file)
@@ -44,7 +44,8 @@
 #include "vlog.h"
 \f
 struct unixctl_command {
-    void (*cb)(struct unixctl_conn *, const char *args);
+    unixctl_cb_func *cb;
+    void *aux;
 };
 
 struct unixctl_conn {
@@ -76,7 +77,8 @@ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
 static struct shash commands = SHASH_INITIALIZER(&commands);
 
 static void
-unixctl_help(struct unixctl_conn *conn, const char *args UNUSED)
+unixctl_help(struct unixctl_conn *conn, const char *args UNUSED,
+             void *aux UNUSED)
 {
     struct ds ds = DS_EMPTY_INITIALIZER;
     struct shash_node *node;
@@ -90,8 +92,7 @@ unixctl_help(struct unixctl_conn *conn, const char *args UNUSED)
 }
 
 void
-unixctl_command_register(const char *name,
-                         void (*cb)(struct unixctl_conn *, const char *args))
+unixctl_command_register(const char *name, unixctl_cb_func *cb, void *aux)
 {
     struct unixctl_command *command;
 
@@ -99,6 +100,7 @@ unixctl_command_register(const char *name,
            || shash_find_data(&commands, name) == cb);
     command = xmalloc(sizeof *command);
     command->cb = cb;
+    command->aux = aux;
     shash_add(&commands, name, command);
 }
 
@@ -178,7 +180,7 @@ unixctl_server_create(const char *path, struct unixctl_server **serverp)
     struct unixctl_server *server;
     int error;
 
-    unixctl_command_register("help", unixctl_help);
+    unixctl_command_register("help", unixctl_help, NULL);
 
     server = xmalloc(sizeof *server);
     list_init(&server->conns);
@@ -282,7 +284,7 @@ process_command(struct unixctl_conn *conn, char *s)
 
     command = shash_find_data(&commands, name);
     if (command) {
-        command->cb(conn, args);
+        command->cb(conn, args, command->aux);
     } else {
         char *msg = xasprintf("\"%s\" is not a valid command", name);
         unixctl_command_reply(conn, 400, msg);
index 18748aa..0b6cbf3 100644 (file)
@@ -35,9 +35,10 @@ const char *unixctl_client_target(const struct unixctl_client *);
 
 /* Command registration. */
 struct unixctl_conn;
+typedef void unixctl_cb_func(struct unixctl_conn *,
+                             const char *args, void *aux);
 void unixctl_command_register(const char *name,
-                              void (*cb)(struct unixctl_conn *,
-                                         const char *args));
+                              unixctl_cb_func *cb, void *aux);
 void unixctl_command_reply(struct unixctl_conn *, int code,
                            const char *body);
 
index f766d59..65cb360 100644 (file)
@@ -42,6 +42,12 @@ xcalloc(size_t count, size_t size)
     return p;
 }
 
+void *
+xzalloc(size_t size)
+{
+    return xcalloc(1, size);
+}
+
 void *
 xmalloc(size_t size) 
 {
@@ -162,8 +168,10 @@ ovs_error(int err_no, const char *format, ...)
     va_start(args, format);
     vfprintf(stderr, format, args);
     va_end(args);
-    if (err_no != 0)
-        fprintf(stderr, " (%s)", strerror(err_no));
+    if (err_no != 0) {
+        fprintf(stderr, " (%s)",
+                err_no == EOF ? "end of file" : strerror(err_no));
+    }
     putc('\n', stderr);
 
     errno = save_errno;
@@ -294,3 +302,84 @@ str_to_ullong(const char *s, int base, unsigned long long *ull)
 {
     return str_to_llong(s, base, (long long *) ull);
 }
+
+/* Converts floating-point string 's' into a double.  If successful, stores
+ * the double in '*d' and returns true; on failure, stores 0 in '*d' and
+ * returns false.
+ *
+ * Underflow (e.g. "1e-9999") is not considered an error, but overflow
+ * (e.g. "1e9999)" is. */
+bool
+str_to_double(const char *s, double *d)
+{
+    int save_errno = errno;
+    char *tail;
+    errno = 0;
+    *d = strtod(s, &tail);
+    if (errno == EINVAL || (errno == ERANGE && *d != 0)
+        || tail == s || *tail != '\0') {
+        errno = save_errno;
+        *d = 0;
+        return false;
+    } else {
+        errno = save_errno;
+        return true;
+    }
+}
+
+/* Returns the value of 'c' as a hexadecimal digit. */
+int
+hexit_value(int c)
+{
+    switch (c) {
+    case '0': case '1': case '2': case '3': case '4':
+    case '5': case '6': case '7': case '8': case '9':
+        return c - '0';
+
+    case 'a': case 'A':
+        return 0xa;
+
+    case 'b': case 'B':
+        return 0xb;
+
+    case 'c': case 'C':
+        return 0xc;
+
+    case 'd': case 'D':
+        return 0xd;
+
+    case 'e': case 'E':
+        return 0xe;
+
+    case 'f': case 'F':
+        return 0xf;
+    }
+
+    NOT_REACHED();
+}
+
+/* Returns the directory name portion of 'file_name' as a malloc()'d string,
+ * similar to the POSIX dirname() function but thread-safe. */
+char *
+dir_name(const char *file_name)
+{
+    size_t len = strlen(file_name);
+    while (len > 0 && file_name[len - 1] == '/') {
+        len--;
+    }
+    while (len > 0 && file_name[len - 1] != '/') {
+        len--;
+    }
+    while (len > 0 && file_name[len - 1] == '/') {
+        len--;
+    }
+    if (!len) {
+        return xstrdup((file_name[0] == '/'
+                        && file_name[1] == '/'
+                        && file_name[2] != '/') ? "//"
+                       : file_name[0] == '/' ? "/"
+                       : ".");
+    } else {
+        return xmemdup0(file_name, len);
+    }
+}
index 962bad2..9e7b88f 100644 (file)
@@ -98,6 +98,7 @@ void ovs_print_version(char *date, char *time,
 void out_of_memory(void) NO_RETURN;
 void *xmalloc(size_t) MALLOC_LIKE;
 void *xcalloc(size_t, size_t) MALLOC_LIKE;
+void *xzalloc(size_t) MALLOC_LIKE;
 void *xrealloc(void *, size_t);
 void *xmemdup(const void *, size_t) MALLOC_LIKE;
 char *xmemdup0(const char *, size_t) MALLOC_LIKE;
@@ -120,6 +121,12 @@ bool str_to_uint(const char *, int base, unsigned int *);
 bool str_to_ulong(const char *, int base, unsigned long *);
 bool str_to_ullong(const char *, int base, unsigned long long *);
 
+bool str_to_double(const char *, double *);
+
+int hexit_value(int c);
+
+char *dir_name(const char *file_name);
+
 #ifdef  __cplusplus
 }
 #endif
diff --git a/lib/uuid.c b/lib/uuid.c
new file mode 100644 (file)
index 0000000..264d9bf
--- /dev/null
@@ -0,0 +1,216 @@
+/* Copyright (c) 2008, 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "uuid.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "aes128.h"
+#include "sha1.h"
+#include "socket-util.h"
+#include "util.h"
+
+static struct aes128 key;
+static uint64_t counter[2];
+BUILD_ASSERT_DECL(sizeof counter == 16);
+
+static void do_init(void);
+static void read_urandom(void *buffer, size_t n);
+
+/*
+ * Initialize the UUID module.  Aborts the program with an error message if
+ * initialization fails (which should never happen on a properly configured
+ * machine.)
+ *
+ * Currently initialization is only needed by uuid_generate().  uuid_generate()
+ * will automatically call uuid_init() itself, so it's only necessary to call
+ * this function explicitly if you want to abort the program earlier than the
+ * first UUID generation in case of failure.
+ */
+void
+uuid_init(void)
+{
+    static bool inited;
+    if (!inited) {
+        do_init();
+        inited = true;
+    }
+}
+
+/* Generates a new random UUID in 'uuid'.
+ *
+ * We go to some trouble to ensure as best we can that the generated UUID has
+ * these properties:
+ *
+ *      - Uniqueness.  The random number generator is seeded using both the
+ *        system clock and the system random number generator, plus a few
+ *        other identifiers, which is about as good as we can get in any kind
+ *        of simple way.
+ *
+ *      - Unpredictability.  In some situations it could be bad for an
+ *        adversary to be able to guess the next UUID to be generated with some
+ *        probability of success.  This property may or may not be important
+ *        for our purposes, but it is better if we can get it.
+ *
+ * To ensure both of these, we start by taking our seed data and passing it
+ * through SHA-1.  We use the result as an AES-128 key.  We also generate a
+ * random 16-byte value[*] which we then use as the counter for CTR mode.  To
+ * generate a UUID in a manner compliant with the above goals, we merely
+ * increment the counter and encrypt it.
+ *
+ * [*] It is not actually important that the initial value of the counter be
+ *     random.  AES-128 in counter mode is secure either way.
+ */
+void
+uuid_generate(struct uuid *uuid)
+{
+    uuid_init();
+
+    /* Increment the counter. */
+    if (++counter[1] == 0) {
+        counter[0]++;
+    }
+
+    /* AES output is exactly 16 bytes, so we encrypt directly into 'uuid'. */
+    aes128_encrypt(&key, counter, uuid);
+
+    /* Set bits to indicate a random UUID.  See RFC 4122 section 4.4. */
+    uuid->parts[2] &= ~0xc0000000;
+    uuid->parts[2] |=  0x80000000;
+    uuid->parts[1] &= ~0x0000f000;
+    uuid->parts[1] |=  0x00004000;
+}
+
+/* Sets 'uuid' to all-zero-bits. */
+void
+uuid_zero(struct uuid *uuid)
+{
+    uuid->parts[0] = uuid->parts[1] = uuid->parts[2] = uuid->parts[3] = 0;
+}
+
+/* Compares 'a' and 'b'.  Returns a negative value if 'a < b', zero if 'a ==
+ * b', or positive if 'a > b'.  The ordering is lexicographical order of the
+ * conventional way of writing out UUIDs as strings. */
+int
+uuid_compare_3way(const struct uuid *a, const struct uuid *b)
+{
+    if (a->parts[0] != b->parts[0]) {
+        return a->parts[0] > b->parts[0] ? 1 : -1;
+    } else if (a->parts[1] != b->parts[1]) {
+        return a->parts[1] > b->parts[1] ? 1 : -1;
+    } else if (a->parts[2] != b->parts[2]) {
+        return a->parts[2] > b->parts[2] ? 1 : -1;
+    } else if (a->parts[3] != b->parts[3]) {
+        return a->parts[3] > b->parts[3] ? 1 : -1;
+    } else {
+        return 0;
+    }
+}
+
+/* Attempts to convert string 's' into a UUID in 'uuid'.  Returns true if
+ * successful, which will be the case only if 's' has the exact format
+ * specified by RFC 4122.  Returns false on failure.  On failure, 'uuid' will
+ * be set to all-zero-bits. */
+bool
+uuid_from_string(struct uuid *uuid, const char *s)
+{
+    static const char template[] = "00000000-1111-1111-2222-222233333333";
+    const char *t;
+
+    uuid_zero(uuid);
+    for (t = template; ; t++, s++) {
+        if (*t >= '0' && *t <= '3') {
+            uint32_t *part = &uuid->parts[*t - '0'];
+            if (!isxdigit(*s)) {
+                goto error;
+            }
+            *part = (*part << 4) + hexit_value(*s);
+        } else if (*t != *s) {
+            goto error;
+        } else if (*t == 0) {
+            return true;
+        }
+    }
+
+error:
+    uuid_zero(uuid);
+    return false;
+}
+\f
+static void
+read_urandom(void *buffer, size_t n)
+{
+    static const char urandom[] = "/dev/urandom";
+    size_t bytes_read;
+    int error;
+    int fd;
+
+    fd = open(urandom, O_RDONLY);
+    if (fd < 0) {
+        ovs_fatal(errno, "%s: open failed", urandom);
+    }
+    error = read_fully(fd, buffer, n, &bytes_read);
+    if (error == EOF) {
+        ovs_fatal(0, "%s: unexpected end of file", urandom);
+    } else if (error) {
+        ovs_fatal(error, "%s: read error", urandom);
+    }
+    close(fd);
+}
+
+static void
+do_init(void)
+{
+    uint8_t sha1[SHA1_DIGEST_SIZE];
+    struct sha1_ctx sha1_ctx;
+    uint8_t random_seed[16];
+    struct timeval now;
+    pid_t pid, ppid;
+    uid_t uid;
+    gid_t gid;
+
+    /* Get seed data. */
+    read_urandom(random_seed, sizeof random_seed);
+    if (gettimeofday(&now, NULL)) {
+        ovs_fatal(errno, "gettimeofday failed");
+    }
+    pid = getpid();
+    ppid = getppid();
+    uid = getuid();
+    gid = getgid();
+
+    /* Convert seed into key. */
+    sha1_init(&sha1_ctx);
+    sha1_update(&sha1_ctx, random_seed, sizeof random_seed);
+    sha1_update(&sha1_ctx, &pid, sizeof pid);
+    sha1_update(&sha1_ctx, &ppid, sizeof ppid);
+    sha1_update(&sha1_ctx, &uid, sizeof uid);
+    sha1_update(&sha1_ctx, &gid, sizeof gid);
+    sha1_final(&sha1_ctx, sha1);
+
+    /* Generate key. */
+    BUILD_ASSERT(sizeof sha1 >= 16);
+    aes128_schedule(&key, sha1);
+
+    /* Generate initial counter. */
+    read_urandom(counter, sizeof counter);
+}
diff --git a/lib/uuid.h b/lib/uuid.h
new file mode 100644 (file)
index 0000000..5dcaa25
--- /dev/null
@@ -0,0 +1,80 @@
+/* Copyright (c) 2008, 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef UUID_H
+#define UUID_H 1
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include "util.h"
+
+#define UUID_BIT 128            /* Number of bits in a UUID. */
+#define UUID_OCTET (UUID_BIT / 8) /* Number of bytes in a UUID. */
+
+/* A Universally Unique IDentifier (UUID) compliant with RFC 4122.
+ *
+ * Each of the parts is stored in host byte order, but the parts themselves are
+ * ordered from left to right.  That is, (parts[0] >> 24) is the first 8 bits
+ * of the UUID when output in the standard form, and (parts[3] & 0xff) is the
+ * final 8 bits. */
+struct uuid {
+    uint32_t parts[4];
+};
+BUILD_ASSERT_DECL(sizeof(struct uuid) == UUID_OCTET);
+
+/* Formats a UUID as a string, in the conventional format.
+ *
+ * Example:
+ *   struct uuid uuid = ...;
+ *   printf("This UUID is "UUID_FMT"\n", UUID_ARGS(&uuid));
+ *
+ */
+#define UUID_LEN 36
+#define UUID_FMT "%08x-%04x-%04x-%04x-%04x%08x"
+#define UUID_ARGS(UUID)                             \
+    ((unsigned int) ((UUID)->parts[0])),            \
+    ((unsigned int) ((UUID)->parts[1] >> 16)),      \
+    ((unsigned int) ((UUID)->parts[1] & 0xffff)),   \
+    ((unsigned int) ((UUID)->parts[2] >> 16)),      \
+    ((unsigned int) ((UUID)->parts[2] & 0xffff)),   \
+    ((unsigned int) ((UUID)->parts[3]))
+
+/* Returns a hash value for 'uuid'.  This hash value is the same regardless of
+ * whether we are running on a 32-bit or 64-bit or big-endian or little-endian
+ * architecture. */
+static inline size_t
+uuid_hash(const struct uuid *uuid)
+{
+    return uuid->parts[0];
+}
+
+/* Returns true if 'a == b', false otherwise. */
+static inline bool
+uuid_equals(const struct uuid *a, const struct uuid *b)
+{
+    return (a->parts[0] == b->parts[0]
+            && a->parts[1] == b->parts[1]
+            && a->parts[2] == b->parts[2]
+            && a->parts[3] == b->parts[3]);
+}
+
+void uuid_init(void);
+void uuid_generate(struct uuid *);
+void uuid_zero(struct uuid *);
+int uuid_compare_3way(const struct uuid *, const struct uuid *);
+bool uuid_from_string(struct uuid *, const char *);
+
+#endif /* vswitchd/cfgdb.h */
index 0d44e73..9a9ea06 100644 (file)
@@ -41,8 +41,10 @@ VLOG_MODULE(fatal_signal)
 VLOG_MODULE(fault)
 VLOG_MODULE(flow)
 VLOG_MODULE(in_band)
+VLOG_MODULE(jsonrpc)
 VLOG_MODULE(leak_checker)
 VLOG_MODULE(learning_switch)
+VLOG_MODULE(lockfile)
 VLOG_MODULE(mac_learning)
 VLOG_MODULE(mgmt)
 VLOG_MODULE(netdev)
@@ -53,6 +55,13 @@ VLOG_MODULE(ofctl)
 VLOG_MODULE(ovs_discover)
 VLOG_MODULE(ofproto)
 VLOG_MODULE(openflowd)
+VLOG_MODULE(ovsdb_client)
+VLOG_MODULE(ovsdb_file)
+VLOG_MODULE(ovsdb_idl)
+VLOG_MODULE(ovsdb_log)
+VLOG_MODULE(ovsdb_jsonrpc_server)
+VLOG_MODULE(ovsdb_server)
+VLOG_MODULE(ovsdb_tool)
 VLOG_MODULE(pktbuf)
 VLOG_MODULE(pcap)
 VLOG_MODULE(poll_loop)
@@ -60,8 +69,13 @@ VLOG_MODULE(port_watcher)
 VLOG_MODULE(proc_net_compat)
 VLOG_MODULE(process)
 VLOG_MODULE(rconn)
+VLOG_MODULE(reconnect)
 VLOG_MODULE(rtnetlink)
 VLOG_MODULE(stp)
+VLOG_MODULE(stream_fd)
+VLOG_MODULE(stream_tcp)
+VLOG_MODULE(stream_unix)
+VLOG_MODULE(stream)
 VLOG_MODULE(stats)
 VLOG_MODULE(status)
 VLOG_MODULE(svec)
diff --git a/lib/vlog-syn.man b/lib/vlog-syn.man
new file mode 100644 (file)
index 0000000..873b2f0
--- /dev/null
@@ -0,0 +1,6 @@
+.IP "Logging options:"
+[\fB-v\fR[\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]]]]\&...
+.br
+[\fB--verbose[=\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]]]]\&...
+.br
+[\fB--log-file\fR[\fB=\fIfile\fR]]
index 5496f01..b2d0c06 100644 (file)
@@ -385,7 +385,7 @@ vlog_set_verbosity(const char *arg)
 }
 
 static void
-vlog_unixctl_set(struct unixctl_conn *conn, const char *args)
+vlog_unixctl_set(struct unixctl_conn *conn, const char *args, void *aux UNUSED)
 {
     char *msg = vlog_set_levels_from_string(args);
     unixctl_command_reply(conn, msg ? 501 : 202, msg);
@@ -393,7 +393,8 @@ vlog_unixctl_set(struct unixctl_conn *conn, const char *args)
 }
 
 static void
-vlog_unixctl_list(struct unixctl_conn *conn, const char *args UNUSED)
+vlog_unixctl_list(struct unixctl_conn *conn,
+                  const char *args UNUSED, void *aux UNUSED)
 {
     char *msg = vlog_get_levels();
     unixctl_command_reply(conn, 200, msg);
@@ -401,7 +402,8 @@ vlog_unixctl_list(struct unixctl_conn *conn, const char *args UNUSED)
 }
 
 static void
-vlog_unixctl_reopen(struct unixctl_conn *conn, const char *args UNUSED)
+vlog_unixctl_reopen(struct unixctl_conn *conn,
+                    const char *args UNUSED, void *aux UNUSED)
 {
     if (log_file_name) {
         int error = vlog_reopen_log_file();
@@ -435,9 +437,9 @@ vlog_init(void)
         VLOG_ERR("current time is negative: %s (%ld)", s, (long int) now);
     }
 
-    unixctl_command_register("vlog/set", vlog_unixctl_set);
-    unixctl_command_register("vlog/list", vlog_unixctl_list);
-    unixctl_command_register("vlog/reopen", vlog_unixctl_reopen);
+    unixctl_command_register("vlog/set", vlog_unixctl_set, NULL);
+    unixctl_command_register("vlog/list", vlog_unixctl_list, NULL);
+    unixctl_command_register("vlog/reopen", vlog_unixctl_reopen, NULL);
 }
 
 /* Closes the logging subsystem. */
index 2868db5..c762506 100644 (file)
@@ -102,7 +102,7 @@ discovery_create(const char *re, bool update_resolv_conf,
     char local_name[IF_NAMESIZE];
     int error;
 
-    d = xcalloc(1, sizeof *d);
+    d = xzalloc(sizeof *d);
 
     /* Controller regular expression. */
     error = discovery_set_accept_controller_re(d, re);
index bc42ccf..cdbe5bd 100644 (file)
@@ -471,7 +471,7 @@ executer_create(const char *command_acl, const char *command_dir,
         return errno;
     }
 
-    e = xcalloc(1, sizeof *e);
+    e = xzalloc(sizeof *e);
     e->command_acl = xstrdup(command_acl);
     e->command_dir = (command_dir
                       ? xstrdup(command_dir)
index c39e5ca..30c24e6 100644 (file)
@@ -624,7 +624,7 @@ in_band_create(struct ofproto *ofproto, struct dpif *dpif,
         return error;
     }
 
-    in_band = xcalloc(1, sizeof *in_band);
+    in_band = xzalloc(sizeof *in_band);
     in_band->ofproto = ofproto;
     in_band->controller = controller;
     in_band->ss_cat = switch_status_register(ss, "in-band",
index 4995bbe..60e1828 100644 (file)
@@ -293,7 +293,7 @@ ofproto_create(const char *datapath, const struct ofhooks *ofhooks, void *aux,
     dpif_recv_purge(dpif);
 
     /* Initialize settings. */
-    p = xcalloc(1, sizeof *p);
+    p = xzalloc(sizeof *p);
     p->fallback_dpid = pick_fallback_dpid();
     p->datapath_id = p->fallback_dpid;
     p->manufacturer = xstrdup("Nicira Networks, Inc.");
@@ -1388,7 +1388,7 @@ rule_create(struct ofproto *ofproto, struct rule *super,
             const union ofp_action *actions, size_t n_actions,
             uint16_t idle_timeout, uint16_t hard_timeout)
 {
-    struct rule *rule = xcalloc(1, sizeof *rule);
+    struct rule *rule = xzalloc(sizeof *rule);
     rule->idle_timeout = idle_timeout;
     rule->hard_timeout = hard_timeout;
     rule->used = rule->created = time_msec();
@@ -2492,7 +2492,7 @@ query_stats(struct ofproto *p, struct rule *rule,
     byte_count = rule->byte_count;
 
     n_odp_flows = rule->cr.wc.wildcards ? list_size(&rule->list) : 1;
-    odp_flows = xcalloc(1, n_odp_flows * sizeof *odp_flows);
+    odp_flows = xzalloc(n_odp_flows * sizeof *odp_flows);
     if (rule->cr.wc.wildcards) {
         size_t i = 0;
         LIST_FOR_EACH (subrule, struct rule, list, &rule->list) {
index 0afd22f..a4f5bfa 100644 (file)
@@ -227,7 +227,7 @@ pinsched_create(int rate_limit, int burst_limit, struct switch_status *ss)
 {
     struct pinsched *ps;
 
-    ps = xcalloc(1, sizeof *ps);
+    ps = xzalloc(sizeof *ps);
     port_array_init(&ps->queues);
     ps->n_queued = 0;
     ps->last_tx_port = PORT_ARRAY_SIZE;
index 450cc3b..3701aeb 100644 (file)
@@ -63,7 +63,7 @@ pktbuf_capacity(void)
 struct pktbuf *
 pktbuf_create(void)
 {
-    return xcalloc(1, sizeof *pktbuf_create());
+    return xzalloc(sizeof *pktbuf_create());
 }
 
 void
index b2cb935..5e61888 100644 (file)
@@ -177,7 +177,7 @@ switch_status_cb(struct status_reply *sr, void *ss_)
 struct switch_status *
 switch_status_create(const struct ofproto *ofproto)
 {
-    struct switch_status *ss = xcalloc(1, sizeof *ss);
+    struct switch_status *ss = xzalloc(sizeof *ss);
     ss->booted = time_now();
     list_init(&ss->categories);
     ss->config_cat = switch_status_register(ss, "config", config_status_cb,
diff --git a/ovsdb/SPECS b/ovsdb/SPECS
new file mode 100644 (file)
index 0000000..3acf32d
--- /dev/null
@@ -0,0 +1,785 @@
+         ===================================================
+          Open vSwitch Configuration Database Specification
+         ===================================================
+
+Basic Notation
+--------------
+
+The descriptions below use the following shorthand notations for JSON
+values.  Additional notation is presented later.
+
+<string>
+
+    A JSON string.
+
+<id>
+
+    A JSON string matching [a-zA-Z_][a-zA-Z0-9_]*.
+
+    <id>s that begin with _ are reserved to the implementation and may
+    not be used by the user.
+
+<boolean>
+
+    A JSON true or false value.
+
+<number>
+
+    A JSON number.
+
+<integer>
+
+    A JSON number with an integer value, within a certain range
+    (currently -2**63...+2**63-1).
+
+<value>
+
+    Any JSON value.
+
+Schema Format
+-------------
+
+An Open vSwitch configuration database consists of a set of tables,
+each of which has a number of columns and zero or more rows.  A schema
+is represented by <database-schema>, as described below.
+
+<database-schema>
+
+    A JSON object with the following members:
+
+        "name": <id>                            required
+        "comment": <string>                     optional
+        "tables": {<id>: <table-schema>, ...}   required
+
+    The "name" identifies the database as a whole.  The "comment"
+    optionally provides more information about the database.  The
+    value of "tables" is a JSON object whose names are table names and
+    whose values are <table-schema>s.
+
+<table-schema>
+
+    A JSON object with the following members:
+
+        "comment": <string>                       optional
+        "columns": {<id>: <column-schema>, ...}   required
+
+    The "comment" optionally provides information about this table for
+    a human reader.  The value of "columns" is a JSON object whose
+    names are column names and whose values are <column-schema>s.
+
+    Every table has the following columns whose definitions are not
+    included in the schema:
+
+        "_uuid": This column, which contains exactly one UUID value,
+        is initialized to a random value by the database engine when
+        it creates a row.  It is read-only, and its value never
+        changes during the lifetime of a row.
+
+        "_version": Like "_uuid", this column contains exactly one
+        UUID value, initialized to a random value by the database
+        engine when it creates a row, and it is read-only.  However,
+        its value changes to a new random value whenever any other
+        field in the row changes.  Furthermore, its value is
+        ephemeral: when the database is closed and reopened, or when
+        the database process is stopped and then started again, each
+        "_version" also changes to a new random value.
+
+<column-schema>
+
+    A JSON object with the following members:
+
+        "comment": <string>                       optional
+        "type": <type>                            required
+        "ephemeral": <boolean>                    optional
+
+    The "comment" optionally provides information about this column
+    for a human reader.  The "type" specifies the type of data stored
+    in this column.  If "ephemeral" is specified as true, then this
+    column's values are not guaranteed to be durable; they may be lost
+    when the database restarts.
+
+<type>
+
+    The type of a database column.  Either an <atomic-type> or a JSON
+    object that describes the type of a database column, with the
+    following members:
+
+        "key": <atomic-type>               required
+        "value": <atomic-type>             optional
+        "min": <integer>                   optional
+        "max": <integer> or "unlimited"    optional
+
+    If "min" or "max" is not specified, each defaults to 1.  If "max"
+    is specified as "unlimited", then there is no specified maximum
+    number of elements, although the implementation will enforce some
+    limit.  After considering defaults, "min" must be at least 0,
+    "max" must be at least 1, and "max" must be greater than or equal
+    to "min".
+
+    If "min" and "max" are both 1 and "value" is not specified, the
+    type is the scalar type specified by "key".
+
+    If "min" is not 1 or "max" is not 1, or both, and "value" is not
+    specified, the type is a set of scalar type "key".
+
+    If "value" is specified, the type is a map from type "key" to type
+    "value".
+
+<atomic-type>
+
+    One of the strings "integer", "real", "boolean", "string", or
+    "uuid", representing the specified scalar type.
+
+Wire Protocol
+-------------
+
+The database wire protocol is implemented in JSON-RPC 1.0.  It
+consists of the following JSON-RPC methods:
+
+get_schema
+..........
+
+Request object members:
+
+    "method": "get_schema"            required
+    "params": []                      required
+    "id": any JSON value except null  required
+
+Response object members:
+
+    "result": <database-schema>
+    "error": null
+    "id": same "id" as request
+
+This operation retrieves a <database-schema> that describes the
+hosted database.
+
+transact
+........
+
+Request object members:
+
+    "method": "transact"              required
+    "params": [<operation>*]          required
+    "id": any JSON value except null  required
+
+Response object members:
+
+    "result": [<object>*]
+    "error": null
+    "id": same "id" as request
+
+The "params" array for this method consists of zero or more JSON
+objects, each of which represents a single database operation.  The
+"Operations" section below describes the valid operations.
+
+The value of "id" must be unique among all in-flight transactions
+within the current JSON-RPC session.  Otherwise, the server may return
+a JSON-RPC error.
+
+The database server executes each of the specified operations in the
+specified order, except that if an operation fails, then the remaining
+operations are not executed.
+
+The set of operations is executed as a single atomic, consistent,
+isolated transaction.  The transaction is committed only if every
+operation succeeds.  Durability of the commit is not guaranteed unless
+the "commit" operation, with "durable" set to true, is included in the
+operation set (see below).
+
+Regardless of whether errors occur, the response is always a JSON-RPC
+response with null "error" and a "result" member that is an array with
+the same number of elements as "params".  Each element of the "result"
+array corresponds to the same element of the "params" array.  The
+"result" array elements may be interpreted as follows:
+
+    - A JSON object that does not contain an "error" member indicates
+      that the operation completed successfully.  The specific members
+      of the object are specified below in the descriptions of
+      individual operations.  Some operations do not produce any
+      results, in which case the object will have no members.
+
+    - A JSON object that contains an "error" member indicates that the
+      operation completed with an error.  The value of the "error"
+      member is a short string, specified in this document, that
+      broadly indicates the class of the error.  Besides the ones
+      listed for a specific operation, any operation may result in one
+      the following "error"s:
+
+      "error": "resources exhausted"
+
+          The operation or the transaction requires more resources
+          (memory, disk, CPU, etc.) than are currently available to
+          the database server.
+
+      "error": "syntax error"
+
+          The operation is not specified correctly: a required request
+          object member is missing, an unknown or unsupported request
+          object member is present, the operation attempts to act on a
+          table that does not exist, the operation modifies a
+          read-only table column, etc.
+
+      Database implementations may use "error" strings not specified
+      in this document to indicate errors that do not fit into any of
+      the specified categories.
+
+      Optionally, the object may include a "details" member, whose
+      value is a string that describes the error in more detail for
+      the benefit of a human user or administrator.  The object may
+      also have other members that describe the error in more detail.
+      This document does not specify the names or values of these
+      members.
+
+    - A JSON null value indicates that the operation was not attempted
+      because a prior operation failed.
+
+In general, "result" contains some number of successful results,
+possibly followed by an error, in turn followed by enough JSON null
+values to match the number of elements in "params".  There is one
+exception: if all of the operations succeed, but the results cannot be
+committed (e.g. due to I/O errors), then "result" will have one more
+element than "params", with the additional element describing the
+error.
+
+If "params" contains one or more "wait" operations, then the
+transaction may take an arbitrary amount of time to complete.  The
+database implementation must be capable of accepting, executing, and
+replying to other transactions and other JSON-RPC requests while a
+transaction or transactions containing "wait" operations are
+outstanding on the same or different JSON-RPC sessions.
+
+The section "Notation for the Wire Protocol" below describes
+additional notation for use with the wire protocol.  After that, the
+"Operations" section describes each operation.
+
+cancel
+......
+
+Request object members:
+
+    "method": "cancel"                              required
+    "params": [the "id" for an outstanding request] required
+    "id": null                                      required
+
+Response object members:
+
+    <no response>
+
+This JSON-RPC notification instructs the database server to
+immediately complete or cancel the "transact" request whose "id" is
+the same as the notification's "params" value.  
+
+If the "transact" request can be completed immediately, then the
+server sends a response in the form described for "transact", above.
+Otherwise, the server sends a JSON-RPC error response of the following
+form:
+
+    "result": null
+    "error": "canceled"
+    "id": the request "id" member
+
+The "cancel" notification itself has no reply.
+
+monitor
+.......
+
+Request object members:
+
+    "method": "monitor"                        required
+    "params": [<value>, <monitor-requests>]    required
+    "id": any JSON value except null           required
+
+<monitor-requests> is an object that maps from a table name to a
+<monitor-request>.
+
+Each <monitor-request> is an object with the following members:
+
+    "columns": [<column>*]            optional
+    "select": <monitor-select>        optional
+
+<monitor-select> is an object with the following members:
+
+    "initial": <boolean>              optional
+    "insert": <boolean>               optional
+    "delete": <boolean>               optional
+    "modify": <boolean>               optional
+
+Response object members:
+
+    "result": <table-updates>
+    "error": null
+    "id": same "id" as request
+
+This JSON-RPC request enables a client to replicate tables or subsets
+of tables.  Each <monitor-request> specifies a table to be replicated.
+The JSON-RPC response to the "monitor" includes the initial contents
+of each table.  Afterward, when changes to those tables are committed,
+the changes are automatically sent to the client using the "update"
+monitor notification.  This monitoring persists until the JSON-RPC
+session terminates or until the client sends a "monitor_cancel"
+JSON-RPC request.
+
+Each <monitor-request> describes how to monitor a table:
+
+    The circumstances in which an "update" notification is sent for a
+    row within the table are determined by <monitor-select>:
+
+        If "initial" is omitted or true, every row in the table is
+        sent as part of the reply to the "monitor" request.
+
+        If "insert" is omitted or true, "update" notifications are
+        sent for rows newly inserted into the table.
+
+        If "delete" is omitted or true, "update" notifications are
+        sent for rows deleted from the table.
+
+        If "modify" is omitted or true, "update" notifications are
+        sent whenever when a row in the table is modified.
+
+    The "columns" member specifies the columns whose values are
+    monitored.  If "columns" is omitted, all columns in the table,
+    except for "_uuid", are monitored.
+
+The "result" in the JSON-RPC response to the "monitor" request is a
+<table-updates> object (see below) that contains the contents of the
+tables for which "initial" rows are selected.  If no tables' initial
+contents are requested, then "result" is an empty object.
+
+update
+......
+
+Notification object members:
+
+    "method": "update"
+    "params": [<value>, <table-updates>]
+    "id": null
+
+The <value> in "params" is the same as the value passed as the <value>
+in "params" for the "monitor" request.
+
+<table-updates> is an object that maps from a table name to a
+<table-update>.
+
+A <table-update> is an object that maps from the row's UUID (as a
+36-byte string) to a <row-update> object.
+
+A <row-update> is an object with the following members:
+
+    "old": <row>         present for "delete" and "modify" updates
+    "new": <row>         present for "initial", "insert", and "modify" updates
+
+This JSON-RPC notification is sent from the server to the client to
+tell it about changes to a monitored table (or the initial state of a
+modified table).  Each table in which one or more rows has changed (or
+whose initial view is being presented) is represented in "updates".
+Each row that has changed (or whose initial view is being presented)
+is represented in its <table-update> as a member with its name taken
+from the row's _uuid member.  The corresponding value is a
+<row-update>:
+
+    The "old" member is present for "delete" and "modify" updates.
+    For "delete" updates, each monitored column is included.  For
+    "modify" updates, the prior value of each monitored column whose
+    value has changed is included (monitored columns that have not
+    changed are represented in "new").
+
+    The "new" member is present for "initial", "insert", and "modify"
+    updates.  For "initial" and "insert" updates, each monitored
+    column is included.  For "modify" updates, the new value of each
+    monitored column is included.
+
+monitor_cancel
+..............
+
+Request object members:
+
+    "method": "monitor_cancel"                              required
+    "params": [<value>]                                     required
+    "id": any JSON value except null                        required
+
+Response object members:
+
+    "result": {}
+    "error": null
+    "id": the request "id" member
+
+Cancels the ongoing table monitor request, identified by the <value>
+in "params" matching the <value> in "params" for an ongoing "monitor"
+request.  No more "update" messages will be sent for this table
+monitor.
+
+echo
+....
+
+Request object members:
+
+    "method": "echo"                                required
+    "params": JSON array with any contents          required
+    "id": <value>                                   required
+
+Response object members:
+
+    "result": same as "params"
+    "error": null
+    "id": the request "id" member
+
+Both the JSON-RPC client and the server must implement this request.
+
+This JSON-RPC request and response can be used to implement connection
+keepalives, by allowing the server to check that the client is still
+there or vice versa.
+
+
+Notation for the Wire Protocol
+------------------------------
+
+<table>
+
+    An <id> that names a table.
+
+<column>
+
+    An <id> that names a table column.
+
+<row>
+
+    A JSON object that describes a table row or a subset of a table
+    row.  Each member is the name of a table column paired with the
+    <value> of that column.
+
+<value>
+
+    A JSON value that represents the value of a column in a table row,
+    one of <atom>, a <set>, or a <map>.
+
+<atom>
+
+    A JSON value that represents a scalar value for a column, one of
+    <string>, <number>, <boolean>, <uuid>, <named-uuid>.
+
+<set>
+
+    A 2-element JSON array that represents a database set value.  The
+    first element of the array must be the string "set" and the second
+    element must be an array of zero or more <atom>s giving the values
+    in the set.  All of the <atom>s must have the same type.
+
+<map>
+
+    A 2-element JSON array that represents a database map value.  The
+    first element of the array must be the string "map" and the second
+    element must be an array of zero or more <pair>s giving the values
+    in the map.  All of the <pair>s must have the same key and value
+    types.
+
+    (JSON objects are not used to represent <map> because JSON only
+    allows string names in an object.)
+
+<pair>
+
+    A 2-element JSON array that represents a pair within a database
+    map.  The first element is an <atom> that represents the key, the
+    second element is an <atom> that represents the value.
+
+<uuid>
+
+    A 2-element JSON array that represents a UUID.  The first element
+    of the array must be the string "uuid" and the second element must
+    be a 36-character string giving the UUID in the format described
+    by RFC 4122.  For example, the following <uuid> represents the
+    UUID 550e8400-e29b-41d4-a716-446655440000:
+
+        ["uuid", "550e8400-e29b-41d4-a716-446655440000"]
+
+<named-uuid>
+
+    A 2-element JSON array that represents the UUID of a row inserted
+    in a previous "insert" operation within the same transaction.  The
+    first element of the array must be the string "named-uuid" and the
+    second element must be the string specified on this "insert"
+    operation's "uuid-name" or on a preceding "insert" within the same
+    transaction.  For example, if this or a previous "insert"
+    operation specified a "uuid-name" of "myrow", the following
+    <named-uuid> represents the UUID created by that operation:
+
+        ["named-uuid", "myrow"]
+
+    A <named-uuid> may be used anywhere a <uuid> is valid.
+
+<condition>
+
+    A 3-element JSON array of the form [<column>, <function>,
+    <value>] that represents a test on a column value.
+
+    Except as otherwise specified below, <value> must have the same
+    type as <column>.
+
+    The meaning depends on the type of <column>:
+
+        integer
+        real
+
+            <function> must be "<", "<=", "==", "!=", ">=", ">",
+            "includes", or "excludes".
+
+            The test is true if the column's value satisfies the
+            relation <function> <value>, e.g. if the column has value
+            1 and <value> is 2, the test is true if <function> is "<",
+            "<=" or "!=", but not otherwise.
+
+            "includes" is equivalent to "=="; "excludes" is equivalent
+            to "!=".
+
+        boolean
+        string
+        uuid
+
+            <function> must be "!=", "==", "includes", or "excludes".
+
+            If <function> is "==" or "includes", the test is true if
+            the column's value equals <value>.  If <function> is "!="
+            or "excludes", the test is inverted.
+
+        set
+        map
+
+            <function> must be "!=", "==", "includes", or "excludes".
+
+            If <function> is "==", the test is true if the column's
+            value contains exactly the same values (for sets) or pairs
+            (for maps).  If <function> is "!=", the test is inverted.
+
+            If <function> is "includes", the test is true if the
+            column's value contains all of the values (for sets) or
+            pairs (for maps) in <value>.  The column's value may also
+            contain other values or pairs.
+
+            If <function> is "excludes", the test is true if the
+            column's value does not contain any of the values (for
+            sets) or pairs (for maps) in <value>.  The column's value
+            may contain other values or pairs not in <value>.
+
+            If <function> is "includes" or "excludes", then the
+            required type of <value> is slightly relaxed, in that it
+            may have fewer than the minimum number of elements
+            specified by the column's type.  If <function> is
+            "excludes", then the required type is additionally relaxed
+            in that <value> may have more than the maximum number of
+            elements specified by the column's type.
+
+<function>
+
+    One of "<", "<=", "==", "!=", ">=", ">", "includes", "excludes".
+
+Operations
+----------
+
+Each of the available operations is described below.
+
+insert
+......
+
+Request object members:
+
+    "op": "insert"          required
+    "table": <table>        required
+    "row": <row>            required
+    "uuid-name": <string>   optional
+
+Result object members:
+
+    "uuid": <uuid>
+
+Semantics:
+
+    Inserts "row" into "table".  If "row" does not specify values
+    for all the columns in "table", those columns receive default
+    values.
+
+    The new row receives a new, randomly generated UUID, which is
+    returned as the "uuid" member of the result.  If "uuid-name" is
+    supplied, then the UUID is made available under that name to this
+    operation and later operations within the same transaction.
+
+select
+......
+
+Request object members:
+
+    "op": "select"                required
+    "table": <table>              required
+    "where": [<condition>*]       required
+    "columns": [<column>*]        optional
+
+Result object members:
+
+    "rows": [<row>*]
+
+Semantics:
+
+    Searches "table" for rows that match all the conditions specified
+    in "where".  If "where" is an empty array, every row in "table" is
+    selected.
+
+    The "rows" member of the result is an array of objects.  Each
+    object corresponds to a matching row, with each column
+    specified in "columns" as a member, the column's name as the
+    member name and its value as the member value.  If "columns"
+    is not specified, all the table's columns are included.  If
+    two rows of the result have the same values for all included
+    columns, only one copy of that row is included in "rows".
+    Specifying "_uuid" within "columns" will avoid dropping
+    duplicates, since every row has a unique UUID.
+
+    The ordering of rows within "rows" is unspecified.
+
+update
+......
+
+Request object members:
+
+    "op": "update"                required
+    "table": <table>              required
+    "where": [<condition>*]       required
+    "row": <row>                  required
+
+Result object members:
+
+    "count": <integer>
+
+Semantics:
+
+    Updates rows in a table.
+
+    Searches "table" for rows that match all the conditions
+    specified in "where".  For each matching row, changes the
+    value of each column specified in "row" to the value for that
+    column specified in "row".
+
+    The "_uuid" and "_version" columns of a table may not be directly
+    updated with this operation.  Columns designated read-only in the 
+    schema also may not be updated.
+
+    The "count" member of the result specifies the number of rows
+    that matched.
+
+delete
+......
+
+Request object members:
+
+    "op": "delete"                required
+    "table": <table>              required
+    "where": [<condition>*]       required
+
+Result object members:
+
+    "count": <integer>
+
+Semantics:
+
+    Deletes all the rows from "table" that match all the conditions
+    specified in "where".
+
+    The "count" member of the result specifies the number of deleted
+    rows.
+
+wait
+....
+
+Request object members:
+
+    "op": "wait"                        required
+    "timeout": <integer>                optional
+    "table": <table>                    required
+    "where": [<condition>*]             required
+    "columns": [<column>*]              required
+    "until": "==" or "!="               required
+    "rows": [<row>*]                    required
+
+Result object members:
+
+    none
+
+Semantics:
+
+    Waits until a condition becomes true.
+
+    If "until" is "==", checks whether the query on "table" specified
+    by "where" and "columns", which is evaluated in the same way as
+    specified for "select", returns the result set specified by
+    "rows".  If it does, then the operation completes successfully.
+    Otherwise, the entire transaction rolls back.  It is automatically
+    restarted later, after a change in the database makes it possible
+    for the operation to succeed.  The client will not receive a
+    response until the operation permanently succeeds or fails.
+    
+    If "until" is "!=", the sense of the test is negated.  That is, as
+    long as the query on "table" specified by "where" and "columns"
+    returns "rows", the transaction will be rolled back and restarted
+    later.
+
+    If "timeout" is specified, then the transaction aborts after the
+    specified number of milliseconds.  The transaction is guaranteed
+    to be attempted at least once before it aborts.  A "timeout" of 0
+    will abort the transaction on the first mismatch.
+
+Errors:
+
+    "error": "not supported"
+
+        One or more of the columns in this table do not support
+        triggers.  This error will not occur if "timeout" is 0.
+
+    "error": "timed out"
+
+        The "timeout" was reached before the transaction was able to
+        complete.
+
+commit
+......
+
+Request object members:
+
+    "op": "commit"                      required
+    "durable": <boolean>                required
+
+Result object members:
+
+    none
+
+Semantics:
+
+    If "durable" is specified as true, then the transaction, if it
+    commits, will be stored durably (to disk) before the reply is sent
+    to the client.
+
+Errors:
+
+    "error": "not supported"
+
+        When "durable" is true, this database implementation does not
+        support durable commits.
+
+abort
+.....
+
+Request object members:
+
+    "op": "abort"                      required
+
+Result object members:
+
+    (never succeeds)
+
+Semantics:
+
+    Aborts the transaction with an error.  This may be useful for
+    testing.
+
+Errors:
+
+    "error": "aborted"
+
+        This operation always fails with this error.
diff --git a/ovsdb/automake.mk b/ovsdb/automake.mk
new file mode 100644 (file)
index 0000000..2dcc463
--- /dev/null
@@ -0,0 +1,94 @@
+# libovsdb
+noinst_LIBRARIES += ovsdb/libovsdb.a
+ovsdb_libovsdb_a_SOURCES = \
+       ovsdb/column.c \
+       ovsdb/column.h \
+       ovsdb/condition.c \
+       ovsdb/condition.h \
+       ovsdb/execution.c \
+       ovsdb/file.c \
+       ovsdb/file.h \
+       ovsdb/jsonrpc-server.c \
+       ovsdb/jsonrpc-server.h \
+       ovsdb/log.c \
+       ovsdb/log.h \
+       ovsdb/ovsdb-server.c \
+       ovsdb/ovsdb.c \
+       ovsdb/ovsdb.h \
+       ovsdb/query.c \
+       ovsdb/query.h \
+       ovsdb/row.c \
+       ovsdb/row.h \
+       ovsdb/table.c \
+       ovsdb/table.h \
+       ovsdb/trigger.c \
+       ovsdb/trigger.h \
+       ovsdb/transaction.c \
+       ovsdb/transaction.h
+
+# ovsdb-tool
+bin_PROGRAMS += ovsdb/ovsdb-tool
+ovsdb_ovsdb_tool_SOURCES = ovsdb/ovsdb-tool.c
+ovsdb_ovsdb_tool_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a
+# ovsdb-tool.1
+man_MANS += ovsdb/ovsdb-tool.1
+DISTCLEANFILES += ovsdb/ovsdb-tool.1
+EXTRA_DIST += ovsdb/ovsdb-tool.1.in
+
+# ovsdb-client
+bin_PROGRAMS += ovsdb/ovsdb-client
+ovsdb_ovsdb_client_SOURCES = ovsdb/ovsdb-client.c
+ovsdb_ovsdb_client_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a
+# ovsdb-client.1
+man_MANS += ovsdb/ovsdb-client.1
+DISTCLEANFILES += ovsdb/ovsdb-client.1
+EXTRA_DIST += ovsdb/ovsdb-client.1.in
+
+# ovsdb-server
+sbin_PROGRAMS += ovsdb/ovsdb-server
+ovsdb_ovsdb_server_SOURCES = ovsdb/ovsdb-server.c
+ovsdb_ovsdb_server_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a $(FAULT_LIBS)
+# ovsdb-server.1
+man_MANS += ovsdb/ovsdb-server.1
+DISTCLEANFILES += ovsdb/ovsdb-server.1
+EXTRA_DIST += ovsdb/ovsdb-server.1.in
+
+# ovsdb-idlc
+EXTRA_DIST += \
+       ovsdb/simplejson/__init__.py \
+       ovsdb/simplejson/_speedups.c                            \
+       ovsdb/simplejson/decoder.py                             \
+       ovsdb/simplejson/encoder.py                             \
+       ovsdb/simplejson/scanner.py                             \
+       ovsdb/simplejson/tests/__init__.py                      \
+       ovsdb/simplejson/tests/test_check_circular.py           \
+       ovsdb/simplejson/tests/test_decode.py                   \
+       ovsdb/simplejson/tests/test_default.py                  \
+       ovsdb/simplejson/tests/test_dump.py                     \
+       ovsdb/simplejson/tests/test_encode_basestring_ascii.py  \
+       ovsdb/simplejson/tests/test_fail.py                     \
+       ovsdb/simplejson/tests/test_float.py                    \
+       ovsdb/simplejson/tests/test_indent.py                   \
+       ovsdb/simplejson/tests/test_pass1.py                    \
+       ovsdb/simplejson/tests/test_pass2.py                    \
+       ovsdb/simplejson/tests/test_pass3.py                    \
+       ovsdb/simplejson/tests/test_recursion.py                \
+       ovsdb/simplejson/tests/test_scanstring.py               \
+       ovsdb/simplejson/tests/test_separators.py               \
+       ovsdb/simplejson/tests/test_unicode.py                  \
+       ovsdb/simplejson/tool.py
+noinst_SCRIPTS += ovsdb/ovsdb-idlc
+EXTRA_DIST += \
+       ovsdb/ovsdb-idlc.in \
+       ovsdb/ovsdb-idlc.1
+DISTCLEANFILES += ovsdb/ovsdb-idlc
+SUFFIXES += .ovsidl
+.ovsidl.c:
+       $(PYTHON) $(srcdir)/ovsdb/ovsdb-idlc.in c-idl-source $< > $@.tmp
+       mv $@.tmp $@
+.ovsidl.h:
+       $(PYTHON) $(srcdir)/ovsdb/ovsdb-idlc.in c-idl-header $< > $@.tmp
+       mv $@.tmp $@
+.ovsidl.ovsschema:
+       $(PYTHON) $(srcdir)/ovsdb/ovsdb-idlc.in ovsdb-schema $< > $@.tmp
+       mv $@.tmp $@
diff --git a/ovsdb/column.c b/ovsdb/column.c
new file mode 100644 (file)
index 0000000..dc93dc7
--- /dev/null
@@ -0,0 +1,245 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb/column.h"
+
+#include <stdlib.h>
+
+#include "column.h"
+#include "json.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "table.h"
+#include "util.h"
+
+struct ovsdb_column *
+ovsdb_column_create(const char *name, const char *comment,
+                    bool mutable, bool persistent,
+                    const struct ovsdb_type *type)
+{
+    struct ovsdb_column *column;
+
+    column = xzalloc(sizeof *column);
+    column->name = xstrdup(name);
+    column->comment = comment ? xstrdup(comment) : NULL;
+    column->mutable = mutable;
+    column->persistent = persistent;
+    column->type = *type;
+
+    return column;
+}
+
+void
+ovsdb_column_destroy(struct ovsdb_column *column)
+{
+    free(column->name);
+    free(column->comment);
+    free(column);
+}
+
+struct ovsdb_error *
+ovsdb_column_from_json(const struct json *json, const char *name,
+                       struct ovsdb_column **columnp)
+{
+    const struct json *comment, *mutable, *ephemeral, *type_json;
+    struct ovsdb_error *error;
+    struct ovsdb_type type;
+    struct ovsdb_parser parser;
+    bool persistent;
+
+    *columnp = NULL;
+
+    ovsdb_parser_init(&parser, json, "schema for column %s", name);
+    comment = ovsdb_parser_member(&parser, "comment", OP_STRING | OP_OPTIONAL);
+    mutable = ovsdb_parser_member(&parser, "mutable",
+                                OP_TRUE | OP_FALSE | OP_OPTIONAL);
+    ephemeral = ovsdb_parser_member(&parser, "ephemeral",
+                                    OP_TRUE | OP_FALSE | OP_OPTIONAL);
+    type_json = ovsdb_parser_member(&parser, "type", OP_STRING | OP_OBJECT);
+    error = ovsdb_parser_finish(&parser);
+    if (error) {
+        return error;
+    }
+
+    error = ovsdb_type_from_json(&type, type_json);
+    if (error) {
+        return error;
+    }
+
+    persistent = ephemeral ? !json_boolean(ephemeral) : true;
+    *columnp = ovsdb_column_create(name,
+                                   comment ? json_string(comment) : NULL,
+                                   mutable ? json_boolean(mutable) : true,
+                                   persistent, &type);
+    return NULL;
+}
+
+struct json *
+ovsdb_column_to_json(const struct ovsdb_column *column)
+{
+    struct json *json = json_object_create();
+    if (column->comment) {
+        json_object_put_string(json, "comment", column->comment);
+    }
+    if (!column->mutable) {
+        json_object_put(json, "mutable", json_boolean_create(false));
+    }
+    if (!column->persistent) {
+        json_object_put(json, "ephemeral", json_boolean_create(true));
+    }
+    json_object_put(json, "type", ovsdb_type_to_json(&column->type));
+    return json;
+}
+\f
+void
+ovsdb_column_set_init(struct ovsdb_column_set *set)
+{
+    set->columns = NULL;
+    set->n_columns = set->allocated_columns = 0;
+}
+
+void
+ovsdb_column_set_destroy(struct ovsdb_column_set *set)
+{
+    free(set->columns);
+}
+
+void
+ovsdb_column_set_clone(struct ovsdb_column_set *new,
+                       const struct ovsdb_column_set *old)
+{
+    new->columns = xmemdup(old->columns,
+                           old->n_columns * sizeof *old->columns);
+    new->n_columns = new->allocated_columns = old->n_columns;
+}
+
+struct ovsdb_error *
+ovsdb_column_set_from_json(const struct json *json,
+                           const struct ovsdb_table *table,
+                           struct ovsdb_column_set *set)
+{
+    ovsdb_column_set_init(set);
+    if (!json) {
+        struct shash_node *node;
+
+        SHASH_FOR_EACH (node, &table->schema->columns) {
+            const struct ovsdb_column *column = node->data;
+            ovsdb_column_set_add(set, column);
+        }
+
+        return NULL;
+    } else {
+        size_t i;
+
+        if (json->type != JSON_ARRAY) {
+            goto error;
+        }
+
+        /* XXX this is O(n**2) */
+        for (i = 0; i < json->u.array.n; i++) {
+            struct ovsdb_column *column;
+
+            if (json->u.array.elems[i]->type != JSON_STRING) {
+                goto error;
+            }
+
+            column = shash_find_data(&table->schema->columns,
+                                     json->u.array.elems[i]->u.string);
+            if (ovsdb_column_set_contains(set, column->index)) {
+                goto error;
+            }
+            ovsdb_column_set_add(set, column);
+        }
+
+        return NULL;
+    }
+
+error:
+    ovsdb_column_set_destroy(set);
+    return ovsdb_syntax_error(json, NULL,
+                              "array of distinct column names expected");
+}
+
+struct json *
+ovsdb_column_set_to_json(const struct ovsdb_column_set *set)
+{
+    struct json *json;
+    size_t i;
+
+    json = json_array_create_empty();
+    for (i = 0; i < set->n_columns; i++) {
+        json_array_add(json, json_string_create(set->columns[i]->name));
+    }
+    return json;
+}
+
+void
+ovsdb_column_set_add(struct ovsdb_column_set *set,
+                     const struct ovsdb_column *column)
+{
+    if (set->n_columns >= set->allocated_columns) {
+        set->columns = x2nrealloc(set->columns, &set->allocated_columns,
+                                  sizeof *set->columns);
+    }
+    set->columns[set->n_columns++] = column;
+}
+
+void
+ovsdb_column_set_add_all(struct ovsdb_column_set *set,
+                         const struct ovsdb_table *table)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &table->schema->columns) {
+        const struct ovsdb_column *column = node->data;
+        ovsdb_column_set_add(set, column);
+    }
+}
+
+bool
+ovsdb_column_set_contains(const struct ovsdb_column_set *set,
+                          unsigned int column_index)
+{
+    size_t i;
+
+    for (i = 0; i < set->n_columns; i++) {
+        if (set->columns[i]->index == column_index) {
+            return true;
+        }
+    }
+    return false;
+}
+
+/* This comparison is sensitive to ordering of columns within a set, but that's
+ * good: the only existing caller wants to make sure that hash values are
+ * comparable, which is only true if column ordering is the same. */
+bool
+ovsdb_column_set_equals(const struct ovsdb_column_set *a,
+                        const struct ovsdb_column_set *b)
+{
+    size_t i;
+
+    if (a->n_columns != b->n_columns) {
+        return false;
+    }
+    for (i = 0; i < a->n_columns; i++) {
+        if (a->columns[i] != b->columns[i]) {
+            return false;
+        }
+    }
+    return true;
+}
diff --git a/ovsdb/column.h b/ovsdb/column.h
new file mode 100644 (file)
index 0000000..5fd39ae
--- /dev/null
@@ -0,0 +1,85 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_COLUMN_H
+#define OVSDB_COLUMN_H 1
+
+#include <stdbool.h>
+#include "compiler.h"
+#include "ovsdb-types.h"
+
+struct ovsdb_table;
+
+/* A column or a column schema (currently there is no distinction). */
+struct ovsdb_column {
+    unsigned int index;
+    char *name;
+
+    char *comment;
+    bool mutable;
+    bool persistent;
+    struct ovsdb_type type;
+};
+
+/* A few columns appear in every table with standardized column indexes.
+ * These macros define those columns' indexes.
+ *
+ * Don't change these values, because ovsdb_query() depends on OVSDB_COL_UUID
+ * having value 0. */
+enum {
+    OVSDB_COL_UUID = 0,         /* UUID for the row. */
+    OVSDB_COL_VERSION = 1,      /* Version number for the row. */
+    OVSDB_N_STD_COLUMNS
+};
+
+struct ovsdb_column *ovsdb_column_create(
+    const char *name, const char *comment, bool mutable, bool persistent,
+    const struct ovsdb_type *);
+void ovsdb_column_destroy(struct ovsdb_column *);
+
+struct ovsdb_error *ovsdb_column_from_json(const struct json *,
+                                           const char *name,
+                                           struct ovsdb_column **)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_column_to_json(const struct ovsdb_column *);
+\f
+/* An unordered set of distinct columns. */
+
+struct ovsdb_column_set {
+    const struct ovsdb_column **columns;
+    size_t n_columns, allocated_columns;
+};
+
+#define OVSDB_COLUMN_SET_INITIALIZER { NULL, 0, 0 }
+
+void ovsdb_column_set_init(struct ovsdb_column_set *);
+void ovsdb_column_set_destroy(struct ovsdb_column_set *);
+void ovsdb_column_set_clone(struct ovsdb_column_set *,
+                            const struct ovsdb_column_set *);
+struct ovsdb_error *ovsdb_column_set_from_json(const struct json *,
+                                               const struct ovsdb_table *,
+                                               struct ovsdb_column_set *);
+struct json *ovsdb_column_set_to_json(const struct ovsdb_column_set *);
+
+void ovsdb_column_set_add(struct ovsdb_column_set *,
+                          const struct ovsdb_column *);
+void ovsdb_column_set_add_all(struct ovsdb_column_set *,
+                              const struct ovsdb_table *);
+bool ovsdb_column_set_contains(const struct ovsdb_column_set *,
+                               unsigned int column_index);
+bool ovsdb_column_set_equals(const struct ovsdb_column_set *,
+                             const struct ovsdb_column_set *);
+
+#endif /* column.h */
diff --git a/ovsdb/condition.c b/ovsdb/condition.c
new file mode 100644 (file)
index 0000000..0342b8e
--- /dev/null
@@ -0,0 +1,284 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "condition.h"
+
+#include <limits.h>
+
+#include "column.h"
+#include "json.h"
+#include "ovsdb-error.h"
+#include "row.h"
+#include "table.h"
+
+struct ovsdb_error *
+ovsdb_function_from_string(const char *name, enum ovsdb_function *function)
+{
+#define OVSDB_FUNCTION(ENUM, NAME)              \
+    if (!strcmp(name, NAME)) {                  \
+        *function = ENUM;                       \
+        return NULL;                            \
+    }
+    OVSDB_FUNCTIONS;
+#undef OVSDB_FUNCTION
+
+    return ovsdb_syntax_error(NULL, "unknown function",
+                              "No function named %s.", name);
+}
+
+const char *
+ovsdb_function_to_string(enum ovsdb_function function)
+{
+    switch (function) {
+#define OVSDB_FUNCTION(ENUM, NAME) case ENUM: return NAME;
+        OVSDB_FUNCTIONS;
+#undef OVSDB_FUNCTION
+    }
+
+    return NULL;
+}
+
+
+static WARN_UNUSED_RESULT struct ovsdb_error *
+ovsdb_clause_from_json(const struct ovsdb_table_schema *ts,
+                       const struct json *json,
+                       const struct ovsdb_symbol_table *symtab,
+                       struct ovsdb_clause *clause)
+{
+    const struct json_array *array;
+    struct ovsdb_error *error;
+    const char *function_name;
+    const char *column_name;
+    struct ovsdb_type type;
+
+    if (json->type != JSON_ARRAY
+        || json->u.array.n != 3
+        || json->u.array.elems[0]->type != JSON_STRING
+        || json->u.array.elems[1]->type != JSON_STRING) {
+        return ovsdb_syntax_error(json, NULL, "Parse error in condition.");
+    }
+    array = json_array(json);
+
+    column_name = json_string(array->elems[0]);
+    clause->column = ovsdb_table_schema_get_column(ts, column_name);
+    if (!clause->column) {
+        return ovsdb_syntax_error(json, "unknown column",
+                                  "No column %s in table %s.",
+                                  column_name, ts->name);
+    }
+    type = clause->column->type;
+
+    function_name = json_string(array->elems[1]);
+    error = ovsdb_function_from_string(function_name, &clause->function);
+    if (error) {
+        return error;
+    }
+
+    /* Type-check and relax restrictions on 'type' if appropriate.  */
+    switch (clause->function) {
+    case OVSDB_F_LT:
+    case OVSDB_F_LE:
+    case OVSDB_F_GT:
+    case OVSDB_F_GE:
+        /* XXX should we also allow these operators for types with n_min == 0,
+         * n_max == 1?  (They would always be "false" if the value was
+         * missing.) */
+        if (!ovsdb_type_is_scalar(&type)
+            || (type.key_type != OVSDB_TYPE_INTEGER
+                && type.key_type != OVSDB_TYPE_REAL)) {
+            char *s = ovsdb_type_to_english(&type);
+            error = ovsdb_syntax_error(
+                json, NULL, "Type mismatch: \"%s\" operator may not be "
+                "applied to column %s of type %s.",
+                ovsdb_function_to_string(clause->function),
+                clause->column->name, s);
+            free(s);
+            return error;
+        }
+        break;
+
+    case OVSDB_F_EQ:
+    case OVSDB_F_NE:
+        break;
+
+    case OVSDB_F_EXCLUDES:
+        if (!ovsdb_type_is_scalar(&type)) {
+            type.n_min = 0;
+            type.n_max = UINT_MAX;
+        }
+        break;
+
+    case OVSDB_F_INCLUDES:
+        if (!ovsdb_type_is_scalar(&type)) {
+            type.n_min = 0;
+        }
+        break;
+    }
+    return ovsdb_datum_from_json(&clause->arg, &type, array->elems[2], symtab);
+}
+
+static void
+ovsdb_clause_free(struct ovsdb_clause *clause)
+{
+    ovsdb_datum_destroy(&clause->arg, &clause->column->type);
+}
+
+static int
+compare_clauses_3way(const void *a_, const void *b_)
+{
+    const struct ovsdb_clause *a = a_;
+    const struct ovsdb_clause *b = b_;
+
+    if (a->function != b->function) {
+        /* Bring functions to the front based on the fraction of table rows
+         * that they are (heuristically) expected to leave in the query
+         * results.  Note that "enum ovsdb_function" is intentionally ordered
+         * to make this trivial. */
+        return a->function < b->function ? -1 : 1;
+    } else if (a->column->index != b->column->index) {
+        if (a->column->index < OVSDB_N_STD_COLUMNS
+            || b->column->index < OVSDB_N_STD_COLUMNS) {
+            /* Bring the standard columns and in particular the UUID column
+             * (since OVSDB_COL_UUID has value 0) to the front.  We have an
+             * index on the UUID column, so that makes our queries cheaper. */
+            return a->column->index < b->column->index ? -1 : 1;
+        } else {
+            /* Order clauses predictably to make testing easier. */
+            return strcmp(a->column->name, b->column->name);
+        }
+    } else {
+        return 0;
+    }
+}
+
+struct ovsdb_error *
+ovsdb_condition_from_json(const struct ovsdb_table_schema *ts,
+                          const struct json *json,
+                          const struct ovsdb_symbol_table *symtab,
+                          struct ovsdb_condition *cnd)
+{
+    const struct json_array *array = json_array(json);
+    size_t i;
+
+    cnd->clauses = xmalloc(array->n * sizeof *cnd->clauses);
+    cnd->n_clauses = 0;
+    for (i = 0; i < array->n; i++) {
+        struct ovsdb_error *error;
+        error = ovsdb_clause_from_json(ts, array->elems[i], symtab,
+                                       &cnd->clauses[i]);
+        if (error) {
+            ovsdb_condition_destroy(cnd);
+            cnd->clauses = NULL;
+            cnd->n_clauses = 0;
+            return error;
+        }
+        cnd->n_clauses++;
+    }
+
+    /* A real database would have a query optimizer here. */
+    qsort(cnd->clauses, cnd->n_clauses, sizeof *cnd->clauses,
+          compare_clauses_3way);
+
+    return NULL;
+}
+
+static struct json *
+ovsdb_clause_to_json(const struct ovsdb_clause *clause)
+{
+    return json_array_create_3(
+        json_string_create(clause->column->name),
+        json_string_create(ovsdb_function_to_string(clause->function)),
+        ovsdb_datum_to_json(&clause->arg, &clause->column->type));
+}
+
+struct json *
+ovsdb_condition_to_json(const struct ovsdb_condition *cnd)
+{
+    struct json **clauses;
+    size_t i;
+
+    clauses = xmalloc(cnd->n_clauses * sizeof *clauses);
+    for (i = 0; i < cnd->n_clauses; i++) {
+        clauses[i] = ovsdb_clause_to_json(&cnd->clauses[i]);
+    }
+    return json_array_create(clauses, cnd->n_clauses);
+}
+
+bool
+ovsdb_condition_evaluate(const struct ovsdb_row *row,
+                         const struct ovsdb_condition *cnd)
+{
+    size_t i;
+
+    for (i = 0; i < cnd->n_clauses; i++) {
+        const struct ovsdb_clause *c = &cnd->clauses[i];
+        const struct ovsdb_datum *field = &row->fields[c->column->index];
+        const struct ovsdb_datum *arg = &cnd->clauses[i].arg;
+        const struct ovsdb_type *type = &c->column->type;
+
+        if (ovsdb_type_is_scalar(type)) {
+            int cmp = ovsdb_atom_compare_3way(&field->keys[0], &arg->keys[0],
+                                              type->key_type);
+            switch (c->function) {
+            case OVSDB_F_LT:
+                return cmp < 0;
+            case OVSDB_F_LE:
+                return cmp <= 0;
+            case OVSDB_F_EQ:
+            case OVSDB_F_INCLUDES:
+                return cmp == 0;
+            case OVSDB_F_NE:
+            case OVSDB_F_EXCLUDES:
+                return cmp != 0;
+            case OVSDB_F_GE:
+                return cmp >= 0;
+            case OVSDB_F_GT:
+                return cmp > 0;
+            }
+        } else {
+            switch (c->function) {
+            case OVSDB_F_EQ:
+                return ovsdb_datum_equals(field, arg, type);
+            case OVSDB_F_NE:
+                return !ovsdb_datum_equals(field, arg, type);
+            case OVSDB_F_INCLUDES:
+                return ovsdb_datum_includes_all(arg, field, type);
+            case OVSDB_F_EXCLUDES:
+                return ovsdb_datum_excludes_all(arg, field, type);
+            case OVSDB_F_LT:
+            case OVSDB_F_LE:
+            case OVSDB_F_GE:
+            case OVSDB_F_GT:
+                NOT_REACHED();
+            }
+        }
+        NOT_REACHED();
+    }
+
+    return true;
+}
+
+void
+ovsdb_condition_destroy(struct ovsdb_condition *cnd)
+{
+    size_t i;
+
+    for (i = 0; i < cnd->n_clauses; i++) {
+        ovsdb_clause_free(&cnd->clauses[i]);
+    }
+    free(cnd->clauses);
+}
diff --git a/ovsdb/condition.h b/ovsdb/condition.h
new file mode 100644 (file)
index 0000000..8c422b9
--- /dev/null
@@ -0,0 +1,72 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_CONDITION_H
+#define OVSDB_CONDITION_H 1
+
+#include <stddef.h>
+#include "compiler.h"
+#include "ovsdb-data.h"
+
+struct json;
+struct ovsdb_table_schema;
+struct ovsdb_row;
+
+/* These list is ordered in ascending order of the fraction of tables row that
+ * they are (heuristically) expected to leave in query results. */
+#define OVSDB_FUNCTIONS                         \
+    OVSDB_FUNCTION(OVSDB_F_EQ, "==")                  \
+    OVSDB_FUNCTION(OVSDB_F_INCLUDES, "includes")      \
+    OVSDB_FUNCTION(OVSDB_F_LE, "<=")                  \
+    OVSDB_FUNCTION(OVSDB_F_LT, "<")                   \
+    OVSDB_FUNCTION(OVSDB_F_GE, ">=")                  \
+    OVSDB_FUNCTION(OVSDB_F_GT, ">")                   \
+    OVSDB_FUNCTION(OVSDB_F_EXCLUDES, "excludes")      \
+    OVSDB_FUNCTION(OVSDB_F_NE, "!=")
+
+enum ovsdb_function {
+#define OVSDB_FUNCTION(ENUM, NAME) ENUM,
+    OVSDB_FUNCTIONS
+#undef OVSDB_FUNCTION
+};
+
+struct ovsdb_error *ovsdb_function_from_string(const char *,
+                                               enum ovsdb_function *)
+    WARN_UNUSED_RESULT;
+const char *ovsdb_function_to_string(enum ovsdb_function);
+
+struct ovsdb_clause {
+    enum ovsdb_function function;
+    const struct ovsdb_column *column;
+    struct ovsdb_datum arg;
+};
+
+struct ovsdb_condition {
+    struct ovsdb_clause *clauses;
+    size_t n_clauses;
+};
+
+#define OVSDB_CONDITION_INITIALIZER { NULL, 0 }
+
+struct ovsdb_error *ovsdb_condition_from_json(
+    const struct ovsdb_table_schema *,
+    const struct json *, const struct ovsdb_symbol_table *,
+    struct ovsdb_condition *) WARN_UNUSED_RESULT;
+struct json *ovsdb_condition_to_json(const struct ovsdb_condition *);
+void ovsdb_condition_destroy(struct ovsdb_condition *);
+bool ovsdb_condition_evaluate(const struct ovsdb_row *,
+                              const struct ovsdb_condition *);
+
+#endif /* ovsdb/condition.h */
diff --git a/ovsdb/execution.c b/ovsdb/execution.c
new file mode 100644 (file)
index 0000000..0bfe86f
--- /dev/null
@@ -0,0 +1,582 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <assert.h>
+#include <limits.h>
+
+#include "column.h"
+#include "condition.h"
+#include "file.h"
+#include "json.h"
+#include "ovsdb-data.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "ovsdb.h"
+#include "query.h"
+#include "row.h"
+#include "table.h"
+#include "timeval.h"
+#include "transaction.h"
+
+struct ovsdb_execution {
+    struct ovsdb *db;
+    struct ovsdb_txn *txn;
+    struct ovsdb_symbol_table *symtab;
+    bool durable;
+
+    /* Triggers. */
+    long long int elapsed_msec;
+    long long int timeout_msec;
+};
+
+typedef struct ovsdb_error *ovsdb_operation_executor(struct ovsdb_execution *,
+                                                     struct ovsdb_parser *,
+                                                     struct json *result);
+
+static ovsdb_operation_executor ovsdb_execute_insert;
+static ovsdb_operation_executor ovsdb_execute_select;
+static ovsdb_operation_executor ovsdb_execute_update;
+static ovsdb_operation_executor ovsdb_execute_delete;
+static ovsdb_operation_executor ovsdb_execute_wait;
+static ovsdb_operation_executor ovsdb_execute_commit;
+static ovsdb_operation_executor ovsdb_execute_abort;
+
+static ovsdb_operation_executor *
+lookup_executor(const char *name)
+{
+    struct ovsdb_operation {
+        const char *name;
+        ovsdb_operation_executor *executor;
+    };
+
+    static const struct ovsdb_operation operations[] = {
+        { "insert", ovsdb_execute_insert },
+        { "select", ovsdb_execute_select },
+        { "update", ovsdb_execute_update },
+        { "delete", ovsdb_execute_delete },
+        { "wait", ovsdb_execute_wait },
+        { "commit", ovsdb_execute_commit },
+        { "abort", ovsdb_execute_abort },
+    };
+
+    size_t i;
+
+    for (i = 0; i < ARRAY_SIZE(operations); i++) {
+        const struct ovsdb_operation *c = &operations[i];
+        if (!strcmp(c->name, name)) {
+            return c->executor;
+        }
+    }
+    return NULL;
+}
+
+struct json *
+ovsdb_execute(struct ovsdb *db, const struct json *params,
+              long long int elapsed_msec, long long int *timeout_msec)
+{
+    struct ovsdb_execution x;
+    struct ovsdb_error *error;
+    struct json *results;
+    size_t n_operations;
+    size_t i;
+
+    if (params->type != JSON_ARRAY) {
+        struct ovsdb_error *error;
+
+        error = ovsdb_syntax_error(params, NULL, "array expected");
+        results = ovsdb_error_to_json(error);
+        ovsdb_error_destroy(error);
+        return results;
+    }
+
+    x.db = db;
+    x.txn = ovsdb_txn_create(db);
+    x.symtab = ovsdb_symbol_table_create();
+    x.durable = false;
+    x.elapsed_msec = elapsed_msec;
+    x.timeout_msec = LLONG_MAX;
+    results = NULL;
+
+    results = json_array_create_empty();
+    n_operations = params->u.array.n;
+    error = NULL;
+    for (i = 0; i < n_operations; i++) {
+        struct json *operation = params->u.array.elems[i];
+        struct ovsdb_error *parse_error;
+        struct ovsdb_parser parser;
+        struct json *result;
+        const struct json *op;
+
+        /* Parse and execute operation. */
+        ovsdb_parser_init(&parser, operation,
+                          "ovsdb operation %zu of %zu", i + 1, n_operations);
+        op = ovsdb_parser_member(&parser, "op", OP_ID);
+        result = json_object_create();
+        if (op) {
+            const char *op_name = json_string(op);
+            ovsdb_operation_executor *executor = lookup_executor(op_name);
+            if (executor) {
+                error = executor(&x, &parser, result);
+            } else {
+                ovsdb_parser_raise_error(&parser, "No operation \"%s\"",
+                                         op_name);
+            }
+        } else {
+            assert(ovsdb_parser_has_error(&parser));
+        }
+
+        /* A parse error overrides any other error.
+         * An error overrides any other result. */
+        parse_error = ovsdb_parser_finish(&parser);
+        if (parse_error) {
+            ovsdb_error_destroy(error);
+            error = parse_error;
+        }
+        if (error) {
+            json_destroy(result);
+            result = ovsdb_error_to_json(error);
+        }
+        if (error && !strcmp(ovsdb_error_get_tag(error), "not supported")
+            && timeout_msec) {
+            ovsdb_txn_abort(x.txn);
+            *timeout_msec = x.timeout_msec;
+            ovsdb_error_destroy(error);
+            json_destroy(results);
+            return NULL;
+        }
+
+        /* Add result to array. */
+        json_array_add(results, result);
+        if (error) {
+            break;
+        }
+    }
+
+    if (!error) {
+        error = ovsdb_txn_commit(x.txn, x.durable);
+        if (error) {
+            json_array_add(results, ovsdb_error_to_json(error));
+        }
+    } else {
+        ovsdb_txn_abort(x.txn);
+    }
+
+    while (json_array(results)->n < n_operations) {
+        json_array_add(results, json_null_create());
+    }
+
+    ovsdb_error_destroy(error);
+    ovsdb_symbol_table_destroy(x.symtab);
+
+    return results;
+}
+
+struct ovsdb_error *
+ovsdb_execute_commit(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                     struct json *result UNUSED)
+{
+    const struct json *durable;
+
+    durable = ovsdb_parser_member(parser, "durable", OP_BOOLEAN);
+    if (durable && json_boolean(durable)) {
+        x->durable = true;
+    }
+    return NULL;
+}
+
+static struct ovsdb_error *
+ovsdb_execute_abort(struct ovsdb_execution *x UNUSED,
+                    struct ovsdb_parser *parser UNUSED,
+                    struct json *result UNUSED)
+{
+    return ovsdb_error("aborted", "aborted by request");
+}
+
+static struct ovsdb_table *
+parse_table(struct ovsdb_execution *x,
+            struct ovsdb_parser *parser, const char *member)
+{
+    struct ovsdb_table *table;
+    const char *table_name;
+    const struct json *json;
+
+    json = ovsdb_parser_member(parser, member, OP_ID);
+    if (!json) {
+        return NULL;
+    }
+    table_name = json_string(json);
+
+    table = shash_find_data(&x->db->tables, table_name);
+    if (!table) {
+        ovsdb_parser_raise_error(parser, "No table named %s.", table_name);
+    }
+    return table;
+}
+
+static WARN_UNUSED_RESULT struct ovsdb_error *
+parse_row(struct ovsdb_parser *parser, const char *member,
+          const struct ovsdb_table *table,
+          const struct ovsdb_symbol_table *symtab,
+          struct ovsdb_row **rowp, struct ovsdb_column_set *columns)
+{
+    struct ovsdb_error *error;
+    const struct json *json;
+    struct ovsdb_row *row;
+
+    *rowp = NULL;
+
+    if (!table) {
+        return OVSDB_BUG("null table");
+    }
+    json = ovsdb_parser_member(parser, member, OP_OBJECT);
+    if (!json) {
+        return OVSDB_BUG("null row member");
+    }
+
+    row = ovsdb_row_create(table);
+    error = ovsdb_row_from_json(row, json, symtab, columns);
+    if (error) {
+        ovsdb_row_destroy(row);
+        return error;
+    } else {
+        *rowp = row;
+        return NULL;
+    }
+}
+
+struct ovsdb_error *
+ovsdb_execute_insert(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                     struct json *result)
+{
+    struct ovsdb_table *table;
+    struct ovsdb_row *row = NULL;
+    const struct json *uuid_name;
+    struct ovsdb_error *error;
+    struct uuid row_uuid;
+
+    table = parse_table(x, parser, "table");
+    uuid_name = ovsdb_parser_member(parser, "uuid-name", OP_ID | OP_OPTIONAL);
+    error = ovsdb_parser_get_error(parser);
+
+    uuid_generate(&row_uuid);
+    if (uuid_name) {
+        ovsdb_symbol_table_put(x->symtab, json_string(uuid_name), &row_uuid);
+    }
+
+    if (!error) {
+        error = parse_row(parser, "row", table, x->symtab, &row, NULL);
+    }
+    if (!error) {
+        *ovsdb_row_get_uuid_rw(row) = row_uuid;
+        ovsdb_txn_row_insert(x->txn, row);
+        json_object_put(result, "uuid",
+                        ovsdb_datum_to_json(&row->fields[OVSDB_COL_UUID],
+                                            &ovsdb_type_uuid));
+        row = NULL;
+    }
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_execute_select(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                     struct json *result)
+{
+    struct ovsdb_table *table;
+    const struct json *where, *columns_json, *sort_json;
+    struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+    struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
+    struct ovsdb_column_set sort = OVSDB_COLUMN_SET_INITIALIZER;
+    struct ovsdb_error *error;
+
+    table = parse_table(x, parser, "table");
+    where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+    columns_json = ovsdb_parser_member(parser, "columns",
+                                       OP_ARRAY | OP_OPTIONAL);
+    sort_json = ovsdb_parser_member(parser, "sort", OP_ARRAY | OP_OPTIONAL);
+
+    error = ovsdb_parser_get_error(parser);
+    if (!error) {
+        error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+                                          &condition);
+    }
+    if (!error) {
+        error = ovsdb_column_set_from_json(columns_json, table, &columns);
+    }
+    if (!error) {
+        error = ovsdb_column_set_from_json(sort_json, table, &sort);
+    }
+    if (!error) {
+        struct ovsdb_row_set rows = OVSDB_ROW_SET_INITIALIZER;
+
+        ovsdb_query_distinct(table, &condition, &columns, &rows);
+        ovsdb_row_set_sort(&rows, &sort);
+        json_object_put(result, "rows",
+                        ovsdb_row_set_to_json(&rows, &columns));
+
+        ovsdb_row_set_destroy(&rows);
+    }
+
+    ovsdb_column_set_destroy(&columns);
+    ovsdb_column_set_destroy(&sort);
+    ovsdb_condition_destroy(&condition);
+
+    return error;
+}
+
+struct update_row_cbdata {
+    size_t n_matches;
+    struct ovsdb_txn *txn;
+    const struct ovsdb_row *row;
+    const struct ovsdb_column_set *columns;
+};
+
+static bool
+update_row_cb(const struct ovsdb_row *row, void *ur_)
+{
+    struct update_row_cbdata *ur = ur_;
+
+    ur->n_matches++;
+    if (!ovsdb_row_equal_columns(row, ur->row, ur->columns)) {
+        ovsdb_row_update_columns(ovsdb_txn_row_modify(ur->txn, row),
+                                 ur->row, ur->columns);
+    }
+
+    return true;
+}
+
+struct ovsdb_error *
+ovsdb_execute_update(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                     struct json *result)
+{
+    struct ovsdb_table *table;
+    const struct json *where;
+    struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+    struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
+    struct ovsdb_row *row = NULL;
+    struct update_row_cbdata ur;
+    struct ovsdb_error *error;
+
+    table = parse_table(x, parser, "table");
+    where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+    error = ovsdb_parser_get_error(parser);
+    if (!error) {
+        error = parse_row(parser, "row", table, x->symtab, &row, &columns);
+    }
+    if (!error) {
+        error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+                                          &condition);
+    }
+    if (!error) {
+        ur.n_matches = 0;
+        ur.txn = x->txn;
+        ur.row = row;
+        ur.columns = &columns;
+        ovsdb_query(table, &condition, update_row_cb, &ur);
+        json_object_put(result, "count", json_integer_create(ur.n_matches));
+    }
+
+    ovsdb_row_destroy(row);
+    ovsdb_column_set_destroy(&columns);
+    ovsdb_condition_destroy(&condition);
+
+    return error;
+}
+
+struct delete_row_cbdata {
+    size_t n_matches;
+    const struct ovsdb_table *table;
+    struct ovsdb_txn *txn;
+};
+
+static bool
+delete_row_cb(const struct ovsdb_row *row, void *dr_)
+{
+    struct delete_row_cbdata *dr = dr_;
+
+    dr->n_matches++;
+    ovsdb_txn_row_delete(dr->txn, row);
+
+    return true;
+}
+
+struct ovsdb_error *
+ovsdb_execute_delete(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                     struct json *result)
+{
+    struct ovsdb_table *table;
+    const struct json *where;
+    struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+    struct ovsdb_error *error;
+
+    where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+    table = parse_table(x, parser, "table");
+    error = ovsdb_parser_get_error(parser);
+    if (!error) {
+        error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+                                          &condition);
+    }
+    if (!error) {
+        struct delete_row_cbdata dr;
+
+        dr.n_matches = 0;
+        dr.table = table;
+        dr.txn = x->txn;
+        ovsdb_query(table, &condition, delete_row_cb, &dr);
+
+        json_object_put(result, "count", json_integer_create(dr.n_matches));
+    }
+
+    ovsdb_condition_destroy(&condition);
+
+    return error;
+}
+
+struct wait_auxdata {
+    struct ovsdb_row_hash *actual;
+    struct ovsdb_row_hash *expected;
+    bool *equal;
+};
+
+static bool
+ovsdb_execute_wait_query_cb(const struct ovsdb_row *row, void *aux_)
+{
+    struct wait_auxdata *aux = aux_;
+
+    if (ovsdb_row_hash_contains(aux->expected, row)) {
+        ovsdb_row_hash_insert(aux->actual, row);
+        return true;
+    } else {
+        /* The query row isn't in the expected result set, so the actual and
+         * expected results sets definitely differ and we can short-circuit the
+         * rest of the query. */
+        *aux->equal = false;
+        return false;
+    }
+}
+
+static struct ovsdb_error *
+ovsdb_execute_wait(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                   struct json *result UNUSED)
+{
+    struct ovsdb_table *table;
+    const struct json *timeout, *where, *columns_json, *until, *rows;
+    struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+    struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
+    struct ovsdb_row_hash expected = OVSDB_ROW_HASH_INITIALIZER(expected);
+    struct ovsdb_row_hash actual = OVSDB_ROW_HASH_INITIALIZER(actual);
+    struct ovsdb_error *error;
+    struct wait_auxdata aux;
+    long long int timeout_msec = 0;
+    size_t i;
+
+    timeout = ovsdb_parser_member(parser, "timeout", OP_NUMBER | OP_OPTIONAL);
+    where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+    columns_json = ovsdb_parser_member(parser, "columns",
+                                       OP_ARRAY | OP_OPTIONAL);
+    until = ovsdb_parser_member(parser, "until", OP_STRING);
+    rows = ovsdb_parser_member(parser, "rows", OP_ARRAY);
+    table = parse_table(x, parser, "table");
+    error = ovsdb_parser_get_error(parser);
+    if (!error) {
+        error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+                                          &condition);
+    }
+    if (!error) {
+        error = ovsdb_column_set_from_json(columns_json, table, &columns);
+    }
+    if (!error) {
+        if (timeout) {
+            timeout_msec = MIN(LLONG_MAX, json_real(timeout));
+            if (timeout_msec < 0) {
+                error = ovsdb_syntax_error(timeout, NULL,
+                                           "timeout must be nonnegative");
+            } else if (timeout_msec < x->timeout_msec) {
+                x->timeout_msec = timeout_msec;
+            }
+        } else {
+            timeout_msec = LLONG_MAX;
+        }
+        if (strcmp(json_string(until), "==")
+            && strcmp(json_string(until), "!=")) {
+            error = ovsdb_syntax_error(until, NULL,
+                                       "\"until\" must be \"==\" or \"!=\"");
+        }
+    }
+    if (!error) {
+        /* Parse "rows" into 'expected'. */
+        ovsdb_row_hash_init(&expected, &columns);
+        for (i = 0; i < rows->u.array.n; i++) {
+            struct ovsdb_error *error;
+            struct ovsdb_row *row;
+
+            row = ovsdb_row_create(table);
+            error = ovsdb_row_from_json(row, rows->u.array.elems[i], x->symtab,
+                                        NULL);
+            if (error) {
+                break;
+            }
+
+            if (!ovsdb_row_hash_insert(&expected, row)) {
+                /* XXX Perhaps we should abort with an error or log a
+                 * warning. */
+                ovsdb_row_destroy(row);
+            }
+        }
+    }
+    if (!error) {
+        /* Execute query. */
+        bool equal = true;
+        ovsdb_row_hash_init(&actual, &columns);
+        aux.actual = &actual;
+        aux.expected = &expected;
+        aux.equal = &equal;
+        ovsdb_query(table, &condition, ovsdb_execute_wait_query_cb, &aux);
+        if (equal) {
+            /* We know that every row in 'actual' is also in 'expected'.  We
+             * also know that all of the rows in 'actual' are distinct and that
+             * all of the rows in 'expected' are distinct.  Therefore, if
+             * 'actual' and 'expected' have the same number of rows, then they
+             * have the same content. */
+            size_t n_actual = ovsdb_row_hash_count(&actual);
+            size_t n_expected = ovsdb_row_hash_count(&expected);
+            equal = n_actual == n_expected;
+        }
+        if (!strcmp(json_string(until), "==") != equal) {
+            if (timeout && x->elapsed_msec >= timeout_msec) {
+                if (x->elapsed_msec) {
+                    error = ovsdb_error("timed out",
+                                        "\"wait\" timed out after %lld ms",
+                                        x->elapsed_msec);
+                } else {
+                    error = ovsdb_error("timed out", "\"wait\" timed out");
+                }
+            } else {
+                /* ovsdb_execute() will change this, if triggers really are
+                 * supported. */
+                error = ovsdb_error("not supported", "triggers not supported");
+            }
+        }
+    }
+
+
+    ovsdb_row_hash_destroy(&expected, true);
+    ovsdb_row_hash_destroy(&actual, false);
+    ovsdb_column_set_destroy(&columns);
+    ovsdb_condition_destroy(&condition);
+
+    return error;
+}
diff --git a/ovsdb/file.c b/ovsdb/file.c
new file mode 100644 (file)
index 0000000..377ca28
--- /dev/null
@@ -0,0 +1,341 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "file.h"
+
+#include <assert.h>
+#include <fcntl.h>
+
+#include "column.h"
+#include "log.h"
+#include "json.h"
+#include "ovsdb.h"
+#include "ovsdb-error.h"
+#include "row.h"
+#include "table.h"
+#include "transaction.h"
+#include "uuid.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_ovsdb_file
+#include "vlog.h"
+
+static struct ovsdb_error *ovsdb_file_txn_from_json(struct ovsdb *,
+                                                    const struct json *,
+                                                    struct ovsdb_txn **);
+static void ovsdb_file_replica_create(struct ovsdb *, struct ovsdb_log *);
+
+struct ovsdb_error *
+ovsdb_file_open(const char *file_name, bool read_only, struct ovsdb **dbp)
+{
+    struct ovsdb_schema *schema;
+    struct ovsdb_error *error;
+    struct ovsdb_log *log;
+    struct json *json;
+    struct ovsdb *db;
+
+    error = ovsdb_log_open(file_name, read_only ? O_RDONLY : O_RDWR, &log);
+    if (error) {
+        return error;
+    }
+
+    error = ovsdb_log_read(log, &json);
+    if (error) {
+        return error;
+    } else if (!json) {
+        return ovsdb_io_error(EOF, "%s: database file contains no schema",
+                              file_name);
+    }
+
+    error = ovsdb_schema_from_json(json, &schema);
+    if (error) {
+        json_destroy(json);
+        return ovsdb_wrap_error(error,
+                                "failed to parse \"%s\" as ovsdb schema",
+                                file_name);
+    }
+    json_destroy(json);
+
+    db = ovsdb_create(schema);
+    while ((error = ovsdb_log_read(log, &json)) == NULL && json) {
+        struct ovsdb_txn *txn;
+
+        error = ovsdb_file_txn_from_json(db, json, &txn);
+        json_destroy(json);
+        if (error) {
+            break;
+        }
+
+        ovsdb_txn_commit(txn, false);
+    }
+    if (error) {
+        char *msg = ovsdb_error_to_string(error);
+        VLOG_WARN("%s", msg);
+        free(msg);
+
+        ovsdb_error_destroy(error);
+    }
+
+    if (!read_only) {
+        ovsdb_file_replica_create(db, log);
+    } else {
+        ovsdb_log_close(log);
+    }
+
+    *dbp = db;
+    return NULL;
+}
+
+static struct ovsdb_error *
+ovsdb_file_txn_row_from_json(struct ovsdb_txn *txn, struct ovsdb_table *table,
+                             const struct uuid *row_uuid, struct json *json)
+{
+    const struct ovsdb_row *row = ovsdb_table_get_row(table, row_uuid);
+    if (json->type == JSON_NULL) {
+        if (!row) {
+            return ovsdb_syntax_error(NULL, NULL, "transaction deletes "
+                                      "row "UUID_FMT" that does not exist",
+                                      UUID_ARGS(row_uuid));
+        }
+        ovsdb_txn_row_delete(txn, row);
+        return NULL;
+    } else if (row) {
+        return ovsdb_row_from_json(ovsdb_txn_row_modify(txn, row),
+                                   json, NULL, NULL);
+    } else {
+        struct ovsdb_error *error;
+        struct ovsdb_row *new;
+
+        new = ovsdb_row_create(table);
+        *ovsdb_row_get_uuid_rw(new) = *row_uuid;
+        error = ovsdb_row_from_json(new, json, NULL, NULL);
+        if (error) {
+            ovsdb_row_destroy(new);
+        }
+
+        ovsdb_txn_row_insert(txn, new);
+
+        return error;
+    }
+}
+
+static struct ovsdb_error *
+ovsdb_file_txn_table_from_json(struct ovsdb_txn *txn,
+                               struct ovsdb_table *table, struct json *json)
+{
+    struct shash_node *node;
+
+    if (json->type != JSON_OBJECT) {
+        return ovsdb_syntax_error(json, NULL, "object expected");
+    }
+
+    SHASH_FOR_EACH (node, json->u.object) {
+        const char *uuid_string = node->name;
+        struct json *txn_row_json = node->data;
+        struct ovsdb_error *error;
+        struct uuid row_uuid;
+
+        if (!uuid_from_string(&row_uuid, uuid_string)) {
+            return ovsdb_syntax_error(json, NULL, "\"%s\" is not a valid UUID",
+                                      uuid_string);
+        }
+
+        error = ovsdb_file_txn_row_from_json(txn, table, &row_uuid,
+                                             txn_row_json);
+        if (error) {
+            return error;
+        }
+    }
+
+    return NULL;
+}
+
+static struct ovsdb_error *
+ovsdb_file_txn_from_json(struct ovsdb *db, const struct json *json,
+                         struct ovsdb_txn **txnp)
+{
+    struct ovsdb_error *error;
+    struct shash_node *node;
+    struct ovsdb_txn *txn;
+
+    *txnp = NULL;
+    if (json->type != JSON_OBJECT) {
+        return ovsdb_syntax_error(json, NULL, "object expected");
+    }
+
+    txn = ovsdb_txn_create(db);
+    SHASH_FOR_EACH (node, json->u.object) {
+        const char *table_name = node->name;
+        struct json *txn_table_json = node->data;
+        struct ovsdb_table *table;
+
+        table = shash_find_data(&db->tables, table_name);
+        if (!table) {
+            error = ovsdb_syntax_error(json, "unknown table",
+                                       "No table named %s.", table_name);
+            goto error;
+        }
+
+        error = ovsdb_file_txn_table_from_json(txn, table, txn_table_json);
+        if (error) {
+            goto error;
+        }
+    }
+    *txnp = txn;
+    return NULL;
+
+error:
+    ovsdb_txn_abort(txn);
+    return error;
+}
+\f
+/* Replica implementation. */
+
+struct ovsdb_file_replica {
+    struct ovsdb_replica replica;
+    struct ovsdb_log *log;
+};
+
+static const struct ovsdb_replica_class ovsdb_file_replica_class;
+
+static void
+ovsdb_file_replica_create(struct ovsdb *db, struct ovsdb_log *log)
+{
+    struct ovsdb_file_replica *r = xmalloc(sizeof *r);
+    ovsdb_replica_init(&r->replica, &ovsdb_file_replica_class);
+    r->log = log;
+    ovsdb_add_replica(db, &r->replica);
+
+}
+
+static struct ovsdb_file_replica *
+ovsdb_file_replica_cast(struct ovsdb_replica *replica)
+{
+    assert(replica->class == &ovsdb_file_replica_class);
+    return CONTAINER_OF(replica, struct ovsdb_file_replica, replica);
+}
+
+struct ovsdb_file_replica_aux {
+    struct json *json;          /* JSON for the whole transaction. */
+    struct json *table_json;    /* JSON for 'table''s transaction. */
+    struct ovsdb_table *table;  /* Table described in 'table_json'.  */
+};
+
+static bool
+ovsdb_file_replica_change_cb(const struct ovsdb_row *old,
+                             const struct ovsdb_row *new,
+                             void *aux_)
+{
+    struct ovsdb_file_replica_aux *aux = aux_;
+    struct json *row;
+
+    if (!new) {
+        row = json_null_create();
+    } else {
+        struct shash_node *node;
+
+        row = NULL;
+        SHASH_FOR_EACH (node, &new->table->schema->columns) {
+            const struct ovsdb_column *column = node->data;
+            const struct ovsdb_type *type = &column->type;
+            unsigned int idx = column->index;
+
+            if (idx != OVSDB_COL_UUID && column->persistent
+                && (!old || !ovsdb_datum_equals(&old->fields[idx],
+                                                &new->fields[idx], type)))
+            {
+                if (!row) {
+                    row = json_object_create();
+                }
+                json_object_put(row, column->name,
+                                ovsdb_datum_to_json(&new->fields[idx], type));
+            }
+        }
+    }
+
+    if (row) {
+        struct ovsdb_table *table = new ? new->table : old->table;
+        char uuid[UUID_LEN + 1];
+
+        if (table != aux->table) {
+            /* Create JSON object for transaction overall. */
+            if (!aux->json) {
+                aux->json = json_object_create();
+            }
+
+            /* Create JSON object for transaction on this table. */
+            aux->table_json = json_object_create();
+            aux->table = table;
+            json_object_put(aux->json, table->schema->name, aux->table_json);
+        }
+
+        /* Add row to transaction for this table. */
+        snprintf(uuid, sizeof uuid,
+                 UUID_FMT, UUID_ARGS(ovsdb_row_get_uuid(new ? new : old)));
+        json_object_put(aux->table_json, uuid, row);
+    }
+
+    return true;
+}
+
+static struct ovsdb_error *
+ovsdb_file_replica_commit(struct ovsdb_replica *r_,
+                          const struct ovsdb_txn *txn, bool durable)
+{
+    struct ovsdb_file_replica *r = ovsdb_file_replica_cast(r_);
+    struct ovsdb_file_replica_aux aux;
+    struct ovsdb_error *error;
+
+    aux.json = NULL;
+    aux.table_json = NULL;
+    aux.table = NULL;
+    ovsdb_txn_for_each_change(txn, ovsdb_file_replica_change_cb, &aux);
+
+    if (!aux.json) {
+        /* Nothing to commit. */
+        return NULL;
+    }
+
+    error = ovsdb_log_write(r->log, aux.json);
+    json_destroy(aux.json);
+    if (error) {
+        return ovsdb_wrap_error(error, "writing transaction failed");
+    }
+
+    if (durable) {
+        error = ovsdb_log_commit(r->log);
+        if (error) {
+            return ovsdb_wrap_error(error, "committing transaction failed");
+        }
+    }
+
+    return NULL;
+}
+
+static void
+ovsdb_file_replica_destroy(struct ovsdb_replica *r_)
+{
+    struct ovsdb_file_replica *r = ovsdb_file_replica_cast(r_);
+
+    ovsdb_log_close(r->log);
+    free(r);
+}
+
+static const struct ovsdb_replica_class ovsdb_file_replica_class = {
+    ovsdb_file_replica_commit,
+    ovsdb_file_replica_destroy
+};
diff --git a/ovsdb/file.h b/ovsdb/file.h
new file mode 100644 (file)
index 0000000..2a27477
--- /dev/null
@@ -0,0 +1,28 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_FILE_H
+#define OVSDB_FILE_H 1
+
+#include <stdbool.h>
+#include "compiler.h"
+
+struct ovsdb;
+
+struct ovsdb_error *ovsdb_file_open(const char *file_name, bool read_only,
+                                    struct ovsdb **)
+    WARN_UNUSED_RESULT;
+
+#endif /* ovsdb/file.h */
diff --git a/ovsdb/jsonrpc-server.c b/ovsdb/jsonrpc-server.c
new file mode 100644 (file)
index 0000000..897f9ae
--- /dev/null
@@ -0,0 +1,952 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "jsonrpc-server.h"
+
+#include <errno.h>
+
+#include "column.h"
+#include "json.h"
+#include "jsonrpc.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "ovsdb.h"
+#include "reconnect.h"
+#include "row.h"
+#include "stream.h"
+#include "table.h"
+#include "timeval.h"
+#include "transaction.h"
+#include "trigger.h"
+
+#define THIS_MODULE VLM_ovsdb_jsonrpc_server
+#include "vlog.h"
+
+struct ovsdb_jsonrpc_session;
+
+/* Sessions. */
+static void ovsdb_jsonrpc_session_create_active(struct ovsdb_jsonrpc_server *,
+                                                const char *name);
+static void ovsdb_jsonrpc_session_create_passive(struct ovsdb_jsonrpc_server *,
+                                                 struct stream *);
+static void ovsdb_jsonrpc_session_run_all(struct ovsdb_jsonrpc_server *);
+static void ovsdb_jsonrpc_session_wait_all(struct ovsdb_jsonrpc_server *);
+
+/* Triggers. */
+static void ovsdb_jsonrpc_trigger_create(struct ovsdb_jsonrpc_session *,
+                                         struct json *id, struct json *params);
+static struct ovsdb_jsonrpc_trigger *ovsdb_jsonrpc_trigger_find(
+    struct ovsdb_jsonrpc_session *, const struct json *id, size_t hash);
+static void ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *);
+static void ovsdb_jsonrpc_trigger_complete_all(struct ovsdb_jsonrpc_session *);
+static void ovsdb_jsonrpc_trigger_complete_done(
+    struct ovsdb_jsonrpc_session *);
+
+/* Monitors. */
+static struct json *ovsdb_jsonrpc_monitor_create(
+    struct ovsdb_jsonrpc_session *, struct json *params);
+static struct jsonrpc_msg *ovsdb_jsonrpc_monitor_cancel(
+    struct ovsdb_jsonrpc_session *,
+    struct json_array *params,
+    const struct json *request_id);
+static void ovsdb_jsonrpc_monitor_remove_all(struct ovsdb_jsonrpc_session *);
+\f
+/* JSON-RPC database server. */
+
+struct ovsdb_jsonrpc_server {
+    struct ovsdb *db;
+
+    struct list sessions;       /* List of "struct ovsdb_jsonrpc_session"s. */
+    unsigned int n_sessions, max_sessions;
+    unsigned int max_triggers;
+
+    struct pstream **listeners;
+    size_t n_listeners, allocated_listeners;
+};
+
+struct ovsdb_jsonrpc_server *
+ovsdb_jsonrpc_server_create(struct ovsdb *db)
+{
+    struct ovsdb_jsonrpc_server *server = xzalloc(sizeof *server);
+    server->db = db;
+    server->max_sessions = 64;
+    server->max_triggers = 64;
+    list_init(&server->sessions);
+    return server;
+}
+
+int
+ovsdb_jsonrpc_server_listen(struct ovsdb_jsonrpc_server *svr, const char *name)
+{
+    struct pstream *pstream;
+    int error;
+
+    error = pstream_open(name, &pstream);
+    if (error) {
+        return error;
+    }
+
+    if (svr->n_listeners >= svr->allocated_listeners) {
+        svr->listeners = x2nrealloc(svr->listeners, &svr->allocated_listeners,
+                                    sizeof *svr->listeners);
+    }
+    svr->listeners[svr->n_listeners++] = pstream;
+    return 0;
+}
+
+void
+ovsdb_jsonrpc_server_connect(struct ovsdb_jsonrpc_server *svr,
+                             const char *name)
+{
+    ovsdb_jsonrpc_session_create_active(svr, name);
+}
+
+void
+ovsdb_jsonrpc_server_run(struct ovsdb_jsonrpc_server *svr)
+{
+    size_t i;
+
+    /* Accept new connections. */
+    for (i = 0; i < svr->n_listeners && svr->n_sessions < svr->max_sessions;) {
+        struct pstream *listener = svr->listeners[i];
+        struct stream *stream;
+        int error;
+
+        error = pstream_accept(listener, &stream);
+        if (!error) {
+            ovsdb_jsonrpc_session_create_passive(svr, stream);
+        } else if (error == EAGAIN) {
+            i++;
+        } else if (error) {
+            VLOG_WARN("%s: accept failed: %s",
+                      pstream_get_name(listener), strerror(error));
+            pstream_close(listener);
+            svr->listeners[i] = svr->listeners[--svr->n_listeners];
+        }
+    }
+
+    /* Handle each session. */
+    ovsdb_jsonrpc_session_run_all(svr);
+}
+
+void
+ovsdb_jsonrpc_server_wait(struct ovsdb_jsonrpc_server *svr)
+{
+    if (svr->n_sessions < svr->max_sessions) {
+        size_t i;
+
+        for (i = 0; i < svr->n_listeners; i++) {
+            pstream_wait(svr->listeners[i]);
+        }
+    }
+
+    ovsdb_jsonrpc_session_wait_all(svr);
+}
+\f
+/* JSON-RPC database server session. */
+
+struct ovsdb_jsonrpc_session {
+    struct ovsdb_jsonrpc_server *server;
+    struct list node;           /* Element in server's sessions list. */
+
+    /* Triggers. */
+    struct hmap triggers;       /* Hmap of "struct ovsdb_jsonrpc_trigger"s. */
+    struct list completions;    /* Completed triggers. */
+
+    /* Monitors. */
+    struct hmap monitors;       /* Hmap of "struct ovsdb_jsonrpc_monitor"s. */
+
+    /* Connecting and reconnecting. */
+    struct reconnect *reconnect; /* For back-off. */
+    bool active;                /* Active or passive connection? */
+    struct jsonrpc *rpc;
+    struct stream *stream;      /* Only if active == false and rpc == NULL. */
+};
+
+static void ovsdb_jsonrpc_session_close(struct ovsdb_jsonrpc_session *);
+static void ovsdb_jsonrpc_session_disconnect(struct ovsdb_jsonrpc_session *s);
+static int ovsdb_jsonrpc_session_run(struct ovsdb_jsonrpc_session *);
+static void ovsdb_jsonrpc_session_wait(struct ovsdb_jsonrpc_session *);
+static void ovsdb_jsonrpc_session_got_request(struct ovsdb_jsonrpc_session *,
+                                             struct jsonrpc_msg *);
+static void ovsdb_jsonrpc_session_got_notify(struct ovsdb_jsonrpc_session *,
+                                             struct jsonrpc_msg *);
+
+static struct ovsdb_jsonrpc_session *
+ovsdb_jsonrpc_session_create(struct ovsdb_jsonrpc_server *svr,
+                             const char *name, bool active)
+{
+    struct ovsdb_jsonrpc_session *s;
+
+    s = xzalloc(sizeof *s);
+    s->server = svr;
+    list_push_back(&svr->sessions, &s->node);
+    hmap_init(&s->triggers);
+    hmap_init(&s->monitors);
+    list_init(&s->completions);
+    s->reconnect = reconnect_create(time_msec());
+    reconnect_set_name(s->reconnect, name);
+    reconnect_enable(s->reconnect, time_msec());
+    s->active = active;
+
+    svr->n_sessions++;
+
+    return s;
+}
+
+static void
+ovsdb_jsonrpc_session_create_active(struct ovsdb_jsonrpc_server *svr,
+                                    const char *name)
+{
+    ovsdb_jsonrpc_session_create(svr, name, true);
+}
+
+static void
+ovsdb_jsonrpc_session_create_passive(struct ovsdb_jsonrpc_server *svr,
+                                     struct stream *stream)
+{
+    struct ovsdb_jsonrpc_session *s;
+
+    s = ovsdb_jsonrpc_session_create(svr, stream_get_name(stream), false);
+    reconnect_connected(s->reconnect, time_msec());
+    s->rpc = jsonrpc_open(stream);
+}
+
+static void
+ovsdb_jsonrpc_session_close(struct ovsdb_jsonrpc_session *s)
+{
+    ovsdb_jsonrpc_session_disconnect(s);
+    list_remove(&s->node);
+    s->server->n_sessions--;
+}
+
+static void
+ovsdb_jsonrpc_session_disconnect(struct ovsdb_jsonrpc_session *s)
+{
+    reconnect_disconnected(s->reconnect, time_msec(), 0);
+    if (s->rpc) {
+        jsonrpc_error(s->rpc, EOF);
+        ovsdb_jsonrpc_trigger_complete_all(s);
+        ovsdb_jsonrpc_monitor_remove_all(s);
+        jsonrpc_close(s->rpc);
+        s->rpc = NULL;
+    } else if (s->stream) {
+        stream_close(s->stream);
+        s->stream = NULL;
+    }
+}
+
+static void
+ovsdb_jsonrpc_session_connect(struct ovsdb_jsonrpc_session *s)
+{
+    ovsdb_jsonrpc_session_disconnect(s);
+    if (s->active) {
+        int error = stream_open(reconnect_get_name(s->reconnect), &s->stream);
+        if (error) {
+            reconnect_connect_failed(s->reconnect, time_msec(), error);
+        } else {
+            reconnect_connecting(s->reconnect, time_msec());
+        }
+    }
+}
+
+static int
+ovsdb_jsonrpc_session_run(struct ovsdb_jsonrpc_session *s)
+{
+    if (s->rpc) {
+        struct jsonrpc_msg *msg;
+        int error;
+
+        jsonrpc_run(s->rpc);
+
+        ovsdb_jsonrpc_trigger_complete_done(s);
+
+        if (!jsonrpc_get_backlog(s->rpc) && !jsonrpc_recv(s->rpc, &msg)) {
+            reconnect_received(s->reconnect, time_msec());
+            if (msg->type == JSONRPC_REQUEST) {
+                ovsdb_jsonrpc_session_got_request(s, msg);
+            } else if (msg->type == JSONRPC_NOTIFY) {
+                ovsdb_jsonrpc_session_got_notify(s, msg);
+            } else if (msg->type == JSONRPC_REPLY
+                       && msg->id && msg->id->type == JSON_STRING
+                       && !strcmp(msg->id->u.string, "echo")) {
+                /* It's a reply to our echo request.  Ignore it. */
+            } else {
+                VLOG_WARN("%s: received unexpected %s message",
+                          jsonrpc_get_name(s->rpc),
+                          jsonrpc_msg_type_to_string(msg->type));
+                jsonrpc_error(s->rpc, EPROTO);
+                jsonrpc_msg_destroy(msg);
+            }
+        }
+
+        error = jsonrpc_get_status(s->rpc);
+        if (error) {
+            if (s->active) {
+                ovsdb_jsonrpc_session_disconnect(s);
+            } else {
+                return error;
+            }
+        }
+    } else if (s->stream) {
+        int error = stream_connect(s->stream);
+        if (!error) {
+            reconnect_connected(s->reconnect, time_msec());
+            s->rpc = jsonrpc_open(s->stream);
+            s->stream = NULL;
+        } else if (error != EAGAIN) {
+            reconnect_connect_failed(s->reconnect, time_msec(), error);
+            stream_close(s->stream);
+            s->stream = NULL;
+        }
+    }
+
+    switch (reconnect_run(s->reconnect, time_msec())) {
+    case RECONNECT_CONNECT:
+        ovsdb_jsonrpc_session_connect(s);
+        break;
+
+    case RECONNECT_DISCONNECT:
+        ovsdb_jsonrpc_session_disconnect(s);
+        break;
+
+    case RECONNECT_PROBE:
+        if (s->rpc) {
+            struct json *params;
+            struct jsonrpc_msg *request;
+
+            params = json_array_create_empty();
+            request = jsonrpc_create_request("echo", params, NULL);
+            json_destroy(request->id);
+            request->id = json_string_create("echo");
+            jsonrpc_send(s->rpc, request);
+        }
+        break;
+    }
+    return s->active || s->rpc ? 0 : ETIMEDOUT;
+}
+
+static void
+ovsdb_jsonrpc_session_run_all(struct ovsdb_jsonrpc_server *svr)
+{
+    struct ovsdb_jsonrpc_session *s, *next;
+
+    LIST_FOR_EACH_SAFE (s, next, struct ovsdb_jsonrpc_session, node,
+                        &svr->sessions) {
+        int error = ovsdb_jsonrpc_session_run(s);
+        if (error) {
+            ovsdb_jsonrpc_session_close(s);
+        }
+    }
+}
+
+static void
+ovsdb_jsonrpc_session_wait(struct ovsdb_jsonrpc_session *s)
+{
+    if (s->rpc) {
+        jsonrpc_wait(s->rpc);
+        if (!jsonrpc_get_backlog(s->rpc)) {
+            jsonrpc_recv_wait(s->rpc);
+        }
+    } else if (s->stream) {
+        stream_connect_wait(s->stream);
+    }
+    reconnect_wait(s->reconnect, time_msec());
+}
+
+static void
+ovsdb_jsonrpc_session_wait_all(struct ovsdb_jsonrpc_server *svr)
+{
+    struct ovsdb_jsonrpc_session *s;
+
+    LIST_FOR_EACH (s, struct ovsdb_jsonrpc_session, node, &svr->sessions) {
+        ovsdb_jsonrpc_session_wait(s);
+    }
+}
+
+static struct jsonrpc_msg *
+execute_transaction(struct ovsdb_jsonrpc_session *s,
+                    struct jsonrpc_msg *request)
+{
+    ovsdb_jsonrpc_trigger_create(s, request->id, request->params);
+    request->id = NULL;
+    request->params = NULL;
+    return NULL;
+}
+
+static void
+ovsdb_jsonrpc_session_got_request(struct ovsdb_jsonrpc_session *s,
+                                  struct jsonrpc_msg *request)
+{
+    struct jsonrpc_msg *reply;
+
+    if (!strcmp(request->method, "transact")) {
+        reply = execute_transaction(s, request);
+    } else if (!strcmp(request->method, "monitor")) {
+        reply = jsonrpc_create_reply(
+            ovsdb_jsonrpc_monitor_create(s, request->params), request->id);
+    } else if (!strcmp(request->method, "monitor_cancel")) {
+        reply = ovsdb_jsonrpc_monitor_cancel(s, json_array(request->params),
+                                             request->id);
+    } else if (!strcmp(request->method, "get_schema")) {
+        reply = jsonrpc_create_reply(
+            ovsdb_schema_to_json(s->server->db->schema), request->id);
+    } else if (!strcmp(request->method, "echo")) {
+        reply = jsonrpc_create_reply(json_clone(request->params), request->id);
+    } else {
+        reply = jsonrpc_create_error(json_string_create("unknown method"),
+                                     request->id);
+    }
+
+    if (reply) {
+        jsonrpc_msg_destroy(request);
+        jsonrpc_send(s->rpc, reply);
+    }
+}
+
+static void
+execute_cancel(struct ovsdb_jsonrpc_session *s, struct jsonrpc_msg *request)
+{
+    if (json_array(request->params)->n == 1) {
+        struct ovsdb_jsonrpc_trigger *t;
+        struct json *id;
+
+        id = request->params->u.array.elems[0];
+        t = ovsdb_jsonrpc_trigger_find(s, id, json_hash(id, 0));
+        if (t) {
+            ovsdb_jsonrpc_trigger_complete(t);
+        }
+    }
+}
+
+static void
+ovsdb_jsonrpc_session_got_notify(struct ovsdb_jsonrpc_session *s,
+                                 struct jsonrpc_msg *request)
+{
+    if (!strcmp(request->method, "cancel")) {
+        execute_cancel(s, request);
+    }
+    jsonrpc_msg_destroy(request);
+}
+\f
+/* JSON-RPC database server triggers.
+ *
+ * (Every transaction is treated as a trigger even if it doesn't actually have
+ * any "wait" operations.) */
+
+struct ovsdb_jsonrpc_trigger {
+    struct ovsdb_trigger trigger;
+    struct ovsdb_jsonrpc_session *session;
+    struct hmap_node hmap_node; /* In session's "triggers" hmap. */
+    struct json *id;
+};
+
+static void
+ovsdb_jsonrpc_trigger_create(struct ovsdb_jsonrpc_session *s,
+                             struct json *id, struct json *params)
+{
+    struct ovsdb_jsonrpc_trigger *t;
+    size_t hash;
+
+    /* Check for duplicate ID. */
+    hash = json_hash(id, 0);
+    t = ovsdb_jsonrpc_trigger_find(s, id, hash);
+    if (t) {
+        jsonrpc_send(s->rpc, jsonrpc_create_error(
+                         json_string_create("duplicate request ID"), id));
+        json_destroy(id);
+        json_destroy(params);
+        return;
+    }
+
+    /* Insert into trigger table. */
+    t = xmalloc(sizeof *t);
+    ovsdb_trigger_init(s->server->db,
+                       &t->trigger, params, &s->completions,
+                       time_msec());
+    t->session = s;
+    t->id = id;
+    hmap_insert(&s->triggers, &t->hmap_node, hash);
+
+    /* Complete early if possible. */
+    if (ovsdb_trigger_is_complete(&t->trigger)) {
+        ovsdb_jsonrpc_trigger_complete(t);
+    }
+}
+
+static struct ovsdb_jsonrpc_trigger *
+ovsdb_jsonrpc_trigger_find(struct ovsdb_jsonrpc_session *s,
+                           const struct json *id, size_t hash)
+{
+    struct ovsdb_jsonrpc_trigger *t;
+
+    HMAP_FOR_EACH_WITH_HASH (t, struct ovsdb_jsonrpc_trigger, hmap_node, hash,
+                             &s->triggers) {
+        if (json_equal(t->id, id)) {
+            return t;
+        }
+    }
+
+    return NULL;
+}
+
+static void
+ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *t)
+{
+    struct ovsdb_jsonrpc_session *s = t->session;
+
+    if (s->rpc && !jsonrpc_get_status(s->rpc)) {
+        struct jsonrpc_msg *reply;
+        struct json *result;
+
+        result = ovsdb_trigger_steal_result(&t->trigger);
+        if (result) {
+            reply = jsonrpc_create_reply(result, t->id);
+        } else {
+            reply = jsonrpc_create_error(json_string_create("canceled"),
+                                         t->id);
+        }
+        jsonrpc_send(s->rpc, reply);
+    }
+
+    json_destroy(t->id);
+    ovsdb_trigger_destroy(&t->trigger);
+    hmap_remove(&s->triggers, &t->hmap_node);
+    free(t);
+}
+
+static void
+ovsdb_jsonrpc_trigger_complete_all(struct ovsdb_jsonrpc_session *s)
+{
+    struct ovsdb_jsonrpc_trigger *t, *next;
+    HMAP_FOR_EACH_SAFE (t, next, struct ovsdb_jsonrpc_trigger, hmap_node,
+                        &s->triggers) {
+        ovsdb_jsonrpc_trigger_complete(t);
+    }
+}
+
+static void
+ovsdb_jsonrpc_trigger_complete_done(struct ovsdb_jsonrpc_session *s)
+{
+    while (!list_is_empty(&s->completions)) {
+        struct ovsdb_jsonrpc_trigger *t
+            = CONTAINER_OF(s->completions.next,
+                           struct ovsdb_jsonrpc_trigger, trigger.node);
+        ovsdb_jsonrpc_trigger_complete(t);
+    }
+}
+\f
+/* JSON-RPC database table monitors. */
+
+enum ovsdb_jsonrpc_monitor_selection {
+    OJMS_INITIAL = 1 << 0,      /* All rows when monitor is created. */
+    OJMS_INSERT = 1 << 1,       /* New rows. */
+    OJMS_DELETE = 1 << 2,       /* Deleted rows. */
+    OJMS_MODIFY = 1 << 3        /* Modified rows. */
+};
+
+struct ovsdb_jsonrpc_monitor_table {
+    const struct ovsdb_table *table;
+    enum ovsdb_jsonrpc_monitor_selection select;
+    struct ovsdb_column_set columns;
+};
+
+struct ovsdb_jsonrpc_monitor {
+    struct ovsdb_replica replica;
+    struct ovsdb_jsonrpc_session *session;
+    struct hmap_node node;      /* In ovsdb_jsonrpc_session's "monitors". */
+
+    struct json *monitor_id;
+    struct shash tables;     /* Holds "struct ovsdb_jsonrpc_monitor_table"s. */
+};
+
+static const struct ovsdb_replica_class ovsdb_jsonrpc_replica_class;
+
+struct ovsdb_jsonrpc_monitor *ovsdb_jsonrpc_monitor_find(
+    struct ovsdb_jsonrpc_session *, const struct json *monitor_id);
+static void ovsdb_jsonrpc_monitor_destroy(struct ovsdb_replica *);
+static struct json *ovsdb_jsonrpc_monitor_get_initial(
+    const struct ovsdb_jsonrpc_monitor *);
+
+static bool
+parse_bool(struct ovsdb_parser *parser, const char *name, bool default_value)
+{
+    const struct json *json;
+
+    json = ovsdb_parser_member(parser, name, OP_BOOLEAN | OP_OPTIONAL);
+    return json ? json_boolean(json) : default_value;
+}
+
+struct ovsdb_jsonrpc_monitor *
+ovsdb_jsonrpc_monitor_find(struct ovsdb_jsonrpc_session *s,
+                           const struct json *monitor_id)
+{
+    struct ovsdb_jsonrpc_monitor *m;
+
+    HMAP_FOR_EACH_WITH_HASH (m, struct ovsdb_jsonrpc_monitor, node,
+                             json_hash(monitor_id, 0), &s->monitors) {
+        if (json_equal(m->monitor_id, monitor_id)) {
+            return m;
+        }
+    }
+
+    return NULL;
+}
+
+static struct json *
+ovsdb_jsonrpc_monitor_create(struct ovsdb_jsonrpc_session *s,
+                             struct json *params)
+{
+    struct ovsdb_jsonrpc_monitor *m = NULL;
+    struct json *monitor_id, *monitor_requests;
+    struct ovsdb_error *error = NULL;
+    struct shash_node *node;
+    struct json *json;
+
+    if (json_array(params)->n != 2) {
+        error = ovsdb_syntax_error(params, NULL, "invalid parameters");
+        goto error;
+    }
+    monitor_id = params->u.array.elems[0];
+    monitor_requests = params->u.array.elems[1];
+    if (monitor_requests->type != JSON_OBJECT) {
+        error = ovsdb_syntax_error(monitor_requests, NULL,
+                                   "monitor-requests must be object");
+        goto error;
+    }
+
+    if (ovsdb_jsonrpc_monitor_find(s, monitor_id)) {
+        error = ovsdb_syntax_error(monitor_id, NULL, "duplicate monitor ID");
+        goto error;
+    }
+
+    m = xzalloc(sizeof *m);
+    ovsdb_replica_init(&m->replica, &ovsdb_jsonrpc_replica_class);
+    ovsdb_add_replica(s->server->db, &m->replica);
+    m->session = s;
+    hmap_insert(&s->monitors, &m->node, json_hash(monitor_id, 0));
+    m->monitor_id = json_clone(monitor_id);
+    shash_init(&m->tables);
+
+    SHASH_FOR_EACH (node, json_object(monitor_requests)) {
+        const struct ovsdb_table *table;
+        struct ovsdb_jsonrpc_monitor_table *mt;
+        const struct json *columns_json, *select_json;
+        struct ovsdb_parser parser;
+
+        table = ovsdb_get_table(s->server->db, node->name);
+        if (!table) {
+            error = ovsdb_syntax_error(NULL, NULL,
+                                       "no table named %s", node->name);
+            goto error;
+        }
+
+        mt = xzalloc(sizeof *mt);
+        mt->table = table;
+        mt->select = OJMS_INITIAL | OJMS_INSERT | OJMS_DELETE | OJMS_MODIFY;
+        ovsdb_column_set_init(&mt->columns);
+        shash_add(&m->tables, table->schema->name, mt);
+
+        ovsdb_parser_init(&parser, node->data, "table %s", node->name);
+        columns_json = ovsdb_parser_member(&parser, "columns",
+                                           OP_ARRAY | OP_OPTIONAL);
+        select_json = ovsdb_parser_member(&parser, "select",
+                                          OP_OBJECT | OP_OPTIONAL);
+        error = ovsdb_parser_finish(&parser);
+        if (error) {
+            goto error;
+        }
+
+        if (columns_json) {
+            error = ovsdb_column_set_from_json(columns_json, table,
+                                               &mt->columns);
+            if (error) {
+                goto error;
+            }
+        } else {
+            struct shash_node *node;
+
+            SHASH_FOR_EACH (node, &table->schema->columns) {
+                const struct ovsdb_column *column = node->data;
+                if (column->index != OVSDB_COL_UUID) {
+                    ovsdb_column_set_add(&mt->columns, column);
+                }
+            }
+        }
+
+        if (select_json) {
+            mt->select = 0;
+            ovsdb_parser_init(&parser, select_json, "table %s select",
+                              table->schema->name);
+            if (parse_bool(&parser, "initial", true)) {
+                mt->select |= OJMS_INITIAL;
+            }
+            if (parse_bool(&parser, "insert", true)) {
+                mt->select |= OJMS_INSERT;
+            }
+            if (parse_bool(&parser, "delete", true)) {
+                mt->select |= OJMS_DELETE;
+            }
+            if (parse_bool(&parser, "modify", true)) {
+                mt->select |= OJMS_MODIFY;
+            }
+            error = ovsdb_parser_finish(&parser);
+            if (error) {
+                goto error;
+            }
+        }
+    }
+
+    return ovsdb_jsonrpc_monitor_get_initial(m);
+
+error:
+    if (m) {
+        ovsdb_remove_replica(s->server->db, &m->replica);
+    }
+
+    json = ovsdb_error_to_json(error);
+    ovsdb_error_destroy(error);
+    return json;
+}
+
+static struct jsonrpc_msg *
+ovsdb_jsonrpc_monitor_cancel(struct ovsdb_jsonrpc_session *s,
+                             struct json_array *params,
+                             const struct json *request_id)
+{
+    if (params->n != 1) {
+        return jsonrpc_create_error(json_string_create("invalid parameters"),
+                                    request_id);
+    } else {
+        struct ovsdb_jsonrpc_monitor *m;
+
+        m = ovsdb_jsonrpc_monitor_find(s, params->elems[0]);
+        if (!m) {
+            return jsonrpc_create_error(json_string_create("unknown monitor"),
+                                        request_id);
+        } else {
+            ovsdb_remove_replica(s->server->db, &m->replica);
+            return jsonrpc_create_reply(json_object_create(), request_id);
+        }
+    }
+}
+
+static void
+ovsdb_jsonrpc_monitor_remove_all(struct ovsdb_jsonrpc_session *s)
+{
+    struct ovsdb_jsonrpc_monitor *m, *next;
+
+    HMAP_FOR_EACH_SAFE (m, next,
+                        struct ovsdb_jsonrpc_monitor, node, &s->monitors) {
+        ovsdb_remove_replica(s->server->db, &m->replica);
+    }
+}
+
+static struct ovsdb_jsonrpc_monitor *
+ovsdb_jsonrpc_monitor_cast(struct ovsdb_replica *replica)
+{
+    assert(replica->class == &ovsdb_jsonrpc_replica_class);
+    return CONTAINER_OF(replica, struct ovsdb_jsonrpc_monitor, replica);
+}
+
+struct ovsdb_jsonrpc_monitor_aux {
+    bool initial;               /* Sending initial contents of table? */
+    const struct ovsdb_jsonrpc_monitor *monitor;
+    struct json *json;          /* JSON for the whole transaction. */
+
+    /* Current table.  */
+    struct ovsdb_jsonrpc_monitor_table *mt;
+    struct json *table_json;    /* JSON for table's transaction. */
+};
+
+static bool
+ovsdb_jsonrpc_monitor_change_cb(const struct ovsdb_row *old,
+                                const struct ovsdb_row *new,
+                                void *aux_)
+{
+    struct ovsdb_jsonrpc_monitor_aux *aux = aux_;
+    const struct ovsdb_jsonrpc_monitor *m = aux->monitor;
+    struct ovsdb_table *table = new ? new->table : old->table;
+    enum ovsdb_jsonrpc_monitor_selection type;
+    struct json *old_json, *new_json;
+    struct json *row_json;
+    char uuid[UUID_LEN + 1];
+    int n_changed;
+    size_t i;
+
+    if (!aux->mt || table != aux->mt->table) {
+        aux->mt = shash_find_data(&m->tables, table->schema->name);
+        aux->table_json = NULL;
+        if (!aux->mt) {
+            /* We don't care about rows in this table at all.  Tell the caller
+             * to skip it.  */
+            return false;
+        }
+    }
+
+    type = (aux->initial ? OJMS_INITIAL
+            : !old ? OJMS_INSERT
+            : !new ? OJMS_DELETE
+            : OJMS_MODIFY);
+    if (!(aux->mt->select & type)) {
+        /* We don't care about this type of change (but do want to be called
+         * back for changes to other rows in the same table). */
+        return true;
+    }
+
+    old_json = new_json = NULL;
+    n_changed = 0;
+    for (i = 0; i < aux->mt->columns.n_columns; i++) {
+        const struct ovsdb_column *column = aux->mt->columns.columns[i];
+        unsigned int idx = column->index;
+        bool changed = false;
+
+        if (type == OJMS_MODIFY) {
+            changed = !ovsdb_datum_equals(&old->fields[idx],
+                                          &new->fields[idx], &column->type);
+            n_changed += changed;
+        }
+        if (changed || type == OJMS_DELETE) {
+            if (!old_json) {
+                old_json = json_object_create();
+            }
+            json_object_put(old_json, column->name,
+                            ovsdb_datum_to_json(&old->fields[idx],
+                                                &column->type));
+        }
+        if (type & (OJMS_INITIAL | OJMS_INSERT | OJMS_MODIFY)) {
+            if (!new_json) {
+                new_json = json_object_create();
+            }
+            json_object_put(new_json, column->name,
+                            ovsdb_datum_to_json(&new->fields[idx],
+                                                &column->type));
+        }
+    }
+    if ((type == OJMS_MODIFY && !n_changed) || (!old_json && !new_json)) {
+        /* No reportable changes. */
+        json_destroy(old_json);
+        json_destroy(new_json);
+        return true;
+    }
+
+    /* Create JSON object for transaction overall. */
+    if (!aux->json) {
+        aux->json = json_object_create();
+    }
+
+    /* Create JSON object for transaction on this table. */
+    if (!aux->table_json) {
+        aux->table_json = json_object_create();
+        json_object_put(aux->json, aux->mt->table->schema->name,
+                        aux->table_json);
+    }
+
+    /* Create JSON object for transaction on this row. */
+    row_json = json_object_create();
+    if (old_json) {
+        json_object_put(row_json, "old", old_json);
+    }
+    if (new_json) {
+        json_object_put(row_json, "new", new_json);
+    }
+
+    /* Add JSON row to JSON table. */
+    snprintf(uuid, sizeof uuid,
+             UUID_FMT, UUID_ARGS(ovsdb_row_get_uuid(new ? new : old)));
+    json_object_put(aux->table_json, uuid, row_json);
+
+    return true;
+}
+
+static void
+ovsdb_jsonrpc_monitor_init_aux(struct ovsdb_jsonrpc_monitor_aux *aux,
+                               const struct ovsdb_jsonrpc_monitor *m,
+                               bool initial)
+{
+    aux->initial = initial;
+    aux->monitor = m;
+    aux->json = NULL;
+    aux->mt = NULL;
+    aux->table_json = NULL;
+}
+
+static struct ovsdb_error *
+ovsdb_jsonrpc_monitor_commit(struct ovsdb_replica *replica,
+                             const struct ovsdb_txn *txn, bool durable UNUSED)
+{
+    struct ovsdb_jsonrpc_monitor *m = ovsdb_jsonrpc_monitor_cast(replica);
+    struct ovsdb_jsonrpc_monitor_aux aux;
+
+    ovsdb_jsonrpc_monitor_init_aux(&aux, m, false);
+    ovsdb_txn_for_each_change(txn, ovsdb_jsonrpc_monitor_change_cb, &aux);
+    if (aux.json) {
+        struct jsonrpc_msg *msg;
+        struct json *params;
+
+        params = json_array_create_2(json_clone(aux.monitor->monitor_id),
+                                     aux.json);
+        msg = jsonrpc_create_notify("update", params);
+        jsonrpc_send(aux.monitor->session->rpc, msg);
+    }
+
+    return NULL;
+}
+
+static struct json *
+ovsdb_jsonrpc_monitor_get_initial(const struct ovsdb_jsonrpc_monitor *m)
+{
+    struct ovsdb_jsonrpc_monitor_aux aux;
+    struct shash_node *node;
+
+    ovsdb_jsonrpc_monitor_init_aux(&aux, m, true);
+    SHASH_FOR_EACH (node, &m->tables) {
+        struct ovsdb_jsonrpc_monitor_table *mt = node->data;
+
+        if (mt->select & OJMS_INITIAL) {
+            struct ovsdb_row *row;
+
+            HMAP_FOR_EACH (row, struct ovsdb_row, hmap_node,
+                           &mt->table->rows) {
+                ovsdb_jsonrpc_monitor_change_cb(NULL, row, &aux);
+            }
+        }
+    }
+    return aux.json ? aux.json : json_object_create();
+}
+
+static void
+ovsdb_jsonrpc_monitor_destroy(struct ovsdb_replica *replica)
+{
+    struct ovsdb_jsonrpc_monitor *m = ovsdb_jsonrpc_monitor_cast(replica);
+    struct shash_node *node;
+
+    json_destroy(m->monitor_id);
+    SHASH_FOR_EACH (node, &m->tables) {
+        struct ovsdb_jsonrpc_monitor_table *mt = node->data;
+        ovsdb_column_set_destroy(&mt->columns);
+        free(mt);
+    }
+    shash_destroy(&m->tables);
+    hmap_remove(&m->session->monitors, &m->node);
+    free(m);
+}
+
+static const struct ovsdb_replica_class ovsdb_jsonrpc_replica_class = {
+    ovsdb_jsonrpc_monitor_commit,
+    ovsdb_jsonrpc_monitor_destroy
+};
diff --git a/ovsdb/jsonrpc-server.h b/ovsdb/jsonrpc-server.h
new file mode 100644 (file)
index 0000000..0a80ac6
--- /dev/null
@@ -0,0 +1,31 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_JSONRPC_SERVER_H
+#define OVSDB_JSONRPC_SERVER_H 1
+
+struct ovsdb;
+
+struct ovsdb_jsonrpc_server *ovsdb_jsonrpc_server_create(struct ovsdb *);
+
+int ovsdb_jsonrpc_server_listen(struct ovsdb_jsonrpc_server *,
+                                const char *name);
+void ovsdb_jsonrpc_server_connect(struct ovsdb_jsonrpc_server *,
+                                  const char *name);
+
+void ovsdb_jsonrpc_server_run(struct ovsdb_jsonrpc_server *);
+void ovsdb_jsonrpc_server_wait(struct ovsdb_jsonrpc_server *);
+
+#endif /* ovsdb/jsonrpc-server.h */
diff --git a/ovsdb/log.c b/ovsdb/log.c
new file mode 100644 (file)
index 0000000..9c2e277
--- /dev/null
@@ -0,0 +1,363 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "log.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "json.h"
+#include "lockfile.h"
+#include "ovsdb.h"
+#include "ovsdb-error.h"
+#include "sha1.h"
+#include "transaction.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_ovsdb_log
+#include "vlog.h"
+
+enum ovsdb_log_mode {
+    OVSDB_LOG_READ,
+    OVSDB_LOG_WRITE
+};
+
+struct ovsdb_log {
+    off_t offset;
+    char *name;
+    struct lockfile *lockfile;
+    FILE *stream;
+    struct ovsdb_error *read_error;
+    struct ovsdb_error *write_error;
+    enum ovsdb_log_mode mode;
+};
+
+struct ovsdb_error *
+ovsdb_log_open(const char *name, int flags, struct ovsdb_log **filep)
+{
+    struct lockfile *lockfile;
+    struct ovsdb_error *error;
+    struct ovsdb_log *file;
+    struct stat s;
+    FILE *stream;
+    int accmode;
+    int fd;
+
+    *filep = NULL;
+
+    accmode = flags & O_ACCMODE;
+    if (accmode == O_RDWR || accmode == O_WRONLY) {
+        int retval = lockfile_lock(name, 0, &lockfile);
+        if (retval) {
+            error = ovsdb_io_error(retval, "%s: failed to lock lockfile",
+                                   name);
+            goto error;
+        }
+    } else {
+        lockfile = NULL;
+    }
+
+    fd = open(name, flags, 0666);
+    if (fd < 0) {
+        const char *op = flags & O_CREAT && flags & O_EXCL ? "create" : "open";
+        error = ovsdb_io_error(errno, "%s: %s failed", op, name);
+        goto error_unlock;
+    }
+
+    if (!fstat(fd, &s) && s.st_size == 0) {
+        /* It's (probably) a new file so fsync() its parent directory to ensure
+         * that its directory entry is committed to disk. */
+        char *dir = dir_name(name);
+        int dirfd = open(dir, O_RDONLY);
+        if (dirfd >= 0) {
+            if (fsync(dirfd) && errno != EINVAL) {
+                VLOG_ERR("%s: fsync failed (%s)", dir, strerror(errno));
+            }
+            close(dirfd);
+        } else {
+            VLOG_ERR("%s: open failed (%s)", dir, strerror(errno));
+        }
+        free(dir);
+    }
+
+    stream = fdopen(fd, (accmode == O_RDONLY ? "rb"
+                         : accmode == O_WRONLY ? "wb"
+                         : "w+b"));
+    if (!stream) {
+        error = ovsdb_io_error(errno, "%s: fdopen failed", name);
+        goto error_close;
+    }
+
+    file = xmalloc(sizeof *file);
+    file->name = xstrdup(name);
+    file->lockfile = lockfile;
+    file->stream = stream;
+    file->offset = 0;
+    file->read_error = NULL;
+    file->write_error = NULL;
+    file->mode = OVSDB_LOG_READ;
+    *filep = file;
+    return NULL;
+
+error_close:
+    close(fd);
+error_unlock:
+    lockfile_unlock(lockfile);
+error:
+    return error;
+}
+
+void
+ovsdb_log_close(struct ovsdb_log *file)
+{
+    if (file) {
+        free(file->name);
+        fclose(file->stream);
+        lockfile_unlock(file->lockfile);
+        ovsdb_error_destroy(file->read_error);
+        ovsdb_error_destroy(file->write_error);
+        free(file);
+    }
+}
+
+static const char magic[] = "OVSDB JSON ";
+
+static bool
+parse_header(char *header, unsigned long int *length,
+             uint8_t sha1[SHA1_DIGEST_SIZE])
+{
+    char *p;
+
+    /* 'header' must consist of a magic string... */
+    if (strncmp(header, magic, strlen(magic))) {
+        return false;
+    }
+
+    /* ...followed by a length in bytes... */
+    *length = strtoul(header + strlen(magic), &p, 10);
+    if (!*length || *length == ULONG_MAX || *p != ' ') {
+        return false;
+    }
+    p++;
+
+    /* ...followed by a SHA-1 hash... */
+    if (!sha1_from_hex(sha1, p)) {
+        return false;
+    }
+    p += SHA1_HEX_DIGEST_LEN;
+
+    /* ...and ended by a new-line. */
+    if (*p != '\n') {
+        return false;
+    }
+
+    return true;
+}
+
+struct ovsdb_log_read_cbdata {
+    char input[4096];
+    struct ovsdb_log *file;
+    int error;
+    unsigned long length;
+};
+
+static struct ovsdb_error *
+parse_body(struct ovsdb_log *file, off_t offset, unsigned long int length,
+           uint8_t sha1[SHA1_DIGEST_SIZE], struct json **jsonp)
+{
+    unsigned long int bytes_left;
+    struct json_parser *parser;
+    struct sha1_ctx ctx;
+
+    sha1_init(&ctx);
+    parser = json_parser_create(JSPF_TRAILER);
+
+    bytes_left = length;
+    while (length > 0) {
+        char input[BUFSIZ];
+        int chunk;
+
+        chunk = MIN(length, sizeof input);
+        if (fread(input, 1, chunk, file->stream) != chunk) {
+            json_parser_abort(parser);
+            return ovsdb_io_error(ferror(file->stream) ? errno : EOF,
+                                  "%s: error reading %lu bytes "
+                                  "starting at offset %lld", file->name,
+                                  length, (long long int) offset);
+        }
+        sha1_update(&ctx, input, chunk);
+        json_parser_feed(parser, input, chunk);
+        length -= chunk;
+    }
+
+    sha1_final(&ctx, sha1);
+    *jsonp = json_parser_finish(parser);
+    return NULL;
+}
+
+struct ovsdb_error *
+ovsdb_log_read(struct ovsdb_log *file, struct json **jsonp)
+{
+    uint8_t expected_sha1[SHA1_DIGEST_SIZE];
+    uint8_t actual_sha1[SHA1_DIGEST_SIZE];
+    struct ovsdb_error *error;
+    off_t data_offset;
+    unsigned long data_length;
+    struct json *json;
+    char header[128];
+
+    *jsonp = json = NULL;
+
+    if (file->read_error) {
+        return ovsdb_error_clone(file->read_error);
+    } else if (file->mode == OVSDB_LOG_WRITE) {
+        return OVSDB_BUG("reading file in write mode");
+    }
+
+    if (!fgets(header, sizeof header, file->stream)) {
+        if (feof(file->stream)) {
+            error = NULL;
+        } else {
+            error = ovsdb_io_error(errno, "%s: read failed", file->name);
+        }
+        goto error;
+    }
+
+    if (!parse_header(header, &data_length, expected_sha1)) {
+        error = ovsdb_syntax_error(NULL, NULL, "%s: parse error at offset "
+                                   "%lld in header line \"%.*s\"",
+                                   file->name, (long long int) file->offset,
+                                   (int) strcspn(header, "\n"), header);
+        goto error;
+    }
+
+    data_offset = file->offset + strlen(header);
+    error = parse_body(file, data_offset, data_length, actual_sha1, &json);
+    if (error) {
+        goto error;
+    }
+
+    if (memcmp(expected_sha1, actual_sha1, SHA1_DIGEST_SIZE)) {
+        error = ovsdb_syntax_error(NULL, NULL, "%s: %lu bytes starting at "
+                                   "offset %lld have SHA-1 hash "SHA1_FMT" "
+                                   "but should have hash "SHA1_FMT,
+                                   file->name, data_length,
+                                   (long long int) data_offset,
+                                   SHA1_ARGS(actual_sha1),
+                                   SHA1_ARGS(expected_sha1));
+        goto error;
+    }
+
+    if (json->type == JSON_STRING) {
+        error = ovsdb_syntax_error(NULL, NULL, "%s: %lu bytes starting at "
+                                   "offset %lld are not valid JSON (%s)",
+                                   file->name, data_length,
+                                   (long long int) data_offset,
+                                   json->u.string);
+        goto error;
+    }
+
+    file->offset = data_offset + data_length;
+    *jsonp = json;
+    return 0;
+
+error:
+    file->read_error = ovsdb_error_clone(error);
+    json_destroy(json);
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_log_write(struct ovsdb_log *file, struct json *json)
+{
+    uint8_t sha1[SHA1_DIGEST_SIZE];
+    struct ovsdb_error *error;
+    char *json_string;
+    char header[128];
+    size_t length;
+
+    json_string = NULL;
+
+    if (file->write_error) {
+        return ovsdb_error_clone(file->write_error);
+    } else if (file->mode == OVSDB_LOG_READ) {
+        file->mode = OVSDB_LOG_WRITE;
+        if (fseeko(file->stream, file->offset, SEEK_SET)) {
+            error = ovsdb_io_error(errno, "%s: cannot seek to offset %lld",
+                                   file->name, (long long int) file->offset);
+            goto error;
+        }
+        if (ftruncate(fileno(file->stream), file->offset)) {
+            error = ovsdb_io_error(errno, "%s: cannot truncate to length %lld",
+                                   file->name, (long long int) file->offset);
+            goto error;
+        }
+    }
+
+    if (json->type != JSON_OBJECT && json->type != JSON_ARRAY) {
+        error = OVSDB_BUG("bad JSON type");
+        goto error;
+    }
+
+    /* Compose content.  Add a new-line (replacing the null terminator) to make
+     * the file easier to read, even though it has no semantic value.  */
+    json_string = json_to_string(json, 0);
+    length = strlen(json_string) + 1;
+    json_string[length - 1] = '\n';
+
+    /* Compose header. */
+    sha1_bytes(json_string, length, sha1);
+    snprintf(header, sizeof header, "%s%zu "SHA1_FMT"\n",
+             magic, length, SHA1_ARGS(sha1));
+
+    /* Write. */
+    if (fwrite(header, strlen(header), 1, file->stream) != 1
+        || fwrite(json_string, length, 1, file->stream) != 1
+        || fflush(file->stream))
+    {
+        error = ovsdb_io_error(errno, "%s: write failed", file->name);
+
+        /* Remove any partially written data, ignoring errors since there is
+         * nothing further we can do. */
+        ftruncate(fileno(file->stream), file->offset);
+
+        goto error;
+    }
+
+    file->offset += strlen(header) + length;
+    free(json_string);
+    return 0;
+
+error:
+    file->write_error = ovsdb_error_clone(error);
+    free(json_string);
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_log_commit(struct ovsdb_log *file)
+{
+    if (fsync(fileno(file->stream))) {
+        return ovsdb_io_error(errno, "%s: fsync failed", file->name);
+    }
+    return 0;
+}
+
diff --git a/ovsdb/log.h b/ovsdb/log.h
new file mode 100644 (file)
index 0000000..2daa635
--- /dev/null
@@ -0,0 +1,36 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_LOG_H
+#define OVSDB_LOG_H 1
+
+#include <sys/types.h>
+#include "compiler.h"
+
+struct json;
+struct ovsdb_log;
+
+struct ovsdb_error *ovsdb_log_open(const char *name, int flags,
+                                   struct ovsdb_log **) WARN_UNUSED_RESULT;
+void ovsdb_log_close(struct ovsdb_log *);
+
+struct ovsdb_error *ovsdb_log_read(struct ovsdb_log *, struct json **)
+    WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_log_write(struct ovsdb_log *, struct json *)
+    WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_log_commit(struct ovsdb_log *)
+    WARN_UNUSED_RESULT;
+
+#endif /* ovsdb/log.h */
diff --git a/ovsdb/ovsdb-client.1.in b/ovsdb/ovsdb-client.1.in
new file mode 100644 (file)
index 0000000..9825d32
--- /dev/null
@@ -0,0 +1,126 @@
+.\" -*- nroff -*-
+.de IQ
+.  br
+.  ns
+.  IP "\\$1"
+..
+.\" -*- nroff -*-
+.TH ovsdb\-client 1 "November 2009" "Open vSwitch" "Open vSwitch Manual"
+.ds PN ovsdb\-client
+.
+.SH NAME
+ovsdb\-client \- command-line interface to \fBovsdb-server\fR(1)
+.
+.SH SYNOPSIS
+\fBovsdb\-client \fR[\fIoptions\fR] \fBget-schema\fI server\fR
+.br
+\fBovsdb\-client \fR[\fIoptions\fR] \fBlist-tables\fI server\fR
+.br
+\fBovsdb\-client \fR[\fIoptions\fR] \fBlist-columns\fI server \fR[\fItable\fR]
+.br
+\fBovsdb\-client \fR[\fIoptions\fR] \fBtransact\fI server transaction\fR
+.br
+\fBovsdb\-client \fR[\fIoptions\fR] \fBmonitor\fI server table\fR
+[\fIcolumn\fR[\fB,\fIcolumn\fR]...]
+[\fIselect\fR[\fB,\fIselect\fR]...]
+.br
+\fBovsdb\-client help\fR
+.IP "Output formatting options:"
+[\fB--format=\fIformat\fR]
+[\fB--wide\fR]
+[\fB--no-heading\fR]
+.so lib/vlog-syn.man
+.so lib/common-syn.man
+.
+.SH DESCRIPTION
+The \fBovsdb\-client\fR program is a command-line client for
+interacting with a running \fBovsdb\-server\fR process.  For each
+command, the \fIserver\fR to connect to must be specified in one of
+the following forms:
+.IP "\fBtcp:\fIip\fB:\fIport\fR"
+Connect to the given TCP \fIport\fR on \fIip\fR.
+.IP "\fBunix:\fIfile\fR"
+Connect to the Unix domain server socket named \fIfile\fR.
+.IP "\fBptcp:\fIport\fR[\fB:\fIip\fR]"
+Listen on the given TCP \fIport\fR for a connection.  By default,
+\fB\*(PN\fR listens for connections to any local IP address, but
+\fIip\fR may be specified to listen only for connections to the given
+\fIip\fR.
+.IP "\fBpunix:\fIfile\fR"
+Listen on the Unix domain server socket named \fIfile\fR for a
+connection.
+.
+.SS "Commands"
+The following commands are implemented:
+.IP "\fBget-schema\fI server\fR"
+Connects to \fIserver\fR, retrieves the database schema, and prints it
+in JSON format.
+.
+.IP "\fBlist-tables\fI server\fR"
+Connects to \fIserver\fR, retrieves the database schema, and prints
+a table listing the names and comments (if any) on each table within
+the database.
+.
+.IP "\fBlist-columns\fI server \fR[\fItable\fR]"
+Connects to \fIserver\fR, retrieves the database schema, and prints
+a table listing the names, type, and comment (if any) on each column.  If
+\fItable\fR is specified, only columns in that table are listed;
+otherwise, the tables include columns in all tables.
+.
+.IP "\fBtransact\fI server transaction\fR"
+Connects to \fIserver\fR, sends it the specified \fItransaction\fR,
+which must be a JSON array containing one or more valid OVSDB
+operations, and prints the received reply on stdout.
+.
+.IP "\fBmonitor\fI server table\fR [\fIcolumn\fR[\fB,\fIcolumn\fR]...] [\fIselect\fR[\fB,\fIselect\fR]...]"
+Connects to \fIserver\fR and monitors the contents of \fItable\fR.  By
+default, the initial contents of \fItable\fR are printed, followed by
+each change as it occurs.  If at least one \fIcolumn\fR is specified,
+only those columns are monitored.  If at least one \fIselect\fR is
+specified, they are interpreted as follows:
+.RS
+.IP "\fBinitial\fR"
+Print the initial contents of the specified columns.
+.IP "\fBinsert\fR"
+Print newly inserted rows.
+.IP "\fBdelete\fR"
+Print deleted rows.
+.IP "\fBmodify\fR"
+Print old and new values of modified rows.
+.RE
+.SH OPTIONS
+.SS "Output Formatting Options"
+Much of the output from \fBovsdb\-client\fR is in the form of tables.
+The following options controlling output formatting:
+.
+.IP "\fB-f \fIformat\fR"
+.IQ "\fB--format=\fIformat\fR"
+Sets the basic type of output formatting.  The following types of
+\fIformat\fR are available:
+.RS
+.IP "\fBtable\fR (default)"
+Text-based tables with aligned columns.
+.IP "\fBhtml\fR"
+HTML tables.
+.IP "\fBcvs\fR"
+Comma-separated values as defined in RFC 4180.
+.RE
+.
+.IP "\fB--wide\fR"
+In \fBtable\fR output (the default), when standard output is a
+terminal device, by default lines are truncated at a width of 79
+characters.  Specifying this option prevents line truncation.
+.
+.IP "\fB--no-heading\fR"
+This option suppresses the heading row that otherwise appears in the
+first row of table output.
+.
+.SS "Logging Options"
+.so lib/vlog.man
+.SS "Other Options"
+.so lib/common.man
+.SH "SEE ALSO"
+.
+\fBovsdb\-server\fR(1),
+\fBovsdb\-client\fR(1),
+and the OVSDB specification.
diff --git a/ovsdb/ovsdb-client.c b/ovsdb/ovsdb-client.c
new file mode 100644 (file)
index 0000000..65d6d81
--- /dev/null
@@ -0,0 +1,844 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "command-line.h"
+#include "column.h"
+#include "compiler.h"
+#include "dynamic-string.h"
+#include "json.h"
+#include "jsonrpc.h"
+#include "ovsdb.h"
+#include "ovsdb-error.h"
+#include "stream.h"
+#include "table.h"
+#include "timeval.h"
+#include "util.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_ovsdb_client
+
+/* --format: Output formatting. */
+static enum {
+    FMT_TABLE,                  /* Textual table. */
+    FMT_HTML,                   /* HTML table. */
+    FMT_CSV                     /* Comma-separated lines. */
+} output_format;
+
+/* --wide: For --format=table, the maximum output width. */
+static int output_width;
+
+/* --no-headings: Whether table output should include headings. */
+static int output_headings = true;
+
+static const struct command all_commands[];
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+    set_program_name(argv[0]);
+    time_init();
+    vlog_init();
+    parse_options(argc, argv);
+    signal(SIGPIPE, SIG_IGN);
+    run_command(argc - optind, argv + optind, all_commands);
+    return 0;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+    static struct option long_options[] = {
+        {"wide", no_argument, &output_width, INT_MAX},
+        {"format", required_argument, 0, 'f'},
+           {"no-headings", no_argument, &output_headings, 0},
+        {"verbose", optional_argument, 0, 'v'},
+        {"help", no_argument, 0, 'h'},
+        {"version", no_argument, 0, 'V'},
+        {0, 0, 0, 0},
+    };
+    char *short_options = long_options_to_short_options(long_options);
+
+    output_width = isatty(fileno(stdout)) ? 79 : INT_MAX;
+    for (;;) {
+        int c;
+
+        c = getopt_long(argc, argv, short_options, long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case 'f':
+            if (!strcmp(optarg, "table")) {
+                output_format = FMT_TABLE;
+            } else if (!strcmp(optarg, "html")) {
+                output_format = FMT_HTML;
+            } else if (!strcmp(optarg, "csv")) {
+                output_format = FMT_CSV;
+            } else {
+                ovs_fatal(0, "unknown output format \"%s\"", optarg);
+            }
+            break;
+
+        case 'w':
+            output_width = INT_MAX;
+            break;
+
+        case 'h':
+            usage();
+
+        case 'V':
+            OVS_PRINT_VERSION(0, 0);
+            exit(EXIT_SUCCESS);
+
+        case 'v':
+            vlog_set_verbosity(optarg);
+            break;
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        case 0:
+            /* getopt_long() already set the value for us. */
+            break;
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+}
+
+static void
+usage(void)
+{
+    printf("%s: Open vSwitch database JSON-RPC client\n"
+           "usage: %s [OPTIONS] COMMAND [ARG...]\n"
+           "\nValid commands are:\n"
+           "\n  get-schema SERVER\n"
+           "    retrieve schema from SERVER\n"
+           "\n  list-tables SERVER\n"
+           "    list SERVER's tables\n"
+           "\n  list-columns SERVER [TABLE]\n"
+           "    list columns in TABLE (or all tables) on SERVER\n"
+           "\n  transact SERVER TRANSACTION\n"
+           "    run TRANSACTION (a JSON array of operations) on SERVER\n"
+           "    and print the results as JSON on stdout\n"
+           "\n  monitor SERVER TABLE [COLUMN,...] [SELECT,...]\n"
+           "    monitor contents of (COLUMNs in) TABLE on SERVER\n"
+           "    Valid SELECTs are: initial, insert, delete, modify\n",
+           program_name, program_name);
+    stream_usage("SERVER", true, true);
+    printf("\nOutput formatting options:\n"
+           "  -f, --format=FORMAT         set output formatting to FORMAT\n"
+           "                              (\"table\", \"html\", or \"csv\"\n"
+           "  --wide                      don't limit TTY lines to 79 bytes\n"
+           "  --no-headings               omit table heading row\n");
+    vlog_usage();
+    printf("\nOther options:\n"
+           "  -h, --help                  display this help message\n"
+           "  -V, --version               display version information\n");
+    exit(EXIT_SUCCESS);
+}
+\f
+static struct json *
+parse_json(const char *s)
+{
+    struct json *json = json_from_string(s);
+    if (json->type == JSON_STRING) {
+        ovs_fatal(0, "\"%s\": %s", s, json->u.string);
+    }
+    return json;
+}
+
+static struct jsonrpc *
+open_jsonrpc(const char *server)
+{
+    struct stream *stream;
+    int error;
+
+    error = stream_open_block(server, &stream);
+    if (error == EAFNOSUPPORT) {
+        struct pstream *pstream;
+
+        error = pstream_open(server, &pstream);
+        if (error) {
+            ovs_fatal(error, "failed to connect or listen to \"%s\"", server);
+        }
+
+        VLOG_INFO("%s: waiting for connection...", server);
+        error = pstream_accept_block(pstream, &stream);
+        if (error) {
+            ovs_fatal(error, "failed to accept connection on \"%s\"", server);
+        }
+
+        pstream_close(pstream);
+    } else if (error) {
+        ovs_fatal(error, "failed to connect to \"%s\"", server);
+    }
+
+    return jsonrpc_open(stream);
+}
+
+static void
+print_json(struct json *json)
+{
+    char *string = json_to_string(json, JSSF_SORT);
+    fputs(string, stdout);
+    free(string);
+}
+
+static void
+print_and_free_json(struct json *json)
+{
+    print_json(json);
+    json_destroy(json);
+}
+
+static void
+check_ovsdb_error(struct ovsdb_error *error)
+{
+    if (error) {
+        ovs_fatal(0, "%s", ovsdb_error_to_string(error));
+    }
+}
+
+static struct ovsdb_schema *
+fetch_schema_from_rpc(struct jsonrpc *rpc)
+{
+    struct jsonrpc_msg *request, *reply;
+    struct ovsdb_schema *schema;
+    int error;
+
+    request = jsonrpc_create_request("get_schema", json_array_create_empty(),
+                                     NULL);
+    error = jsonrpc_transact_block(rpc, request, &reply);
+    if (error) {
+        ovs_fatal(error, "transaction failed");
+    }
+    check_ovsdb_error(ovsdb_schema_from_json(reply->result, &schema));
+    jsonrpc_msg_destroy(reply);
+
+    return schema;
+}
+
+static struct ovsdb_schema *
+fetch_schema(const char *server)
+{
+    struct ovsdb_schema *schema;
+    struct jsonrpc *rpc;
+
+    rpc = open_jsonrpc(server);
+    schema = fetch_schema_from_rpc(rpc);
+    jsonrpc_close(rpc);
+
+    return schema;
+}
+\f
+struct column {
+    char *heading;
+    int width;
+};
+
+struct table {
+    char **cells;
+    struct column *columns;
+    size_t n_columns, allocated_columns;
+    size_t n_rows, allocated_rows;
+    size_t current_column;
+};
+
+static void
+table_init(struct table *table)
+{
+    memset(table, 0, sizeof *table);
+}
+
+static void
+table_destroy(struct table *table)
+{
+    size_t i;
+
+    for (i = 0; i < table->n_columns; i++) {
+        free(table->columns[i].heading);
+    }
+    free(table->columns);
+
+    for (i = 0; i < table->n_columns * table->n_rows; i++) {
+        free(table->cells[i]);
+    }
+    free(table->cells);
+}
+
+static void
+table_add_column(struct table *table, const char *heading, ...)
+    PRINTF_FORMAT(2, 3);
+
+static void
+table_add_column(struct table *table, const char *heading, ...)
+{
+    struct column *column;
+    va_list args;
+
+    assert(!table->n_rows);
+    if (table->n_columns >= table->allocated_columns) {
+        table->columns = x2nrealloc(table->columns, &table->allocated_columns,
+                                    sizeof *table->columns);
+    }
+    column = &table->columns[table->n_columns++];
+
+    va_start(args, heading);
+    column->heading = xvasprintf(heading, args);
+    column->width = strlen(column->heading);
+    va_end(args);
+}
+
+static char **
+table_cell__(const struct table *table, size_t row, size_t column)
+{
+    return &table->cells[column + row * table->n_columns];
+}
+
+static void
+table_add_row(struct table *table)
+{
+    size_t x, y;
+
+    if (table->n_rows >= table->allocated_rows) {
+        table->cells = x2nrealloc(table->cells, &table->allocated_rows,
+                                  table->n_columns * sizeof *table->cells);
+    }
+
+    y = table->n_rows++;
+    table->current_column = 0;
+    for (x = 0; x < table->n_columns; x++) {
+        *table_cell__(table, y, x) = NULL;
+    }
+}
+
+static void
+table_add_cell_nocopy(struct table *table, char *s)
+{
+    size_t x, y;
+    int length;
+
+    assert(table->n_rows > 0);
+    assert(table->current_column < table->n_columns);
+
+    x = table->current_column++;
+    y = table->n_rows - 1;
+    *table_cell__(table, y, x) = s;
+
+    length = strlen(s);
+    if (length > table->columns[x].width) {
+        table->columns[x].width = length;
+    }
+}
+
+static void
+table_add_cell(struct table *table, const char *format, ...)
+{
+    va_list args;
+
+    va_start(args, format);
+    table_add_cell_nocopy(table, xvasprintf(format, args));
+    va_end(args);
+}
+
+static void
+table_print_table_line__(struct ds *line, size_t max_width)
+{
+    ds_truncate(line, max_width);
+    puts(ds_cstr(line));
+    ds_clear(line);
+}
+
+static void
+table_print_table__(const struct table *table)
+{
+    struct ds line = DS_EMPTY_INITIALIZER;
+    size_t x, y;
+
+    if (output_headings) {
+        for (x = 0; x < table->n_columns; x++) {
+            const struct column *column = &table->columns[x];
+            if (x) {
+                ds_put_char(&line, ' ');
+            }
+            ds_put_format(&line, "%-*s", column->width, column->heading);
+        }
+        table_print_table_line__(&line, output_width);
+
+        for (x = 0; x < table->n_columns; x++) {
+            const struct column *column = &table->columns[x];
+            int i;
+
+            if (x) {
+                ds_put_char(&line, ' ');
+            }
+            for (i = 0; i < column->width; i++) {
+                ds_put_char(&line, '-');
+            }
+        }
+        table_print_table_line__(&line, output_width);
+    }
+
+    for (y = 0; y < table->n_rows; y++) {
+        for (x = 0; x < table->n_columns; x++) {
+            const char *cell = *table_cell__(table, y, x);
+            if (x) {
+                ds_put_char(&line, ' ');
+            }
+            ds_put_format(&line, "%-*s", table->columns[x].width, cell);
+        }
+        table_print_table_line__(&line, output_width);
+    }
+
+    ds_destroy(&line);
+}
+
+static void
+table_print_html_cell__(const char *element, const char *content)
+{
+    const char *p;
+
+    printf("    <%s>", element);
+    for (p = content; *p != '\0'; p++) {
+        switch (*p) {
+        case '&':
+            fputs("&amp;", stdout);
+            break;
+        case '<':
+            fputs("&lt;", stdout);
+            break;
+        case '>':
+            fputs("&gt;", stdout);
+            break;
+        default:
+            putchar(*p);
+            break;
+        }
+    }
+    printf("</%s>\n", element);
+}
+
+static void
+table_print_html__(const struct table *table)
+{
+    size_t x, y;
+
+    fputs("<table>\n", stdout);
+
+    if (output_headings) {
+        fputs("  <tr>\n", stdout);
+        for (x = 0; x < table->n_columns; x++) {
+            const struct column *column = &table->columns[x];
+            table_print_html_cell__("th", column->heading);
+        }
+        fputs("  </tr>\n", stdout);
+    }
+
+    for (y = 0; y < table->n_rows; y++) {
+        fputs("  <tr>\n", stdout);
+        for (x = 0; x < table->n_columns; x++) {
+            table_print_html_cell__("td", *table_cell__(table, y, x));
+        }
+        fputs("  </tr>\n", stdout);
+    }
+
+    fputs("</table>\n", stdout);
+}
+
+static void
+table_print_csv_cell__(const char *content)
+{
+    const char *p;
+
+    if (!strpbrk(content, "\n\",")) {
+        fputs(content, stdout);
+    } else {
+        putchar('"');
+        for (p = content; *p != '\0'; p++) {
+            switch (*p) {
+            case '"':
+                fputs("\"\"", stdout);
+                break;
+            default:
+                putchar(*p);
+                break;
+            }
+        }
+        putchar('"');
+    }
+}
+
+static void
+table_print_csv__(const struct table *table)
+{
+    size_t x, y;
+
+    if (output_headings) {
+        for (x = 0; x < table->n_columns; x++) {
+            const struct column *column = &table->columns[x];
+            if (x) {
+                putchar(',');
+            }
+            table_print_csv_cell__(column->heading);
+        }
+        putchar('\n');
+    }
+
+    for (y = 0; y < table->n_rows; y++) {
+        for (x = 0; x < table->n_columns; x++) {
+            if (x) {
+                putchar(',');
+            }
+            table_print_csv_cell__(*table_cell__(table, y, x));
+        }
+        putchar('\n');
+    }
+}
+
+static void
+table_print(const struct table *table)
+{
+    switch (output_format) {
+    case FMT_TABLE:
+        table_print_table__(table);
+        break;
+
+    case FMT_HTML:
+        table_print_html__(table);
+        break;
+
+    case FMT_CSV:
+        table_print_csv__(table);
+        break;
+    }
+}
+\f
+static void
+do_get_schema(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_schema *schema = fetch_schema(argv[1]);
+    print_and_free_json(ovsdb_schema_to_json(schema));
+    ovsdb_schema_destroy(schema);
+}
+
+static void
+do_list_tables(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_schema *schema;
+    struct shash_node *node;
+    struct table t;
+
+    schema = fetch_schema(argv[1]);
+    table_init(&t);
+    table_add_column(&t, "Table");
+    table_add_column(&t, "Comment");
+    SHASH_FOR_EACH (node, &schema->tables) {
+        struct ovsdb_table_schema *ts = node->data;
+
+        table_add_row(&t);
+        table_add_cell(&t, ts->name);
+        if (ts->comment) {
+            table_add_cell(&t, ts->comment);
+        }
+    }
+    ovsdb_schema_destroy(schema);
+    table_print(&t);
+}
+
+static void
+do_list_columns(int argc UNUSED, char *argv[])
+{
+    const char *table_name = argv[2];
+    struct ovsdb_schema *schema;
+    struct shash_node *table_node;
+    struct table t;
+
+    schema = fetch_schema(argv[1]);
+    table_init(&t);
+    if (!table_name) {
+        table_add_column(&t, "Table");
+    }
+    table_add_column(&t, "Column");
+    table_add_column(&t, "Type");
+    table_add_column(&t, "Comment");
+    SHASH_FOR_EACH (table_node, &schema->tables) {
+        struct ovsdb_table_schema *ts = table_node->data;
+
+        if (!table_name || !strcmp(table_name, ts->name)) {
+            struct shash_node *column_node;
+
+            SHASH_FOR_EACH (column_node, &ts->columns) {
+                struct ovsdb_column *column = column_node->data;
+                struct json *type = ovsdb_type_to_json(&column->type);
+
+                table_add_row(&t);
+                if (!table_name) {
+                    table_add_cell(&t, ts->name);
+                }
+                table_add_cell(&t, column->name);
+                table_add_cell_nocopy(&t, json_to_string(type, JSSF_SORT));
+                if (column->comment) {
+                    table_add_cell(&t, column->comment);
+                }
+
+                json_destroy(type);
+            }
+        }
+    }
+    ovsdb_schema_destroy(schema);
+    table_print(&t);
+}
+
+static void
+do_transact(int argc UNUSED, char *argv[])
+{
+    struct jsonrpc_msg *request, *reply;
+    struct json *transaction;
+    struct jsonrpc *rpc;
+    int error;
+
+    transaction = parse_json(argv[2]);
+
+    rpc = open_jsonrpc(argv[1]);
+    request = jsonrpc_create_request("transact", transaction, NULL);
+    error = jsonrpc_transact_block(rpc, request, &reply);
+    if (error) {
+        ovs_fatal(error, "transaction failed");
+    }
+    if (reply->error) {
+        ovs_fatal(error, "transaction returned error: %s",
+                  json_to_string(reply->error, JSSF_SORT));
+    }
+    print_json(reply->result);
+    putchar('\n');
+    jsonrpc_msg_destroy(reply);
+    jsonrpc_close(rpc);
+}
+
+static void
+monitor_print_row(struct json *row, const char *type, const char *uuid,
+                  const struct ovsdb_column_set *columns, struct table *t)
+{
+    size_t i;
+
+    if (!row) {
+        ovs_error(0, "missing %s row", type);
+        return;
+    } else if (row->type != JSON_OBJECT) {
+        ovs_error(0, "<row> is not object");
+        return;
+    }
+
+    table_add_row(t);
+    table_add_cell(t, uuid);
+    table_add_cell(t, type);
+    for (i = 0; i < columns->n_columns; i++) {
+        const struct ovsdb_column *column = columns->columns[i];
+        struct json *value = shash_find_data(json_object(row), column->name);
+        if (value) {
+            table_add_cell_nocopy(t, json_to_string(value, JSSF_SORT));
+        } else {
+            table_add_cell(t, "");
+        }
+    }
+}
+
+static void
+monitor_print(struct json *table_updates,
+              const struct ovsdb_table_schema *table,
+              const struct ovsdb_column_set *columns, bool initial)
+{
+    struct json *table_update;
+    struct shash_node *node;
+    struct table t;
+    size_t i;
+
+    table_init(&t);
+
+    if (table_updates->type != JSON_OBJECT) {
+        ovs_error(0, "<table-updates> is not object");
+        return;
+    }
+    table_update = shash_find_data(json_object(table_updates), table->name);
+    if (!table_update) {
+        return;
+    }
+    if (table_update->type != JSON_OBJECT) {
+        ovs_error(0, "<table-update> is not object");
+        return;
+    }
+
+    table_add_column(&t, "row");
+    table_add_column(&t, "action");
+    for (i = 0; i < columns->n_columns; i++) {
+        table_add_column(&t, "%s", columns->columns[i]->name);
+    }
+    SHASH_FOR_EACH (node, json_object(table_update)) {
+        struct json *row_update = node->data;
+        struct json *old, *new;
+
+        if (row_update->type != JSON_OBJECT) {
+            ovs_error(0, "<row-update> is not object");
+            continue;
+        }
+        old = shash_find_data(json_object(row_update), "old");
+        new = shash_find_data(json_object(row_update), "new");
+        if (initial) {
+            monitor_print_row(new, "initial", node->name, columns, &t);
+        } else if (!old) {
+            monitor_print_row(new, "insert", node->name, columns, &t);
+        } else if (!new) {
+            monitor_print_row(old, "delete", node->name, columns, &t);
+        } else {
+            monitor_print_row(old, "old", node->name, columns, &t);
+            monitor_print_row(new, "new", "", columns, &t);
+        }
+    }
+    table_print(&t);
+    table_destroy(&t);
+}
+
+static void
+do_monitor(int argc, char *argv[])
+{
+    struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
+    struct ovsdb_table_schema *table;
+    struct ovsdb_schema *schema;
+    struct jsonrpc_msg *request;
+    struct jsonrpc *rpc;
+    struct json *select, *monitor, *monitor_request, *monitor_requests,
+        *request_id;
+
+    rpc = open_jsonrpc(argv[1]);
+
+    schema = fetch_schema_from_rpc(rpc);
+    table = shash_find_data(&schema->tables, argv[2]);
+    if (!table) {
+        ovs_fatal(0, "%s: no table named \"%s\"", argv[1], argv[2]);
+    }
+
+    if (argc >= 4 && *argv[3] != '\0') {
+        char *save_ptr = NULL;
+        char *token;
+
+        for (token = strtok_r(argv[3], ",", &save_ptr); token != NULL;
+             token = strtok_r(NULL, ",", &save_ptr)) {
+            const struct ovsdb_column *column;
+            column = ovsdb_table_schema_get_column(table, token);
+            if (!column) {
+                ovs_fatal(0, "%s: table \"%s\" does not have a "
+                          "column named \"%s\"", argv[1], argv[2], token);
+            }
+            ovsdb_column_set_add(&columns, column);
+        }
+    } else {
+        struct shash_node *node;
+
+        SHASH_FOR_EACH (node, &table->columns) {
+            const struct ovsdb_column *column = node->data;
+            if (column->index != OVSDB_COL_UUID) {
+                ovsdb_column_set_add(&columns, column);
+            }
+        }
+    }
+
+    if (argc >= 5 && *argv[4] != '\0') {
+        char *save_ptr = NULL;
+        char *token;
+
+        select = json_object_create();
+        for (token = strtok_r(argv[4], ",", &save_ptr); token != NULL;
+             token = strtok_r(NULL, ",", &save_ptr)) {
+            json_object_put(select, token, json_boolean_create(true));
+        }
+    } else {
+        select = NULL;
+    }
+
+    monitor_request = json_object_create();
+    json_object_put(monitor_request,
+                    "columns", ovsdb_column_set_to_json(&columns));
+    if (select) {
+        json_object_put(monitor_request, "select", select);
+    }
+
+    monitor_requests = json_object_create();
+    json_object_put(monitor_requests, argv[2], monitor_request);
+
+    monitor = json_array_create_2(json_null_create(), monitor_requests);
+    request = jsonrpc_create_request("monitor", monitor, NULL);
+    request_id = json_clone(request->id);
+    jsonrpc_send(rpc, request);
+    for (;;) {
+        struct jsonrpc_msg *msg;
+        int error;
+
+        error = jsonrpc_recv_block(rpc, &msg);
+        if (error) {
+            ovs_fatal(error, "%s: receive failed", argv[1]);
+        }
+
+        if (msg->type == JSONRPC_REQUEST && !strcmp(msg->method, "echo")) {
+            jsonrpc_send(rpc, jsonrpc_create_reply(json_clone(msg->params),
+                                                   msg->id));
+        } else if (msg->type == JSONRPC_REPLY
+                   && json_equal(msg->id, request_id)) {
+            monitor_print(msg->result, table, &columns, true);
+        } else if (msg->type == JSONRPC_NOTIFY
+                   && !strcmp(msg->method, "update")) {
+            struct json *params = msg->params;
+            if (params->type == JSON_ARRAY
+                && params->u.array.n == 2
+                && params->u.array.elems[0]->type == JSON_NULL) {
+                monitor_print(params->u.array.elems[1],
+                              table, &columns, false);
+            }
+        }
+    }
+}
+
+static void
+do_help(int argc UNUSED, char *argv[] UNUSED)
+{
+    usage();
+}
+
+static const struct command all_commands[] = {
+    { "get-schema", 1, 1, do_get_schema },
+    { "list-tables", 1, 1, do_list_tables },
+    { "list-columns", 1, 2, do_list_columns },
+    { "transact", 2, 2, do_transact },
+    { "monitor", 2, 4, do_monitor },
+    { "help", 0, INT_MAX, do_help },
+    { NULL, 0, 0, NULL },
+};
diff --git a/ovsdb/ovsdb-idlc.1 b/ovsdb/ovsdb-idlc.1
new file mode 100644 (file)
index 0000000..1311759
--- /dev/null
@@ -0,0 +1,85 @@
+.\" -*- nroff -*-
+.TH ovsdb\-idlc 1 "November 2009" "Open vSwitch" "Open vSwitch Manual"
+.ds PN ovsdb\-idlc
+.
+.SH NAME
+ovsdb\-idlc \- Open vSwitch IDL (Interface Definition Language) compiler
+.
+.SH SYNOPSIS
+\fBovsdb\-idlc \fBvalidate\fI schema\fR
+.br
+\fBovsdb\-idlc \fBovsdb\-schema\fI schema\fR
+.br
+\fBovsdb\-idlc \fBc\-idl\-header\fI schema\fR
+.br
+\fBovsdb\-idlc \fBc\-idl\-source\fI schema\fR
+.br
+\fBovsdb\-idlc --help\fR
+.br
+\fBovsdb\-idlc --version\fR
+.
+.SH DESCRIPTION
+The \fBovsdb\-idlc\fR program is a command-line tool for translating
+Open vSwitch database interface definition language (IDL) schemas into
+other formats.  It is used while building Open vSwitch, not at
+installation or configuration time.  Thus, it is not normally
+installed as part of Open vSwitch.
+.
+.PP
+The \fIschema\fR files used as \fBovsdb\-idlc\fR input have the same
+format as the OVSDB schemas, specified in the OVSDB specification,
+with a few additions:
+.
+.IP "\fB//\fR comments"
+Lines that begin with \fB//\fR (two forward slashes) are ignored and
+thus can be used for comments.
+.
+.IP "\fB""\fBidlPrefix\fR"" member of <database-schema>"
+This member, which is required, specifies a string that is prefixed to
+top-level names in C bindings.  It should probably end in an
+underscore.
+.
+.IP "\fB""\fBidlHeader\fR"" member of <database-schema>"
+This member, which is required, specifies the name of the IDL header.
+It will be output on an \fB#include\fR line in the source file
+generated by the C bindings.  It should include the bracketing
+\fB""\fR or \fB<>\fR.
+.
+.IP "\fB""\fBkeyRefTable\fR"" member of <type>"
+A <type> whose \fBkey\fR is \fB"uuid"\fR may have an additional member
+named \fB"keyRefTable"\fR, whose value is a table name.  This
+expresses the constraint that keys of the given <type> are UUIDs that
+reference rows in the named table.  This allows the IDL to supply a
+structure pointer in place of a raw UUID in its marshalled version of
+the given type.
+.
+.IP "\fB""valueRefTable""\fR member of <type>"
+Analogous to \fB"keyRefTable"\fR in meaning and effect, except that it
+applies to the \fB"value"\fR member of the <type>.
+.SS "Commands"
+.
+.IP "\fBvalidate\fI schema\fR"
+Reads \fIschema\fR and checks its format, without producing any output.
+.
+.IP "\fBovsdb\-schema\fI schema\fR"
+Reads \fIschema\fR and prints it on standard output with the parts
+that are not part of the OVSDB schema specification stripped out.
+.
+.IP "\fBc\-idl\-header\fI schema\fR"
+Reads \fIschema\fR and prints on standard output a C header file that
+defines a structure for each table defined by the schema.
+.
+.IP "\fBc\-idl\-source\fI schema\fR"
+Reads \fIschema\fR and prints on standard output a C source file that
+implements C bindings for the database defined by the schema.
+.
+.SS "Options"
+.so lib/common.man
+.
+.SH "BUGS"
+\fBovsdb\-idlc\fR is more lenient about the format of OVSDB schemas
+than other OVSDB tools, so the \fBovsdb\-schema\fR command may output
+schemas that other programs refuse to read.
+.
+.SH "SEE ALSO"
+The OVSDB specification.
diff --git a/ovsdb/ovsdb-idlc.in b/ovsdb/ovsdb-idlc.in
new file mode 100755 (executable)
index 0000000..68cb4ce
--- /dev/null
@@ -0,0 +1,485 @@
+#! @PYTHON@
+
+import getopt
+import re
+import sys
+
+sys.path.insert(0, "@abs_top_srcdir@/ovsdb")
+import simplejson as json
+
+argv0 = sys.argv[0]
+
+class Error(Exception):
+    def __init__(self, msg):
+        Exception.__init__(self)
+        self.msg = msg
+
+def getMember(json, name, validTypes, description, default=None):
+    if name in json:
+        member = json[name]
+        if type(member) not in validTypes:
+            raise Error("%s: type mismatch for '%s' member"
+                        % (description, name))
+        return member
+    return default
+
+def mustGetMember(json, name, expectedType, description):
+    member = getMember(json, name, expectedType, description)
+    if member == None:
+        raise Error("%s: missing '%s' member" % (description, name))
+    return member
+
+class DbSchema:
+    def __init__(self, name, comment, tables, idlPrefix, idlHeader):
+        self.name = name
+        self.comment = comment
+        self.tables = tables
+        self.idlPrefix = idlPrefix
+        self.idlHeader = idlHeader
+
+    @staticmethod
+    def fromJson(json):
+        name = mustGetMember(json, 'name', [unicode], 'database')
+        comment = getMember(json, 'comment', [unicode], 'database')
+        tablesJson = mustGetMember(json, 'tables', [dict], 'database')
+        tables = {}
+        for name, tableJson in tablesJson.iteritems():
+            tables[name] = TableSchema.fromJson(tableJson, "%s table" % name)
+        idlPrefix = mustGetMember(json, 'idlPrefix', [unicode], 'database')
+        idlHeader = mustGetMember(json, 'idlHeader', [unicode], 'database')
+        return DbSchema(name, comment, tables, idlPrefix, idlHeader)
+
+    def toJson(self):
+        d = {"name": self.name,
+             "tables": {}}
+        for name, table in self.tables.iteritems():
+            d["tables"][name] = table.toJson()
+        if self.comment != None:
+            d["comment"] = self.comment
+        return d
+
+class TableSchema:
+    def __init__(self, comment, columns):
+        self.comment = comment
+        self.columns = columns
+
+    @staticmethod
+    def fromJson(json, description):
+        comment = getMember(json, 'comment', [unicode], description)
+        columnsJson = mustGetMember(json, 'columns', [dict], description)
+        columns = {}
+        for name, json in columnsJson.iteritems():
+            columns[name] = ColumnSchema.fromJson(
+                json, "column %s in %s" % (name, description))
+        return TableSchema(comment, columns)
+
+    def toJson(self):
+        d = {"columns": {}}
+        for name, column in self.columns.iteritems():
+            d["columns"][name] = column.toJson()
+        if self.comment != None:
+            d["comment"] = self.comment
+        return d
+
+class ColumnSchema:
+    def __init__(self, comment, type, persistent):
+        self.comment = comment
+        self.type = type
+        self.persistent = persistent
+
+    @staticmethod
+    def fromJson(json, description):
+        comment = getMember(json, 'comment', [unicode], description)
+        type = Type.fromJson(mustGetMember(json, 'type', [dict, unicode],
+                                           description),
+                             'type of %s' % description)
+        ephemeral = getMember(json, 'ephemeral', [True,False], description)
+        persistent = ephemeral != True
+        return ColumnSchema(comment, type, persistent)
+
+    def toJson(self):
+        d = {"type": self.type.toJson()}
+        if self.persistent == False:
+            d["ephemeral"] = True
+        if self.comment != None:
+            d["comment"] = self.comment
+        return d
+
+class Type:
+    def __init__(self, key, keyRefTable=None, value=None, valueRefTable=None,
+                 min=1, max=1):
+        self.key = key
+        self.keyRefTable = keyRefTable
+        self.value = value
+        self.valueRefTable = valueRefTable
+        self.min = min
+        self.max = max
+    
+    @staticmethod
+    def fromJson(json, description):
+        if type(json) == unicode:
+            return Type(json)
+        else:
+            key = mustGetMember(json, 'key', [unicode], description)
+            keyRefTable = getMember(json, 'keyRefTable', [unicode], description)
+            value = getMember(json, 'value', [unicode], description)
+            valueRefTable = getMember(json, 'valueRefTable', [unicode], description)
+            min = getMember(json, 'min', [int], description, 1)
+            max = getMember(json, 'max', [int, unicode], description, 1)
+            return Type(key, keyRefTable, value, valueRefTable, min, max)
+
+    def toJson(self):
+        if self.value == None and self.min == 1 and self.max == 1:
+            return self.key
+        else:
+            d = {"key": self.key}
+            if self.value != None:
+                d["value"] = self.value
+            if self.min != 1:
+                d["min"] = self.min
+            if self.max != 1:
+                d["max"] = self.max
+            return d
+
+def parseSchema(filename):
+    file = open(filename, "r")
+    s = ""
+    for line in file:
+        if not line.startswith('//'):
+            s += line
+    return DbSchema.fromJson(json.loads(s))
+
+def cBaseType(prefix, type, refTable=None):
+    if type == 'uuid' and refTable:
+        return "struct %s%s *" % (prefix, refTable.lower())
+    else:
+        return {'integer': 'int64_t ',
+                'real': 'double ',
+                'uuid': 'struct uuid ',
+                'boolean': 'bool ',
+                'string': 'char *'}[type]
+
+def printCIDLHeader(schema):
+    prefix = schema.idlPrefix
+    print '''\
+/* Generated automatically -- do not modify!    -*- buffer-read-only: t -*- */
+
+#ifndef %(prefix)sIDL_HEADER
+#define %(prefix)sIDL_HEADER 1
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include "ovsdb-idl-provider.h"
+#include "uuid.h"''' % {'prefix': prefix.upper()}
+    for tableName, table in schema.tables.iteritems():
+        print
+        if table.comment != None:
+            print "/* %s table (%s). */" % (tableName, table.comment)
+        else:
+            print "/* %s table. */" % (tableName)
+        structName = "%s%s" % (prefix, tableName.lower())
+        print "struct %s {" % structName
+        print "\tstruct ovsdb_idl_row header_;"
+        for columnName, column in table.columns.iteritems():
+            print "\n\t/* %s column. */" % columnName
+            type = column.type
+            if type.min == 1 and type.max == 1:
+                singleton = True
+                pointer = ''
+            else:
+                singleton = False
+                pointer = '*'
+            if type.value:
+                print "\tkey_%s%s%s;" % (cBaseType(prefix, type.key, type.keyRefTable), pointer, columnName)
+                print "\tvalue_%s%s%s;" % (cBaseType(prefix, type.value, type.valueRefTable), pointer, columnName)
+            else:
+                print "\t%s%s%s;" % (cBaseType(prefix, type.key, type.keyRefTable), pointer, columnName)
+            if not singleton:
+                print "\tsize_t n_%s;" % columnName
+        print '''
+};
+
+const struct %(s)s *%(s)s_first(const struct ovsdb_idl *);
+const struct %(s)s *%(s)s_next(const struct %(s)s *);
+#define %(S)s_FOR_EACH(ROW, IDL) for ((ROW) = %(s)s_first(IDL); (ROW); (ROW) = %(s)s_next(ROW))''' % {'s': structName, 'S': structName.upper()}
+    print "\nextern struct ovsdb_idl_class %sidl_class;" % prefix
+    print "\n#endif /* %(prefix)sIDL_HEADER */" % {'prefix': prefix.upper()}
+
+def printEnum(members):
+    if len(members) == 0:
+        return
+
+    print "\nenum {";
+    for member in members[:-1]:
+        print "    %s," % member
+    print "    %s" % members[-1]
+    print "};"
+
+def printCIDLSource(schema):
+    prefix = schema.idlPrefix
+    print '''\
+/* Generated automatically -- do not modify!    -*- buffer-read-only: t -*- */
+
+#include <config.h>
+#include %s
+#include <limits.h>
+#include "ovsdb-data.h"''' % schema.idlHeader
+
+    # Table indexes.
+    printEnum(["%sTABLE_%s" % (prefix.upper(), tableName.upper()) for tableName in schema.tables] + ["%sN_TABLES" % prefix.upper()])
+    print "\nstatic struct ovsdb_idl_table_class %stable_classes[%sN_TABLES];" % (prefix, prefix.upper())
+
+    for tableName, table in schema.tables.iteritems():
+        structName = "%s%s" % (prefix, tableName.lower())
+        print "\f"
+        if table.comment != None:
+            print "/* %s table (%s). */" % (tableName, table.comment)
+        else:
+            print "/* %s table. */" % (tableName)
+
+        # Column indexes.
+        printEnum(["%s_COL_%s" % (structName.upper(), columnName.upper())
+                   for columnName in table.columns]
+                  + ["%s_N_COLUMNS" % structName.upper()])
+
+        # Parse function.
+        print '''
+static void
+%s_parse(struct ovsdb_idl_row *row_)
+{
+    struct %s *row = (struct %s *) row_;
+    const struct ovsdb_datum *datum;
+    size_t i UNUSED;
+
+    memset(row_ + 1, 0, sizeof *row - sizeof *row_);''' % (structName, structName, structName)
+
+
+        for columnName, column in table.columns.iteritems():
+            type = column.type
+            refKey = type.key == "uuid" and type.keyRefTable
+            refValue = type.value == "uuid" and type.valueRefTable
+            print
+            print "    datum = &row_->fields[%s_COL_%s];" % (structName.upper(), columnName.upper())
+            if type.value:
+                keyVar = "row->key_%s" % columnName
+                valueVar = "row->value_%s" % columnName
+            else:
+                keyVar = "row->%s" % columnName
+                valueVar = None
+
+            if type.min == 1 and type.max == 1:
+                print "    if (datum->n >= 1) {"
+                if not refKey:
+                    print "        %s = datum->keys[0].%s;" % (keyVar, type.key)
+                else:
+                    print "        %s = (struct %s%s *) ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->keys[0].uuid);" % (keyVar, prefix, type.keyRefTable.lower(), prefix, prefix.upper(), type.keyRefTable.upper())
+
+                if valueVar:
+                    if refValue:
+                        print "        %s = datum->values[0].%s;" % (valueVar, type.value)
+                    else:
+                        print "        %s = (struct %s%s *) ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->values[0].uuid);" % (valueVar, prefix, type.valueRefTable.lower(), prefix, prefix.upper(), type.valueRefTable.upper())
+                print "    }"
+            else:
+                if type.max != 'unlimited':
+                    nMax = "MIN(%d, datum->n)" % type.max
+                else:
+                    nMax = "datum->n"
+                print "    for (i = 0; i < %s; i++) {" % nMax
+                refs = []
+                if refKey:
+                    print "        struct %s%s *keyRow = (struct %s%s *) ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->keys[i].uuid);" % (prefix, type.keyRefTable.lower(), prefix, type.keyRefTable.lower(), prefix, prefix.upper(), type.keyRefTable.upper())
+                    keySrc = "keyRow"
+                    refs.append('keyRow')
+                else:
+                    keySrc = "datum->keys[i].%s" % type.key
+                if refValue:
+                    print "        struct %s%s *valueRow = (struct %s%s *) ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->values[i].uuid);" % (prefix, type.valueRefTable.lower(), prefix, type.valueRefTable.lower(), prefix, prefix.upper(), type.valueRefTable.upper())
+                    valueSrc = "valueRow"
+                    refs.append('valueRow')
+                elif valueVar:
+                    valueSrc = "datum->values[i].%s" % type.value
+                if refs:
+                    print "        if (%s) {" % ' && '.join(refs)
+                    indent = "            "
+                else:
+                    indent = "        "
+                print "%sif (!row->n_%s) {" % (indent, columnName)
+                print "%s    %s = xmalloc(%s * sizeof *%s);" % (indent, keyVar, nMax, keyVar)
+                if valueVar:
+                    print "%s    %s = xmalloc(%s * sizeof %%s);" % (indent, valueVar, nMax, valueVar)
+                print "%s}" % indent
+                print "%s%s[row->n_%s] = %s;" % (indent, keyVar, columnName, keySrc)
+                if valueVar:
+                    print "%s[row->n_%s] = %s;" % (indent, valueVar, columnName, valueSrc)
+                print "%srow->n_%s++;" % (indent, columnName)
+                if refs:
+                    print "        }"
+                print "    }"
+        print "}"
+
+        # Unparse function.
+        nArrays = 0
+        for columnName, column in table.columns.iteritems():
+            type = column.type
+            if type.min != 1 or type.max != 1:
+                if not nArrays:
+                    print '''
+static void
+%s_unparse(struct ovsdb_idl_row *row_)
+{
+    struct %s *row = (struct %s *) row_;
+''' % (structName, structName, structName)
+                if type.value:
+                    keyVar = "row->key_%s" % columnName
+                    valueVar = "row->value_%s" % columnName
+                else:
+                    keyVar = "row->%s" % columnName
+                    valueVar = None
+                print "    free(%s);" % keyVar
+                if valueVar:
+                    print "    free(%s);" % valueVar
+                nArrays += 1
+        if not nArrays:
+            print '''
+static void
+%s_unparse(struct ovsdb_idl_row *row UNUSED)
+{''' % (structName)
+        print "}"
+
+        # First, next functions.
+        print '''
+const struct %(s)s *%(s)s_first(const struct ovsdb_idl *idl)
+{
+    return (const struct %(s)s *) ovsdb_idl_first_row(idl, &%(p)stable_classes[%(P)sTABLE_%(T)s]);
+}
+
+const struct %(s)s *%(s)s_next(const struct %(s)s *row)
+{
+    return (const struct %(s)s *) ovsdb_idl_next_row(&row->header_);
+}''' % {'s': structName, 'p': prefix, 'P': prefix.upper(), 'T': tableName.upper()}
+
+        # Table columns.
+        print "\nstatic struct ovsdb_idl_column %s_columns[%s_N_COLUMNS] = {" % (
+            structName, structName.upper())
+        for columnName, column in table.columns.iteritems():
+            type = column.type
+            
+            if type.value:
+                valueTypeName = type.value.upper()
+            else:
+                valueTypeName = "VOID"
+            if type.max == "unlimited":
+                max = "UINT_MAX"
+            else:
+                max = type.max
+            print "    {\"%s\", {OVSDB_TYPE_%s, OVSDB_TYPE_%s, %d, %s}}," % (
+                columnName, type.key.upper(), valueTypeName,
+                type.min, max)
+        print "};"
+
+    # Table classes.
+    print "\f"
+    print "static struct ovsdb_idl_table_class %stable_classes[%sN_TABLES] = {" % (prefix, prefix.upper())
+    for tableName, table in schema.tables.iteritems():
+        structName = "%s%s" % (prefix, tableName.lower())
+        print "    {\"%s\"," % tableName
+        print "     %s_columns, ARRAY_SIZE(%s_columns)," % (
+            structName, structName)
+        print "     sizeof(struct %s)," % structName
+        print "     %s_parse," % structName
+        print "     %s_unparse}," % structName
+    print "};"
+
+    # IDL class.
+    print "\nstruct ovsdb_idl_class %sidl_class = {" % prefix
+    print "    %stable_classes, ARRAY_SIZE(%stable_classes)" % (prefix, prefix)
+    print "};"
+
+def ovsdb_escape(string):
+    def escape(match):
+        c = match.group(0)
+        if c == '\0':
+            raise Error("strings may not contain null bytes")
+        elif c == '\\':
+            return '\\\\'
+        elif c == '\n':
+            return '\\n'
+        elif c == '\r':
+            return '\\r'
+        elif c == '\t':
+            return '\\t'
+        elif c == '\b':
+            return '\\b'
+        elif c == '\a':
+            return '\\a'
+        else:
+            return '\\x%02x' % ord(c)
+    return re.sub(r'["\\\000-\037]', escape, string)
+
+def printOVSDBSchema(schema):
+    json.dump(schema.toJson(), sys.stdout, sort_keys=True, indent=2)
+
+def usage():
+    print """\
+%(argv0)s: ovsdb schema compiler
+usage: %(argv0)s [OPTIONS] ACTION SCHEMA
+where SCHEMA is the ovsdb schema to read (in JSON format).
+
+One of the following actions must specified:
+  validate                    validate schema without taking any other action
+  c-idl-header                print C header file for IDL
+  c-idl-source                print C source file for IDL implementation
+  ovsdb-schema                print ovsdb parseable schema
+
+The following options are also available:
+  -h, --help                  display this help message
+  -V, --version               display version information\
+""" % {'argv0': argv0}
+    sys.exit(0)
+
+if __name__ == "__main__":
+    try:
+        try:
+            options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
+                                              ['help',
+                                               'version'])
+        except getopt.GetoptError, geo:
+            sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
+            sys.exit(1)
+            
+        optKeys = [key for key, value in options]
+        if '-h' in optKeys or '--help' in optKeys:
+            usage()
+        elif '-V' in optKeys or '--version' in optKeys:
+            print "ovsdb-idlc (Open vSwitch) @VERSION@"
+            sys.exit(0)
+
+        if len(args) != 2:
+            sys.stderr.write("%s: exactly two non-option arguments are "
+                             "required (use --help for help)\n" % argv0)
+            sys.exit(1)
+
+        action, inputFile = args
+        schema = parseSchema(inputFile)
+        if action == 'validate':
+            pass
+        elif action == 'ovsdb-schema':
+            printOVSDBSchema(schema)
+        elif action == 'c-idl-header':
+            printCIDLHeader(schema)
+        elif action == 'c-idl-source':
+            printCIDLSource(schema)
+        else:
+            sys.stderr.write(
+                "%s: unknown action '%s' (use --help for help)\n" %
+                (argv0, action))
+            sys.exit(1)
+    except Error, e:
+        sys.stderr.write("%s: %s\n" % (argv0, e.msg))
+        sys.exit(1)
+
+# Local variables:
+# mode: python
+# End:
diff --git a/ovsdb/ovsdb-server.1.in b/ovsdb/ovsdb-server.1.in
new file mode 100644 (file)
index 0000000..9a888fc
--- /dev/null
@@ -0,0 +1,70 @@
+.\" -*- nroff -*-
+.TH ovsdb\-server 1 "November 2009" "Open vSwitch" "Open vSwitch Manual"
+.ds PN ovsdb\-server
+.
+.SH NAME
+ovsdb\-server \- Open vSwitch database server
+.
+.SH SYNOPSIS
+\fBovsdb\-server\fR
+\fIdatabase\fR
+[\fB--connect \fIremote\fR]\&...
+[\fB--listen \fIlocal\fR]\&...
+.so lib/daemon-syn.man
+.so lib/vlog-syn.man
+.so lib/common-syn.man
+.
+.SH DESCRIPTION
+The \fBovsdb\-server\fR program provides RPC interfaces to an Open
+vSwitch database (OVSDB).  It can listen for JSON-RPC connections from
+TCP/IP or Unix domain socket clients (with \fB\-\-listen\fR), connect to
+remote JSON-RPC TCP/IP or Unix domain socket clients (with
+\fB\-\-connect\fR).
+.PP
+The name of the OVSDB file must be specified on the command line as
+\fIdatabase\fR, which must already have been created and initialized
+using, for example, \fBovsdb\-tool create\fR.
+.
+.SH OPTIONS
+.
+.IP "\fB\-\-listen=\fIlocal\fR"
+Makes \fBovsdb\-server\fR listen for JSON-RPC connections on
+\fIlocal\fR, which must take one of the following forms:
+.
+.RS
+.IP "\fBptcp:\fIport\fR[\fB:\fIip\fR]"
+Listens for JSON-RPC connections on the given TCP \fIport\fR.  By
+default, \fB\*(PN\fR listens for connections to any local IP address,
+but \fIip\fR may be specified to listen only for connections to the
+given \fIip\fR.
+.IP "\fBpunix:\fIfile\fR"
+Listens for JSON-RPC connections on the Unix domain server socket
+named \fIfile\fR.
+.RE
+.
+.IP "\fB\-\-connect=\fIremote\fR"
+Makes \fBovsdb\-server\fR initiate a JSON-RPC connection to
+\fIremote\fR, which must take one of the forms listed below.  The
+server will reconnect to \fIremote\fR as necessary.
+.
+.RS
+.IP "\fBtcp:\fIip\fB:\fIport\fR"
+Connects to the given TCP \fIport\fR on \fIip\fR.
+.IP "\fBunix:\fIfile\fR"
+Connects to the Unix domain server socket named \fIfile\fR.
+.RE
+.
+.SS "Daemon Options"
+.so lib/daemon.man
+.SS "Logging Options"
+.so lib/vlog.man
+.SS "Other Options"
+.so lib/common.man
+.SH "RUNTIME MANAGEMENT COMMANDS"
+\fBovs\-appctl\fR(8) can send commands to a running
+\fBovsdb\-server\fR process.  The currently supported commands are
+described below.
+.so lib/vlog-unixctl.man
+.SH "SEE ALSO"
+.
+.BR ovsdb\-tool (1).
diff --git a/ovsdb/ovsdb-server.c b/ovsdb/ovsdb-server.c
new file mode 100644 (file)
index 0000000..f630813
--- /dev/null
@@ -0,0 +1,246 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb.h"
+
+#include <errno.h>
+#include <getopt.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "command-line.h"
+#include "daemon.h"
+#include "fault.h"
+#include "file.h"
+#include "json.h"
+#include "jsonrpc.h"
+#include "jsonrpc-server.h"
+#include "leak-checker.h"
+#include "list.h"
+#include "ovsdb-error.h"
+#include "poll-loop.h"
+#include "process.h"
+#include "stream.h"
+#include "svec.h"
+#include "timeval.h"
+#include "trigger.h"
+#include "util.h"
+#include "unixctl.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_ovsdb_server
+
+static const struct jsonrpc_server_cbs ovsdb_jsonrpc_cbs;
+
+static unixctl_cb_func ovsdb_server_exit;
+
+static void parse_options(int argc, char *argv[], char **file_namep,
+                          struct svec *active, struct svec *passive,
+                          char **unixctl_pathp);
+static void usage(void) NO_RETURN;
+
+int
+main(int argc, char *argv[])
+{
+    char *unixctl_path = NULL;
+    struct unixctl_server *unixctl;
+    struct ovsdb_jsonrpc_server *jsonrpc;
+    struct svec active, passive;
+    struct ovsdb_error *error;
+    struct ovsdb *db;
+    const char *name;
+    char *file_name;
+    bool do_chdir;
+    bool exiting;
+    int retval;
+    size_t i;
+
+    set_program_name(argv[0]);
+    register_fault_handlers();
+    time_init();
+    vlog_init();
+    signal(SIGPIPE, SIG_IGN);
+    process_init();
+
+    parse_options(argc, argv, &file_name, &active, &passive, &unixctl_path);
+
+    if (get_detach() && is_chdir_enabled()) {
+        /* We need to skip chdir("/") in daemonize() and do it later, because
+         * we need to open the database and possible set up up Unix domain
+         * sockets in the current working directory after we daemonize.  We
+         * can't open the database before we daemonize because file locks
+         * aren't inherited by child processes.  */
+        do_chdir = true;
+        set_no_chdir();
+    } else {
+        do_chdir = false;
+    }
+    die_if_already_running();
+    daemonize();
+
+    error = ovsdb_file_open(file_name, false, &db);
+    if (error) {
+        ovs_fatal(0, "%s", ovsdb_error_to_string(error));
+    }
+
+    jsonrpc = ovsdb_jsonrpc_server_create(db);
+    SVEC_FOR_EACH (i, name, &active) {
+        ovsdb_jsonrpc_server_connect(jsonrpc, name);
+    }
+    SVEC_FOR_EACH (i, name, &passive) {
+        retval = ovsdb_jsonrpc_server_listen(jsonrpc, name);
+        if (retval) {
+            ovs_fatal(retval, "failed to listen on %s", name);
+        }
+    }
+    svec_destroy(&active);
+    svec_destroy(&passive);
+
+    retval = unixctl_server_create(unixctl_path, &unixctl);
+    if (retval) {
+        ovs_fatal(retval, "could not listen for control connections");
+    }
+
+    unixctl_command_register("exit", ovsdb_server_exit, &exiting);
+
+    if (do_chdir) {
+        chdir("/");
+    }
+
+    exiting = false;
+    while (!exiting) {
+        ovsdb_jsonrpc_server_run(jsonrpc);
+        unixctl_server_run(unixctl);
+        ovsdb_trigger_run(db, time_msec());
+
+        ovsdb_jsonrpc_server_wait(jsonrpc);
+        unixctl_server_wait(unixctl);
+        ovsdb_trigger_wait(db, time_msec());
+        poll_block();
+    }
+
+    return 0;
+}
+
+static void
+ovsdb_server_exit(struct unixctl_conn *conn, const char *args UNUSED,
+                  void *exiting_)
+{
+    bool *exiting = exiting_;
+    *exiting = true;
+    unixctl_command_reply(conn, 200, NULL);
+}
+
+static void
+parse_options(int argc, char *argv[], char **file_namep,
+              struct svec *active, struct svec *passive,
+              char **unixctl_pathp)
+{
+    enum {
+        OPT_DUMMY = UCHAR_MAX + 1,
+        OPT_CONNECT,
+        OPT_LISTEN,
+        OPT_UNIXCTL,
+        VLOG_OPTION_ENUMS,
+        LEAK_CHECKER_OPTION_ENUMS
+    };
+    static struct option long_options[] = {
+        {"connect",     required_argument, 0, OPT_CONNECT},
+        {"listen",      required_argument, 0, OPT_LISTEN},
+        {"unixctl",     required_argument, 0, OPT_UNIXCTL},
+        {"help",        no_argument, 0, 'h'},
+        {"version",     no_argument, 0, 'V'},
+        DAEMON_LONG_OPTIONS,
+        VLOG_LONG_OPTIONS,
+        LEAK_CHECKER_LONG_OPTIONS,
+        {0, 0, 0, 0},
+    };
+    char *short_options = long_options_to_short_options(long_options);
+
+    svec_init(active);
+    svec_init(passive);
+    for (;;) {
+        int c;
+
+        c = getopt_long(argc, argv, short_options, long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case OPT_CONNECT:
+            svec_add(active, optarg);
+            break;
+
+        case OPT_LISTEN:
+            svec_add(passive, optarg);
+            break;
+
+        case OPT_UNIXCTL:
+            *unixctl_pathp = optarg;
+            break;
+
+        case 'h':
+            usage();
+
+        case 'V':
+            OVS_PRINT_VERSION(0, 0);
+            exit(EXIT_SUCCESS);
+
+        VLOG_OPTION_HANDLERS
+        DAEMON_OPTION_HANDLERS
+        LEAK_CHECKER_OPTION_HANDLERS
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+
+    argc -= optind;
+    argv += optind;
+
+    if (argc != 1) {
+        ovs_fatal(0, "database file is only non-option argument; "
+                "use --help for usage");
+    }
+
+    *file_namep = argv[0];
+}
+
+static void
+usage(void)
+{
+    printf("%s: Open vSwitch database server\n"
+           "usage: %s [OPTIONS] DATABASE\n"
+           "where DATABASE is a database file in ovsdb format.\n",
+           program_name, program_name);
+    printf("\nJSON-RPC options (may be specified any number of times):\n"
+           "  --connect=REMOTE        make active connection to REMOTE\n"
+           "  --listen=LOCAL          passively listen on LOCAL\n");
+    stream_usage("JSON-RPC", true, true);
+    daemon_usage();
+    vlog_usage();
+    printf("\nOther options:\n"
+           "  -h, --help              display this help message\n"
+           "  -V, --version           display version information\n");
+    leak_checker_usage();
+    exit(EXIT_SUCCESS);
+}
diff --git a/ovsdb/ovsdb-tool.1.in b/ovsdb/ovsdb-tool.1.in
new file mode 100644 (file)
index 0000000..5671310
--- /dev/null
@@ -0,0 +1,74 @@
+.\" -*- nroff -*-
+.de IQ
+.  br
+.  ns
+.  IP "\\$1"
+..
+.\" -*- nroff -*-
+.TH ovsdb\-tool 1 "November 2009" "Open vSwitch" "Open vSwitch Manual"
+.ds PN ovsdb\-tool
+.
+.SH NAME
+ovsdb\-tool \- Open vSwitch database management utility
+.
+.SH SYNOPSIS
+\fBovsdb\-tool \fR[\fIoptions\fR] \fBcreate\fI db schema\fR
+.br
+\fBovsdb\-tool \fR[\fIoptions\fR] \fBquery\fI db transaction\fR
+.br
+\fBovsdb\-tool \fR[\fIoptions\fR] \fBtransact\fI db transaction\fR
+.br
+\fBovsdb\-tool help\fR
+.so lib/vlog-syn.man
+.so lib/common-syn.man
+.
+.SH DESCRIPTION
+The \fBovsdb\-tool\fR program is a command-line tool for managing Open
+vSwitch database (OVSDB) files.  It does not interact directly with
+running Open vSwitch database servers (instead, use
+\fBovsdb\-client\fR).
+.
+.SS "Basic Commands"
+.IP "\fBcreate\fI db schema\fR"
+Reads an OVSDB schema from the file named \fIschema\fR and creates a
+new OVSDB database file named \fIdb\fR using that schema.  The new
+database is initially empty.  This command will not overwrite an
+existing \fIdb\fR.
+.IP
+\fIschema\fR must contain an OVSDB schema in JSON format.  Refer to
+the OVSDB specification for details.
+.
+.IP "\fBquery\fI db transaction\fR"
+Opens \fIdb\fR, executes \fItransaction\fR on it, and prints the
+results.  The \fItransaction\fR must be a JSON array in the format of
+the \fBparams\fR array for the JSON-RPC \fBtransact\fR method, as
+described in the OVSDB specification.
+.IP
+The \fIdb\fR is opened for read-only access, so this command may
+safely run concurrently with other database activity, including
+\fBovsdb-server\fR and other database writers.  The \fItransaction\fR
+may specify database modifications, but these will have no effect on
+\fIdb\fR.
+.
+.IQ "\fBtransact\fI db transaction\fR"
+Opens \fIdb\fR, executes \fItransaction\fR on it, prints the results,
+and commits any changes to \fIdb\fR.  The \fItransaction\fR must be a
+JSON array in the format of the \fBparams\fR array for the JSON-RPC
+\fBtransact\fR method, as described in the OVSDB specification.
+.IP
+The \fIdb\fR is opened and locked for read/write access, so this
+command will fail if the database is opened for writing by any other
+process, including \fBovsdb-server\fR(1).  Use \fBovsdb\-client\fR(1),
+instead, to write to a database that is served by
+\fBovsdb-server\fR(1).
+.
+.SH OPTIONS
+.SS "Logging Options"
+.so lib/vlog.man
+.SS "Other Options"
+.so lib/common.man
+.SH "SEE ALSO"
+.
+\fBovsdb\-server\fR(1),
+\fBovsdb\-client\fR(1),
+and the OVSDB specification.
diff --git a/ovsdb/ovsdb-tool.c b/ovsdb/ovsdb-tool.c
new file mode 100644 (file)
index 0000000..61aea75
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "command-line.h"
+#include "compiler.h"
+#include "file.h"
+#include "log.h"
+#include "json.h"
+#include "ovsdb.h"
+#include "ovsdb-error.h"
+#include "table.h"
+#include "timeval.h"
+#include "util.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_ovsdb_tool
+
+static const struct command all_commands[];
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+    set_program_name(argv[0]);
+    time_init();
+    vlog_init();
+    parse_options(argc, argv);
+    signal(SIGPIPE, SIG_IGN);
+    run_command(argc - optind, argv + optind, all_commands);
+    return 0;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+    static struct option long_options[] = {
+        {"verbose", optional_argument, 0, 'v'},
+        {"help", no_argument, 0, 'h'},
+        {"version", no_argument, 0, 'V'},
+        {0, 0, 0, 0},
+    };
+    char *short_options = long_options_to_short_options(long_options);
+
+    for (;;) {
+        int c;
+
+        c = getopt_long(argc, argv, short_options, long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case 'h':
+            usage();
+
+        case 'V':
+            OVS_PRINT_VERSION(0, 0);
+            exit(EXIT_SUCCESS);
+
+        case 'v':
+            vlog_set_verbosity(optarg);
+            break;
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+}
+
+static void
+usage(void)
+{
+    printf("%s: Open vSwitch database management utility\n"
+           "usage: %s [OPTIONS] COMMAND [ARG...]\n"
+           "  create DB SCHEMA   create DB with the given SCHEMA\n"
+           "  compact DB [DST]   compact DB in-place (or to DST)\n"
+           "  extract-schema DB  print DB's schema on stdout\n"
+           "  query DB TRNS      execute read-only transaction on DB\n"
+           "  transact DB TRNS   execute read/write transaction on DB\n",
+           program_name, program_name);
+    vlog_usage();
+    printf("\nOther options:\n"
+           "  -h, --help                  display this help message\n"
+           "  -V, --version               display version information\n");
+    exit(EXIT_SUCCESS);
+}
+\f
+static struct json *
+parse_json(const char *s)
+{
+    struct json *json = json_from_string(s);
+    if (json->type == JSON_STRING) {
+        ovs_fatal(0, "\"%s\": %s", s, json->u.string);
+    }
+    return json;
+}
+
+static void
+print_and_free_json(struct json *json)
+{
+    char *string = json_to_string(json, JSSF_SORT);
+    json_destroy(json);
+    puts(string);
+    free(string);
+}
+
+static void
+check_ovsdb_error(struct ovsdb_error *error)
+{
+    if (error) {
+        ovs_fatal(0, "%s", ovsdb_error_to_string(error));
+    }
+}
+\f
+static void
+do_create(int argc UNUSED, char *argv[])
+{
+    const char *db_file_name = argv[1];
+    const char *schema_file_name = argv[2];
+    struct ovsdb_schema *schema;
+    struct ovsdb_log *log;
+    struct json *json;
+
+    /* Read schema from file and convert to JSON. */
+    check_ovsdb_error(ovsdb_schema_from_file(schema_file_name, &schema));
+    json = ovsdb_schema_to_json(schema);
+
+    /* Create database file. */
+    check_ovsdb_error(ovsdb_log_open(db_file_name, O_RDWR | O_CREAT | O_EXCL,
+                                     &log));
+    check_ovsdb_error(ovsdb_log_write(log, json));
+    check_ovsdb_error(ovsdb_log_commit(log));
+    ovsdb_log_close(log);
+
+    json_destroy(json);
+}
+
+static void
+transact(bool read_only, const char *db_file_name, const char *transaction)
+{
+    struct json *request, *result;
+    struct ovsdb *db;
+
+    check_ovsdb_error(ovsdb_file_open(db_file_name, read_only, &db));
+
+    request = parse_json(transaction);
+    result = ovsdb_execute(db, request, 0, NULL);
+    json_destroy(request);
+
+    print_and_free_json(result);
+    ovsdb_destroy(db);
+}
+
+static void
+do_query(int argc UNUSED, char *argv[])
+{
+    transact(true, argv[1], argv[2]);
+}
+
+static void
+do_transact(int argc UNUSED, char *argv[])
+{
+    transact(false, argv[1], argv[2]);
+}
+
+static void
+do_help(int argc UNUSED, char *argv[] UNUSED)
+{
+    usage();
+}
+
+static const struct command all_commands[] = {
+    { "create", 2, 2, do_create },
+    { "query", 2, 2, do_query },
+    { "transact", 2, 2, do_transact },
+    { "help", 0, INT_MAX, do_help },
+    { NULL, 0, 0, NULL },
+};
diff --git a/ovsdb/ovsdb.c b/ovsdb/ovsdb.c
new file mode 100644 (file)
index 0000000..27254e6
--- /dev/null
@@ -0,0 +1,227 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb.h"
+
+#include "json.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "table.h"
+#include "transaction.h"
+
+struct ovsdb_schema *
+ovsdb_schema_create(const char *name, const char *comment)
+{
+    struct ovsdb_schema *schema;
+
+    schema = xzalloc(sizeof *schema);
+    schema->name = xstrdup(name);
+    schema->comment = comment ? xstrdup(comment) : NULL;
+    shash_init(&schema->tables);
+
+    return schema;
+}
+
+void
+ovsdb_schema_destroy(struct ovsdb_schema *schema)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &schema->tables) {
+        ovsdb_table_schema_destroy(node->data);
+    }
+    shash_destroy(&schema->tables);
+    free(schema->comment);
+    free(schema->name);
+    free(schema);
+}
+
+struct ovsdb_error *
+ovsdb_schema_from_file(const char *file_name, struct ovsdb_schema **schemap)
+{
+    struct ovsdb_schema *schema;
+    struct ovsdb_error *error;
+    struct json *json;
+
+    *schemap = NULL;
+    json = json_from_file(file_name);
+    if (json->type == JSON_STRING) {
+        error = ovsdb_error("failed to read schema",
+                           "\"%s\" could not be read as JSON (%s)",
+                           file_name, json_string(json));
+        json_destroy(json);
+        return error;
+    }
+
+    error = ovsdb_schema_from_json(json, &schema);
+    if (error) {
+        json_destroy(json);
+        return ovsdb_wrap_error(error,
+                                "failed to parse \"%s\" as ovsdb schema",
+                                file_name);
+    }
+
+    *schemap = schema;
+    return NULL;
+}
+
+struct ovsdb_error *
+ovsdb_schema_from_json(struct json *json, struct ovsdb_schema **schemap)
+{
+    struct ovsdb_schema *schema;
+    const struct json *name, *comment, *tables;
+    struct ovsdb_error *error;
+    struct shash_node *node;
+    struct ovsdb_parser parser;
+
+    *schemap = NULL;
+
+    ovsdb_parser_init(&parser, json, "database schema");
+    name = ovsdb_parser_member(&parser, "name", OP_ID);
+    comment = ovsdb_parser_member(&parser, "comment", OP_STRING | OP_OPTIONAL);
+    tables = ovsdb_parser_member(&parser, "tables", OP_OBJECT);
+    error = ovsdb_parser_finish(&parser);
+    if (error) {
+        return error;
+    }
+
+    schema = ovsdb_schema_create(json_string(name),
+                                 comment ? json_string(comment) : NULL);
+    SHASH_FOR_EACH (node, json_object(tables)) {
+        struct ovsdb_table_schema *table;
+
+        if (node->name[0] == '_') {
+            error = ovsdb_syntax_error(json, NULL, "names beginning with "
+                                       "\"_\" are reserved");
+        } else if (!ovsdb_parser_is_id(node->name)) {
+            error = ovsdb_syntax_error(json, NULL, "name must be a valid id");
+        } else {
+            error = ovsdb_table_schema_from_json(node->data, node->name,
+                                                 &table);
+        }
+        if (error) {
+            ovsdb_schema_destroy(schema);
+            return error;
+        }
+
+        shash_add(&schema->tables, table->name, table);
+    }
+    *schemap = schema;
+    return 0;
+}
+
+struct json *
+ovsdb_schema_to_json(const struct ovsdb_schema *schema)
+{
+    struct json *json, *tables;
+    struct shash_node *node;
+
+    json = json_object_create();
+    json_object_put_string(json, "name", schema->name);
+    if (schema->comment) {
+        json_object_put_string(json, "comment", schema->comment);
+    }
+
+    tables = json_object_create();
+
+    SHASH_FOR_EACH (node, &schema->tables) {
+        struct ovsdb_table_schema *table = node->data;
+        json_object_put(tables, table->name,
+                        ovsdb_table_schema_to_json(table));
+    }
+    json_object_put(json, "tables", tables);
+
+    return json;
+}
+\f
+struct ovsdb *
+ovsdb_create(struct ovsdb_schema *schema)
+{
+    struct shash_node *node;
+    struct ovsdb *db;
+
+    db = xmalloc(sizeof *db);
+    db->schema = schema;
+    list_init(&db->replicas);
+    list_init(&db->triggers);
+    db->run_triggers = false;
+
+    shash_init(&db->tables);
+    SHASH_FOR_EACH (node, &schema->tables) {
+        struct ovsdb_table_schema *ts = node->data;
+        shash_add(&db->tables, node->name, ovsdb_table_create(ts));
+    }
+
+    return db;
+}
+
+void
+ovsdb_destroy(struct ovsdb *db)
+{
+    if (db) {
+        struct shash_node *node;
+
+        /* Remove all the replicas. */
+        while (!list_is_empty(&db->replicas)) {
+            struct ovsdb_replica *r
+                = CONTAINER_OF(list_pop_back(&db->replicas),
+                               struct ovsdb_replica, node);
+            ovsdb_remove_replica(db, r);
+        }
+
+        /* Delete all the tables.  This also deletes their schemas. */
+        SHASH_FOR_EACH (node, &db->tables) {
+            struct ovsdb_table *table = node->data;
+            ovsdb_table_destroy(table);
+        }
+        shash_destroy(&db->tables);
+
+        /* The schemas, but not the table that points to them, were deleted in
+         * the previous step, so we need to clear out the table.  We can't
+         * destroy the table, because ovsdb_schema_destroy() will do that. */
+        shash_clear(&db->schema->tables);
+
+        ovsdb_schema_destroy(db->schema);
+        free(db);
+    }
+}
+
+struct ovsdb_table *
+ovsdb_get_table(const struct ovsdb *db, const char *name)
+{
+    return shash_find_data(&db->tables, name);
+}
+\f
+void
+ovsdb_replica_init(struct ovsdb_replica *r,
+                   const struct ovsdb_replica_class *class)
+{
+    r->class = class;
+}
+
+void
+ovsdb_add_replica(struct ovsdb *db, struct ovsdb_replica *r)
+{
+    list_push_back(&db->replicas, &r->node);
+}
+
+void
+ovsdb_remove_replica(struct ovsdb *db UNUSED, struct ovsdb_replica *r)
+{
+    list_remove(&r->node);
+    (r->class->destroy)(r);
+}
diff --git a/ovsdb/ovsdb.h b/ovsdb/ovsdb.h
new file mode 100644 (file)
index 0000000..24ebd9c
--- /dev/null
@@ -0,0 +1,91 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_OVSDB_H
+#define OVSDB_OVSDB_H 1
+
+#include "compiler.h"
+#include "hmap.h"
+#include "list.h"
+#include "shash.h"
+
+struct json;
+struct ovsdb_log;
+struct ovsdb_txn;
+struct uuid;
+
+/* Database schema. */
+struct ovsdb_schema {
+    char *name;
+    char *comment;
+    struct shash tables;        /* Contains "struct ovsdb_table_schema *"s. */
+};
+
+struct ovsdb_schema *ovsdb_schema_create(const char *name,
+                                         const char *comment);
+void ovsdb_schema_destroy(struct ovsdb_schema *);
+
+struct ovsdb_error *ovsdb_schema_from_file(const char *file_name,
+                                           struct ovsdb_schema **)
+    WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_schema_from_json(struct json *,
+                                           struct ovsdb_schema **)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_schema_to_json(const struct ovsdb_schema *);
+\f
+/* Database. */
+struct ovsdb {
+    struct ovsdb_schema *schema;
+    struct list replicas;       /* Contains "struct ovsdb_replica"s. */
+    struct shash tables;        /* Contains "struct ovsdb_table *"s. */
+
+    /* Triggers. */
+    struct list triggers;       /* Contains "struct ovsdb_trigger"s. */
+    bool run_triggers;
+};
+
+struct ovsdb *ovsdb_create(struct ovsdb_schema *);
+void ovsdb_destroy(struct ovsdb *);
+
+struct ovsdb_error *ovsdb_from_json(const struct json *, struct ovsdb **)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_to_json(const struct ovsdb *);
+
+struct ovsdb_table *ovsdb_get_table(const struct ovsdb *, const char *);
+
+struct json *ovsdb_execute(struct ovsdb *, const struct json *params,
+                           long long int elapsed_msec,
+                           long long int *timeout_msec);
+\f
+/* Database replication. */
+
+struct ovsdb_replica {
+    struct list node;           /* Element in "struct ovsdb" replicas list. */
+    const struct ovsdb_replica_class *class;
+};
+
+struct ovsdb_replica_class {
+    struct ovsdb_error *(*commit)(struct ovsdb_replica *,
+                                  const struct ovsdb_txn *, bool durable);
+    void (*destroy)(struct ovsdb_replica *);
+};
+
+void ovsdb_replica_init(struct ovsdb_replica *,
+                        const struct ovsdb_replica_class *);
+
+void ovsdb_add_replica(struct ovsdb *, struct ovsdb_replica *);
+void ovsdb_remove_replica(struct ovsdb *, struct ovsdb_replica *);
+
+#endif /* ovsdb/ovsdb.h */
diff --git a/ovsdb/query.c b/ovsdb/query.c
new file mode 100644 (file)
index 0000000..878ac5b
--- /dev/null
@@ -0,0 +1,99 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "query.h"
+
+#include "column.h"
+#include "condition.h"
+#include "row.h"
+#include "table.h"
+
+void
+ovsdb_query(struct ovsdb_table *table, const struct ovsdb_condition *cnd,
+            bool (*output_row)(const struct ovsdb_row *, void *aux), void *aux)
+{
+    if (cnd->n_clauses > 0
+        && cnd->clauses[0].column->index == OVSDB_COL_UUID
+        && cnd->clauses[0].function == OVSDB_F_EQ) {
+        /* Optimize the case where the query has a clause of the form "uuid ==
+         * <some-uuid>", since we have an index on UUID. */
+        const struct ovsdb_row *row;
+
+        row = ovsdb_table_get_row(table, &cnd->clauses[0].arg.keys[0].uuid);
+        if (row && row->table == table && ovsdb_condition_evaluate(row, cnd)) {
+            output_row(row, aux);
+        }
+    } else {
+        /* Linear scan. */
+        const struct ovsdb_row *row, *next;
+
+        HMAP_FOR_EACH_SAFE (row, next, struct ovsdb_row, hmap_node,
+                            &table->rows) {
+            if (ovsdb_condition_evaluate(row, cnd) && !output_row(row, aux)) {
+                break;
+            }
+        }
+    }
+}
+
+static bool
+query_row_set_cb(const struct ovsdb_row *row, void *results_)
+{
+    struct ovsdb_row_set *results = results_;
+    ovsdb_row_set_add_row(results, row);
+    return true;
+}
+
+void
+ovsdb_query_row_set(struct ovsdb_table *table,
+                    const struct ovsdb_condition *condition,
+                    struct ovsdb_row_set *results)
+{
+    ovsdb_query(table, condition, query_row_set_cb, results);
+}
+
+static bool
+query_distinct_cb(const struct ovsdb_row *row, void *hash_)
+{
+    struct ovsdb_row_hash *hash = hash_;
+    ovsdb_row_hash_insert(hash, row);
+    return true;
+}
+
+void
+ovsdb_query_distinct(struct ovsdb_table *table,
+                     const struct ovsdb_condition *condition,
+                     const struct ovsdb_column_set *columns,
+                     struct ovsdb_row_set *results)
+{
+    if (!columns || ovsdb_column_set_contains(columns, OVSDB_COL_UUID)) {
+        /* All the result rows are guaranteed to be distinct anyway. */
+        return ovsdb_query_row_set(table, condition, results);
+    } else {
+        /* Use hash table to drop duplicates. */
+        struct ovsdb_row_hash_node *node;
+        struct ovsdb_row_hash hash;
+
+        ovsdb_row_hash_init(&hash, columns);
+        ovsdb_query(table, condition, query_distinct_cb, &hash);
+        HMAP_FOR_EACH (node, struct ovsdb_row_hash_node, hmap_node,
+                       &hash.rows) {
+            ovsdb_row_set_add_row(results, node->row);
+        }
+        ovsdb_row_hash_destroy(&hash, false);
+    }
+}
diff --git a/ovsdb/query.h b/ovsdb/query.h
new file mode 100644 (file)
index 0000000..f5cfe2e
--- /dev/null
@@ -0,0 +1,37 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_QUERY_H
+#define OVSDB_QUERY_H 1
+
+#include <stdbool.h>
+
+struct ovsdb_column_set;
+struct ovsdb_condition;
+struct ovsdb_row;
+struct ovsdb_row_set;
+struct ovsdb_table;
+struct ovsdb_txn;
+
+void ovsdb_query(struct ovsdb_table *, const struct ovsdb_condition *,
+                 bool (*output_row)(const struct ovsdb_row *, void *aux),
+                 void *aux);
+void ovsdb_query_row_set(struct ovsdb_table *, const struct ovsdb_condition *,
+                         struct ovsdb_row_set *);
+void ovsdb_query_distinct(struct ovsdb_table *, const struct ovsdb_condition *,
+                          const struct ovsdb_column_set *,
+                          struct ovsdb_row_set *);
+
+#endif /* ovsdb/query.h */
diff --git a/ovsdb/row.c b/ovsdb/row.c
new file mode 100644 (file)
index 0000000..1b81942
--- /dev/null
@@ -0,0 +1,386 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "row.h"
+
+#include <assert.h>
+#include <stddef.h>
+
+#include "json.h"
+#include "ovsdb-error.h"
+#include "shash.h"
+#include "sort.h"
+#include "table.h"
+
+static struct ovsdb_row *
+allocate_row(const struct ovsdb_table *table)
+{
+    size_t n_fields = shash_count(&table->schema->columns);
+    size_t row_size = (offsetof(struct ovsdb_row, fields)
+                       + sizeof(struct ovsdb_datum) * n_fields);
+    struct ovsdb_row *row = xmalloc(row_size);
+    row->table = (struct ovsdb_table *) table;
+    row->txn_row = NULL;
+    return row;
+}
+
+struct ovsdb_row *
+ovsdb_row_create(const struct ovsdb_table *table)
+{
+    struct shash_node *node;
+    struct ovsdb_row *row;
+
+    row = allocate_row(table);
+    SHASH_FOR_EACH (node, &table->schema->columns) {
+        const struct ovsdb_column *column = node->data;
+        ovsdb_datum_init_default(&row->fields[column->index], &column->type);
+    }
+    return row;
+}
+
+struct ovsdb_row *
+ovsdb_row_clone(const struct ovsdb_row *old)
+{
+    const struct ovsdb_table *table = old->table;
+    const struct shash_node *node;
+    struct ovsdb_row *new;
+
+    new = allocate_row(table);
+    SHASH_FOR_EACH (node, &table->schema->columns) {
+        const struct ovsdb_column *column = node->data;
+        ovsdb_datum_clone(&new->fields[column->index],
+                          &old->fields[column->index],
+                          &column->type);
+    }
+    return new;
+}
+
+/* The caller is responsible for ensuring that 'row' has been removed from its
+ * table and that it is not participating in a transaction. */
+void
+ovsdb_row_destroy(struct ovsdb_row *row)
+{
+    if (row) {
+        const struct ovsdb_table *table = row->table;
+        const struct shash_node *node;
+
+        SHASH_FOR_EACH (node, &table->schema->columns) {
+            const struct ovsdb_column *column = node->data;
+            ovsdb_datum_destroy(&row->fields[column->index], &column->type);
+        }
+        free(row);
+    }
+}
+
+uint32_t
+ovsdb_row_hash_columns(const struct ovsdb_row *row,
+                       const struct ovsdb_column_set *columns,
+                       uint32_t basis)
+{
+    size_t i;
+
+    for (i = 0; i < columns->n_columns; i++) {
+        const struct ovsdb_column *column = columns->columns[i];
+        basis = ovsdb_datum_hash(&row->fields[column->index], &column->type,
+                                 basis);
+    }
+
+    return basis;
+}
+
+int
+ovsdb_row_compare_columns_3way(const struct ovsdb_row *a,
+                               const struct ovsdb_row *b,
+                               const struct ovsdb_column_set *columns)
+{
+    size_t i;
+
+    for (i = 0; i < columns->n_columns; i++) {
+        const struct ovsdb_column *column = columns->columns[i];
+        int cmp = ovsdb_datum_compare_3way(&a->fields[column->index],
+                                           &b->fields[column->index],
+                                           &column->type);
+        if (cmp) {
+            return cmp;
+        }
+    }
+
+    return 0;
+}
+
+bool
+ovsdb_row_equal_columns(const struct ovsdb_row *a,
+                        const struct ovsdb_row *b,
+                        const struct ovsdb_column_set *columns)
+{
+    size_t i;
+
+    for (i = 0; i < columns->n_columns; i++) {
+        const struct ovsdb_column *column = columns->columns[i];
+        if (!ovsdb_datum_equals(&a->fields[column->index],
+                                &b->fields[column->index],
+                                &column->type)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+void
+ovsdb_row_update_columns(struct ovsdb_row *dst,
+                         const struct ovsdb_row *src,
+                         const struct ovsdb_column_set *columns)
+{
+    size_t i;
+
+    for (i = 0; i < columns->n_columns; i++) {
+        const struct ovsdb_column *column = columns->columns[i];
+        ovsdb_datum_destroy(&dst->fields[column->index], &column->type);
+        ovsdb_datum_clone(&dst->fields[column->index],
+                          &src->fields[column->index],
+                          &column->type);
+    }
+}
+
+struct ovsdb_error *
+ovsdb_row_from_json(struct ovsdb_row *row, const struct json *json,
+                    const struct ovsdb_symbol_table *symtab,
+                    struct ovsdb_column_set *included)
+{
+    struct ovsdb_table_schema *schema = row->table->schema;
+    struct ovsdb_error *error;
+    struct shash_node *node;
+
+    if (json->type != JSON_OBJECT) {
+        return ovsdb_syntax_error(json, NULL, "row must be JSON object");
+    }
+
+    SHASH_FOR_EACH (node, json_object(json)) {
+        const char *column_name = node->name;
+        const struct ovsdb_column *column;
+        struct ovsdb_datum datum;
+
+        column = ovsdb_table_schema_get_column(schema, column_name);
+        if (!column) {
+            return ovsdb_syntax_error(json, "unknown column",
+                                      "No column %s in table %s.",
+                                      column_name, schema->name);
+        }
+
+        error = ovsdb_datum_from_json(&datum, &column->type, node->data,
+                                      symtab);
+        if (error) {
+            return error;
+        }
+        ovsdb_datum_swap(&row->fields[column->index], &datum);
+        ovsdb_datum_destroy(&datum, &column->type);
+        if (included) {
+            ovsdb_column_set_add(included, column);
+        }
+    }
+
+    return NULL;
+}
+
+static void
+put_json_column(struct json *object, const struct ovsdb_row *row,
+                const struct ovsdb_column *column)
+{
+    json_object_put(object, column->name,
+                    ovsdb_datum_to_json(&row->fields[column->index],
+                                        &column->type));
+}
+
+struct json *
+ovsdb_row_to_json(const struct ovsdb_row *row,
+                  const struct ovsdb_column_set *columns)
+{
+    struct json *json;
+    size_t i;
+
+    json = json_object_create();
+    for (i = 0; i < columns->n_columns; i++) {
+        put_json_column(json, row, columns->columns[i]);
+    }
+    return json;
+}
+\f
+void
+ovsdb_row_set_init(struct ovsdb_row_set *set)
+{
+    set->rows = NULL;
+    set->n_rows = set->allocated_rows = 0;
+}
+
+void
+ovsdb_row_set_destroy(struct ovsdb_row_set *set)
+{
+    free(set->rows);
+}
+
+void
+ovsdb_row_set_add_row(struct ovsdb_row_set *set, const struct ovsdb_row *row)
+{
+    if (set->n_rows >= set->allocated_rows) {
+        set->rows = x2nrealloc(set->rows, &set->allocated_rows,
+                               sizeof *set->rows);
+    }
+    set->rows[set->n_rows++] = row;
+}
+
+struct json *
+ovsdb_row_set_to_json(const struct ovsdb_row_set *rows,
+                      const struct ovsdb_column_set *columns)
+{
+    struct json **json_rows;
+    size_t i;
+
+    json_rows = xmalloc(rows->n_rows * sizeof *json_rows);
+    for (i = 0; i < rows->n_rows; i++) {
+        json_rows[i] = ovsdb_row_to_json(rows->rows[i], columns);
+    }
+    return json_array_create(json_rows, rows->n_rows);
+}
+
+struct ovsdb_row_set_sort_cbdata {
+    struct ovsdb_row_set *set;
+    const struct ovsdb_column_set *columns;
+};
+
+static int
+ovsdb_row_set_sort_compare_cb(size_t a, size_t b, void *cbdata_)
+{
+    struct ovsdb_row_set_sort_cbdata *cbdata = cbdata_;
+    return ovsdb_row_compare_columns_3way(cbdata->set->rows[a],
+                                          cbdata->set->rows[b],
+                                          cbdata->columns);
+}
+
+static void
+ovsdb_row_set_sort_swap_cb(size_t a, size_t b, void *cbdata_)
+{
+    struct ovsdb_row_set_sort_cbdata *cbdata = cbdata_;
+    const struct ovsdb_row *tmp = cbdata->set->rows[a];
+    cbdata->set->rows[a] = cbdata->set->rows[b];
+    cbdata->set->rows[b] = tmp;
+}
+
+void
+ovsdb_row_set_sort(struct ovsdb_row_set *set,
+                   const struct ovsdb_column_set *columns)
+{
+    if (columns && columns->n_columns && set->n_rows > 1) {
+        struct ovsdb_row_set_sort_cbdata cbdata;
+        cbdata.set = set;
+        cbdata.columns = columns;
+        sort(set->n_rows,
+             ovsdb_row_set_sort_compare_cb,
+             ovsdb_row_set_sort_swap_cb,
+             &cbdata);
+    }
+}
+\f
+void
+ovsdb_row_hash_init(struct ovsdb_row_hash *rh,
+                    const struct ovsdb_column_set *columns)
+{
+    hmap_init(&rh->rows);
+    ovsdb_column_set_clone(&rh->columns, columns);
+}
+
+void
+ovsdb_row_hash_destroy(struct ovsdb_row_hash *rh, bool destroy_rows)
+{
+    struct ovsdb_row_hash_node *node, *next;
+
+    HMAP_FOR_EACH_SAFE (node, next, struct ovsdb_row_hash_node, hmap_node,
+                        &rh->rows) {
+        hmap_remove(&rh->rows, &node->hmap_node);
+        if (destroy_rows) {
+            ovsdb_row_destroy((struct ovsdb_row *) node->row);
+        }
+        free(node);
+    }
+    hmap_destroy(&rh->rows);
+    ovsdb_column_set_destroy(&rh->columns);
+}
+
+size_t
+ovsdb_row_hash_count(const struct ovsdb_row_hash *rh)
+{
+    return hmap_count(&rh->rows);
+}
+
+bool
+ovsdb_row_hash_contains(const struct ovsdb_row_hash *rh,
+                        const struct ovsdb_row *row)
+{
+    size_t hash = ovsdb_row_hash_columns(row, &rh->columns, 0);
+    return ovsdb_row_hash_contains__(rh, row, hash);
+}
+
+/* Returns true if every row in 'b' has an equal row in 'a'. */
+bool
+ovsdb_row_hash_contains_all(const struct ovsdb_row_hash *a,
+                            const struct ovsdb_row_hash *b)
+{
+    struct ovsdb_row_hash_node *node;
+
+    assert(ovsdb_column_set_equals(&a->columns, &b->columns));
+    HMAP_FOR_EACH (node, struct ovsdb_row_hash_node, hmap_node, &b->rows) {
+        if (!ovsdb_row_hash_contains__(a, node->row, node->hmap_node.hash)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool
+ovsdb_row_hash_insert(struct ovsdb_row_hash *rh, const struct ovsdb_row *row)
+{
+    size_t hash = ovsdb_row_hash_columns(row, &rh->columns, 0);
+    return ovsdb_row_hash_insert__(rh, row, hash);
+}
+
+bool
+ovsdb_row_hash_contains__(const struct ovsdb_row_hash *rh,
+                          const struct ovsdb_row *row, size_t hash)
+{
+    struct ovsdb_row_hash_node *node;
+    HMAP_FOR_EACH_WITH_HASH (node, struct ovsdb_row_hash_node, hmap_node,
+                             hash, &rh->rows) {
+        if (ovsdb_row_equal_columns(row, node->row, &rh->columns)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool
+ovsdb_row_hash_insert__(struct ovsdb_row_hash *rh, const struct ovsdb_row *row,
+                        size_t hash)
+{
+    if (!ovsdb_row_hash_contains__(rh, row, hash)) {
+        struct ovsdb_row_hash_node *node = xmalloc(sizeof *node);
+        node->row = row;
+        hmap_insert(&rh->rows, &node->hmap_node, hash);
+        return true;
+    } else {
+        return false;
+    }
+}
diff --git a/ovsdb/row.h b/ovsdb/row.h
new file mode 100644 (file)
index 0000000..55c4f14
--- /dev/null
@@ -0,0 +1,139 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_ROW_H
+#define OVSDB_ROW_H 1
+
+#include <stddef.h>
+#include <stdint.h>
+#include "column.h"
+#include "hmap.h"
+#include "ovsdb-data.h"
+
+struct ovsdb_column_set;
+
+/* A row in a database table. */
+struct ovsdb_row {
+    struct ovsdb_table *table;  /* Table to which this belongs. */
+    struct hmap_node hmap_node; /* Element in ovsdb_table's 'rows' hmap. */
+    struct ovsdb_txn_row *txn_row; /* Transaction that row is in, if any. */
+    struct ovsdb_datum fields[];
+};
+
+struct ovsdb_row *ovsdb_row_create(const struct ovsdb_table *);
+struct ovsdb_row *ovsdb_row_clone(const struct ovsdb_row *);
+void ovsdb_row_destroy(struct ovsdb_row *);
+
+uint32_t ovsdb_row_hash_columns(const struct ovsdb_row *,
+                                const struct ovsdb_column_set *,
+                                uint32_t basis);
+bool ovsdb_row_equal_columns(const struct ovsdb_row *,
+                             const struct ovsdb_row *,
+                             const struct ovsdb_column_set *);
+int ovsdb_row_compare_columns_3way(const struct ovsdb_row *,
+                                   const struct ovsdb_row *,
+                                   const struct ovsdb_column_set *);
+void ovsdb_row_update_columns(struct ovsdb_row *, const struct ovsdb_row *,
+                              const struct ovsdb_column_set *);
+
+struct ovsdb_error *ovsdb_row_from_json(struct ovsdb_row *,
+                                        const struct json *,
+                                        const struct ovsdb_symbol_table *,
+                                        struct ovsdb_column_set *included)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_row_to_json(const struct ovsdb_row *,
+                               const struct ovsdb_column_set *include);
+
+static inline const struct uuid *
+ovsdb_row_get_uuid(const struct ovsdb_row *row)
+{
+    return &row->fields[OVSDB_COL_UUID].keys[0].uuid;
+}
+
+static inline struct uuid *
+ovsdb_row_get_uuid_rw(struct ovsdb_row *row)
+{
+    return &row->fields[OVSDB_COL_UUID].keys[0].uuid;
+}
+
+static inline const struct uuid *
+ovsdb_row_get_version(const struct ovsdb_row *row)
+{
+    return &row->fields[OVSDB_COL_VERSION].keys[0].uuid;
+}
+
+static inline struct uuid *
+ovsdb_row_get_version_rw(struct ovsdb_row *row)
+{
+    return &row->fields[OVSDB_COL_VERSION].keys[0].uuid;
+}
+
+static inline uint32_t
+ovsdb_row_hash(const struct ovsdb_row *row)
+{
+    return uuid_hash(ovsdb_row_get_uuid(row));
+}
+\f
+/* An unordered collection of rows. */
+struct ovsdb_row_set {
+    const struct ovsdb_row **rows;
+    size_t n_rows, allocated_rows;
+};
+
+#define OVSDB_ROW_SET_INITIALIZER { NULL, 0, 0 }
+
+void ovsdb_row_set_init(struct ovsdb_row_set *);
+void ovsdb_row_set_destroy(struct ovsdb_row_set *);
+void ovsdb_row_set_add_row(struct ovsdb_row_set *, const struct ovsdb_row *);
+
+struct json *ovsdb_row_set_to_json(const struct ovsdb_row_set *,
+                                   const struct ovsdb_column_set *);
+
+void ovsdb_row_set_sort(struct ovsdb_row_set *,
+                        const struct ovsdb_column_set *);
+\f
+/* A hash table of rows.  A specified set of columns is used for hashing and
+ * comparing rows.
+ *
+ * The row hash doesn't necessarily own its rows.  They may be owned by, for
+ * example, an ovsdb_table. */
+struct ovsdb_row_hash {
+    struct hmap rows;
+    struct ovsdb_column_set columns;
+};
+
+#define OVSDB_ROW_HASH_INITIALIZER(RH) \
+    { HMAP_INITIALIZER(&(RH).rows), OVSDB_COLUMN_SET_INITIALIZER }
+
+struct ovsdb_row_hash_node {
+    struct hmap_node hmap_node;
+    const struct ovsdb_row *row;
+};
+
+void ovsdb_row_hash_init(struct ovsdb_row_hash *,
+                         const struct ovsdb_column_set *);
+void ovsdb_row_hash_destroy(struct ovsdb_row_hash *, bool destroy_rows);
+size_t ovsdb_row_hash_count(const struct ovsdb_row_hash *);
+bool ovsdb_row_hash_contains(const struct ovsdb_row_hash *,
+                             const struct ovsdb_row *);
+bool ovsdb_row_hash_contains_all(const struct ovsdb_row_hash *,
+                                 const struct ovsdb_row_hash *);
+bool ovsdb_row_hash_insert(struct ovsdb_row_hash *, const struct ovsdb_row *);
+bool ovsdb_row_hash_contains__(const struct ovsdb_row_hash *,
+                               const struct ovsdb_row *, size_t hash);
+bool ovsdb_row_hash_insert__(struct ovsdb_row_hash *,
+                             const struct ovsdb_row *, size_t hash);
+
+#endif /* ovsdb/row.h */
diff --git a/ovsdb/simplejson/__init__.py b/ovsdb/simplejson/__init__.py
new file mode 100644 (file)
index 0000000..d5b4d39
--- /dev/null
@@ -0,0 +1,318 @@
+r"""JSON (JavaScript Object Notation) <http://json.org> is a subset of
+JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data
+interchange format.
+
+:mod:`simplejson` exposes an API familiar to users of the standard library
+:mod:`marshal` and :mod:`pickle` modules. It is the externally maintained
+version of the :mod:`json` library contained in Python 2.6, but maintains
+compatibility with Python 2.4 and Python 2.5 and (currently) has
+significant performance advantages, even without using the optional C
+extension for speedups.
+
+Encoding basic Python object hierarchies::
+
+    >>> import simplejson as json
+    >>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
+    '["foo", {"bar": ["baz", null, 1.0, 2]}]'
+    >>> print json.dumps("\"foo\bar")
+    "\"foo\bar"
+    >>> print json.dumps(u'\u1234')
+    "\u1234"
+    >>> print json.dumps('\\')
+    "\\"
+    >>> print json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True)
+    {"a": 0, "b": 0, "c": 0}
+    >>> from StringIO import StringIO
+    >>> io = StringIO()
+    >>> json.dump(['streaming API'], io)
+    >>> io.getvalue()
+    '["streaming API"]'
+
+Compact encoding::
+
+    >>> import simplejson as json
+    >>> json.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':'))
+    '[1,2,3,{"4":5,"6":7}]'
+
+Pretty printing::
+
+    >>> import simplejson as json
+    >>> s = json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=4)
+    >>> print '\n'.join([l.rstrip() for l in  s.splitlines()])
+    {
+        "4": 5,
+        "6": 7
+    }
+
+Decoding JSON::
+
+    >>> import simplejson as json
+    >>> obj = [u'foo', {u'bar': [u'baz', None, 1.0, 2]}]
+    >>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') == obj
+    True
+    >>> json.loads('"\\"foo\\bar"') == u'"foo\x08ar'
+    True
+    >>> from StringIO import StringIO
+    >>> io = StringIO('["streaming API"]')
+    >>> json.load(io)[0] == 'streaming API'
+    True
+
+Specializing JSON object decoding::
+
+    >>> import simplejson as json
+    >>> def as_complex(dct):
+    ...     if '__complex__' in dct:
+    ...         return complex(dct['real'], dct['imag'])
+    ...     return dct
+    ...
+    >>> json.loads('{"__complex__": true, "real": 1, "imag": 2}',
+    ...     object_hook=as_complex)
+    (1+2j)
+    >>> import decimal
+    >>> json.loads('1.1', parse_float=decimal.Decimal) == decimal.Decimal('1.1')
+    True
+
+Specializing JSON object encoding::
+
+    >>> import simplejson as json
+    >>> def encode_complex(obj):
+    ...     if isinstance(obj, complex):
+    ...         return [obj.real, obj.imag]
+    ...     raise TypeError(repr(o) + " is not JSON serializable")
+    ...
+    >>> json.dumps(2 + 1j, default=encode_complex)
+    '[2.0, 1.0]'
+    >>> json.JSONEncoder(default=encode_complex).encode(2 + 1j)
+    '[2.0, 1.0]'
+    >>> ''.join(json.JSONEncoder(default=encode_complex).iterencode(2 + 1j))
+    '[2.0, 1.0]'
+
+
+Using simplejson.tool from the shell to validate and pretty-print::
+
+    $ echo '{"json":"obj"}' | python -m simplejson.tool
+    {
+        "json": "obj"
+    }
+    $ echo '{ 1.2:3.4}' | python -m simplejson.tool
+    Expecting property name: line 1 column 2 (char 2)
+"""
+__version__ = '2.0.9'
+__all__ = [
+    'dump', 'dumps', 'load', 'loads',
+    'JSONDecoder', 'JSONEncoder',
+]
+
+__author__ = 'Bob Ippolito <bob@redivi.com>'
+
+from decoder import JSONDecoder
+from encoder import JSONEncoder
+
+_default_encoder = JSONEncoder(
+    skipkeys=False,
+    ensure_ascii=True,
+    check_circular=True,
+    allow_nan=True,
+    indent=None,
+    separators=None,
+    encoding='utf-8',
+    default=None,
+)
+
+def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
+        allow_nan=True, cls=None, indent=None, separators=None,
+        encoding='utf-8', default=None, **kw):
+    """Serialize ``obj`` as a JSON formatted stream to ``fp`` (a
+    ``.write()``-supporting file-like object).
+
+    If ``skipkeys`` is true then ``dict`` keys that are not basic types
+    (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
+    will be skipped instead of raising a ``TypeError``.
+
+    If ``ensure_ascii`` is false, then the some chunks written to ``fp``
+    may be ``unicode`` instances, subject to normal Python ``str`` to
+    ``unicode`` coercion rules. Unless ``fp.write()`` explicitly
+    understands ``unicode`` (as in ``codecs.getwriter()``) this is likely
+    to cause an error.
+
+    If ``check_circular`` is false, then the circular reference check
+    for container types will be skipped and a circular reference will
+    result in an ``OverflowError`` (or worse).
+
+    If ``allow_nan`` is false, then it will be a ``ValueError`` to
+    serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``)
+    in strict compliance of the JSON specification, instead of using the
+    JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
+
+    If ``indent`` is a non-negative integer, then JSON array elements and object
+    members will be pretty-printed with that indent level. An indent level
+    of 0 will only insert newlines. ``None`` is the most compact representation.
+
+    If ``separators`` is an ``(item_separator, dict_separator)`` tuple
+    then it will be used instead of the default ``(', ', ': ')`` separators.
+    ``(',', ':')`` is the most compact JSON representation.
+
+    ``encoding`` is the character encoding for str instances, default is UTF-8.
+
+    ``default(obj)`` is a function that should return a serializable version
+    of obj or raise TypeError. The default simply raises TypeError.
+
+    To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
+    ``.default()`` method to serialize additional types), specify it with
+    the ``cls`` kwarg.
+
+    """
+    # cached encoder
+    if (not skipkeys and ensure_ascii and
+        check_circular and allow_nan and
+        cls is None and indent is None and separators is None and
+        encoding == 'utf-8' and default is None and not kw):
+        iterable = _default_encoder.iterencode(obj)
+    else:
+        if cls is None:
+            cls = JSONEncoder
+        iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii,
+            check_circular=check_circular, allow_nan=allow_nan, indent=indent,
+            separators=separators, encoding=encoding,
+            default=default, **kw).iterencode(obj)
+    # could accelerate with writelines in some versions of Python, at
+    # a debuggability cost
+    for chunk in iterable:
+        fp.write(chunk)
+
+
+def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
+        allow_nan=True, cls=None, indent=None, separators=None,
+        encoding='utf-8', default=None, **kw):
+    """Serialize ``obj`` to a JSON formatted ``str``.
+
+    If ``skipkeys`` is false then ``dict`` keys that are not basic types
+    (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
+    will be skipped instead of raising a ``TypeError``.
+
+    If ``ensure_ascii`` is false, then the return value will be a
+    ``unicode`` instance subject to normal Python ``str`` to ``unicode``
+    coercion rules instead of being escaped to an ASCII ``str``.
+
+    If ``check_circular`` is false, then the circular reference check
+    for container types will be skipped and a circular reference will
+    result in an ``OverflowError`` (or worse).
+
+    If ``allow_nan`` is false, then it will be a ``ValueError`` to
+    serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in
+    strict compliance of the JSON specification, instead of using the
+    JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
+
+    If ``indent`` is a non-negative integer, then JSON array elements and
+    object members will be pretty-printed with that indent level. An indent
+    level of 0 will only insert newlines. ``None`` is the most compact
+    representation.
+
+    If ``separators`` is an ``(item_separator, dict_separator)`` tuple
+    then it will be used instead of the default ``(', ', ': ')`` separators.
+    ``(',', ':')`` is the most compact JSON representation.
+
+    ``encoding`` is the character encoding for str instances, default is UTF-8.
+
+    ``default(obj)`` is a function that should return a serializable version
+    of obj or raise TypeError. The default simply raises TypeError.
+
+    To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
+    ``.default()`` method to serialize additional types), specify it with
+    the ``cls`` kwarg.
+
+    """
+    # cached encoder
+    if (not skipkeys and ensure_ascii and
+        check_circular and allow_nan and
+        cls is None and indent is None and separators is None and
+        encoding == 'utf-8' and default is None and not kw):
+        return _default_encoder.encode(obj)
+    if cls is None:
+        cls = JSONEncoder
+    return cls(
+        skipkeys=skipkeys, ensure_ascii=ensure_ascii,
+        check_circular=check_circular, allow_nan=allow_nan, indent=indent,
+        separators=separators, encoding=encoding, default=default,
+        **kw).encode(obj)
+
+
+_default_decoder = JSONDecoder(encoding=None, object_hook=None)
+
+
+def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None,
+        parse_int=None, parse_constant=None, **kw):
+    """Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
+    a JSON document) to a Python object.
+
+    If the contents of ``fp`` is encoded with an ASCII based encoding other
+    than utf-8 (e.g. latin-1), then an appropriate ``encoding`` name must
+    be specified. Encodings that are not ASCII based (such as UCS-2) are
+    not allowed, and should be wrapped with
+    ``codecs.getreader(fp)(encoding)``, or simply decoded to a ``unicode``
+    object and passed to ``loads()``
+
+    ``object_hook`` is an optional function that will be called with the
+    result of any object literal decode (a ``dict``). The return value of
+    ``object_hook`` will be used instead of the ``dict``. This feature
+    can be used to implement custom decoders (e.g. JSON-RPC class hinting).
+
+    To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
+    kwarg.
+
+    """
+    return loads(fp.read(),
+        encoding=encoding, cls=cls, object_hook=object_hook,
+        parse_float=parse_float, parse_int=parse_int,
+        parse_constant=parse_constant, **kw)
+
+
+def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None,
+        parse_int=None, parse_constant=None, **kw):
+    """Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON
+    document) to a Python object.
+
+    If ``s`` is a ``str`` instance and is encoded with an ASCII based encoding
+    other than utf-8 (e.g. latin-1) then an appropriate ``encoding`` name
+    must be specified. Encodings that are not ASCII based (such as UCS-2)
+    are not allowed and should be decoded to ``unicode`` first.
+
+    ``object_hook`` is an optional function that will be called with the
+    result of any object literal decode (a ``dict``). The return value of
+    ``object_hook`` will be used instead of the ``dict``. This feature
+    can be used to implement custom decoders (e.g. JSON-RPC class hinting).
+
+    ``parse_float``, if specified, will be called with the string
+    of every JSON float to be decoded. By default this is equivalent to
+    float(num_str). This can be used to use another datatype or parser
+    for JSON floats (e.g. decimal.Decimal).
+
+    ``parse_int``, if specified, will be called with the string
+    of every JSON int to be decoded. By default this is equivalent to
+    int(num_str). This can be used to use another datatype or parser
+    for JSON integers (e.g. float).
+
+    ``parse_constant``, if specified, will be called with one of the
+    following strings: -Infinity, Infinity, NaN, null, true, false.
+    This can be used to raise an exception if invalid JSON numbers
+    are encountered.
+
+    To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
+    kwarg.
+
+    """
+    if (cls is None and encoding is None and object_hook is None and
+            parse_int is None and parse_float is None and
+            parse_constant is None and not kw):
+        return _default_decoder.decode(s)
+    if cls is None:
+        cls = JSONDecoder
+    if object_hook is not None:
+        kw['object_hook'] = object_hook
+    if parse_float is not None:
+        kw['parse_float'] = parse_float
+    if parse_int is not None:
+        kw['parse_int'] = parse_int
+    if parse_constant is not None:
+        kw['parse_constant'] = parse_constant
+    return cls(encoding=encoding, **kw).decode(s)
diff --git a/ovsdb/simplejson/_speedups.c b/ovsdb/simplejson/_speedups.c
new file mode 100644 (file)
index 0000000..23b5f4a
--- /dev/null
@@ -0,0 +1,2329 @@
+#include "Python.h"
+#include "structmember.h"
+#if PY_VERSION_HEX < 0x02060000 && !defined(Py_TYPE)
+#define Py_TYPE(ob)     (((PyObject*)(ob))->ob_type)
+#endif
+#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
+typedef int Py_ssize_t;
+#define PY_SSIZE_T_MAX INT_MAX
+#define PY_SSIZE_T_MIN INT_MIN
+#define PyInt_FromSsize_t PyInt_FromLong
+#define PyInt_AsSsize_t PyInt_AsLong
+#endif
+#ifndef Py_IS_FINITE
+#define Py_IS_FINITE(X) (!Py_IS_INFINITY(X) && !Py_IS_NAN(X))
+#endif
+
+#ifdef __GNUC__
+#define UNUSED __attribute__((__unused__))
+#else
+#define UNUSED
+#endif
+
+#define DEFAULT_ENCODING "utf-8"
+
+#define PyScanner_Check(op) PyObject_TypeCheck(op, &PyScannerType)
+#define PyScanner_CheckExact(op) (Py_TYPE(op) == &PyScannerType)
+#define PyEncoder_Check(op) PyObject_TypeCheck(op, &PyEncoderType)
+#define PyEncoder_CheckExact(op) (Py_TYPE(op) == &PyEncoderType)
+
+static PyTypeObject PyScannerType;
+static PyTypeObject PyEncoderType;
+
+typedef struct _PyScannerObject {
+    PyObject_HEAD
+    PyObject *encoding;
+    PyObject *strict;
+    PyObject *object_hook;
+    PyObject *parse_float;
+    PyObject *parse_int;
+    PyObject *parse_constant;
+} PyScannerObject;
+
+static PyMemberDef scanner_members[] = {
+    {"encoding", T_OBJECT, offsetof(PyScannerObject, encoding), READONLY, "encoding"},
+    {"strict", T_OBJECT, offsetof(PyScannerObject, strict), READONLY, "strict"},
+    {"object_hook", T_OBJECT, offsetof(PyScannerObject, object_hook), READONLY, "object_hook"},
+    {"parse_float", T_OBJECT, offsetof(PyScannerObject, parse_float), READONLY, "parse_float"},
+    {"parse_int", T_OBJECT, offsetof(PyScannerObject, parse_int), READONLY, "parse_int"},
+    {"parse_constant", T_OBJECT, offsetof(PyScannerObject, parse_constant), READONLY, "parse_constant"},
+    {NULL}
+};
+
+typedef struct _PyEncoderObject {
+    PyObject_HEAD
+    PyObject *markers;
+    PyObject *defaultfn;
+    PyObject *encoder;
+    PyObject *indent;
+    PyObject *key_separator;
+    PyObject *item_separator;
+    PyObject *sort_keys;
+    PyObject *skipkeys;
+    int fast_encode;
+    int allow_nan;
+} PyEncoderObject;
+
+static PyMemberDef encoder_members[] = {
+    {"markers", T_OBJECT, offsetof(PyEncoderObject, markers), READONLY, "markers"},
+    {"default", T_OBJECT, offsetof(PyEncoderObject, defaultfn), READONLY, "default"},
+    {"encoder", T_OBJECT, offsetof(PyEncoderObject, encoder), READONLY, "encoder"},
+    {"indent", T_OBJECT, offsetof(PyEncoderObject, indent), READONLY, "indent"},
+    {"key_separator", T_OBJECT, offsetof(PyEncoderObject, key_separator), READONLY, "key_separator"},
+    {"item_separator", T_OBJECT, offsetof(PyEncoderObject, item_separator), READONLY, "item_separator"},
+    {"sort_keys", T_OBJECT, offsetof(PyEncoderObject, sort_keys), READONLY, "sort_keys"},
+    {"skipkeys", T_OBJECT, offsetof(PyEncoderObject, skipkeys), READONLY, "skipkeys"},
+    {NULL}
+};
+
+static Py_ssize_t
+ascii_escape_char(Py_UNICODE c, char *output, Py_ssize_t chars);
+static PyObject *
+ascii_escape_unicode(PyObject *pystr);
+static PyObject *
+ascii_escape_str(PyObject *pystr);
+static PyObject *
+py_encode_basestring_ascii(PyObject* self UNUSED, PyObject *pystr);
+void init_speedups(void);
+static PyObject *
+scan_once_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr);
+static PyObject *
+scan_once_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr);
+static PyObject *
+_build_rval_index_tuple(PyObject *rval, Py_ssize_t idx);
+static PyObject *
+scanner_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
+static int
+scanner_init(PyObject *self, PyObject *args, PyObject *kwds);
+static void
+scanner_dealloc(PyObject *self);
+static int
+scanner_clear(PyObject *self);
+static PyObject *
+encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
+static int
+encoder_init(PyObject *self, PyObject *args, PyObject *kwds);
+static void
+encoder_dealloc(PyObject *self);
+static int
+encoder_clear(PyObject *self);
+static int
+encoder_listencode_list(PyEncoderObject *s, PyObject *rval, PyObject *seq, Py_ssize_t indent_level);
+static int
+encoder_listencode_obj(PyEncoderObject *s, PyObject *rval, PyObject *obj, Py_ssize_t indent_level);
+static int
+encoder_listencode_dict(PyEncoderObject *s, PyObject *rval, PyObject *dct, Py_ssize_t indent_level);
+static PyObject *
+_encoded_const(PyObject *const);
+static void
+raise_errmsg(char *msg, PyObject *s, Py_ssize_t end);
+static PyObject *
+encoder_encode_string(PyEncoderObject *s, PyObject *obj);
+static int
+_convertPyInt_AsSsize_t(PyObject *o, Py_ssize_t *size_ptr);
+static PyObject *
+_convertPyInt_FromSsize_t(Py_ssize_t *size_ptr);
+static PyObject *
+encoder_encode_float(PyEncoderObject *s, PyObject *obj);
+
+#define S_CHAR(c) (c >= ' ' && c <= '~' && c != '\\' && c != '"')
+#define IS_WHITESPACE(c) (((c) == ' ') || ((c) == '\t') || ((c) == '\n') || ((c) == '\r'))
+
+#define MIN_EXPANSION 6
+#ifdef Py_UNICODE_WIDE
+#define MAX_EXPANSION (2 * MIN_EXPANSION)
+#else
+#define MAX_EXPANSION MIN_EXPANSION
+#endif
+
+static int
+_convertPyInt_AsSsize_t(PyObject *o, Py_ssize_t *size_ptr)
+{
+    /* PyObject to Py_ssize_t converter */
+    *size_ptr = PyInt_AsSsize_t(o);
+    if (*size_ptr == -1 && PyErr_Occurred());
+        return 1;
+    return 0;
+}
+
+static PyObject *
+_convertPyInt_FromSsize_t(Py_ssize_t *size_ptr)
+{
+    /* Py_ssize_t to PyObject converter */
+    return PyInt_FromSsize_t(*size_ptr);
+}
+
+static Py_ssize_t
+ascii_escape_char(Py_UNICODE c, char *output, Py_ssize_t chars)
+{
+    /* Escape unicode code point c to ASCII escape sequences
+    in char *output. output must have at least 12 bytes unused to
+    accommodate an escaped surrogate pair "\uXXXX\uXXXX" */
+    output[chars++] = '\\';
+    switch (c) {
+        case '\\': output[chars++] = (char)c; break;
+        case '"': output[chars++] = (char)c; break;
+        case '\b': output[chars++] = 'b'; break;
+        case '\f': output[chars++] = 'f'; break;
+        case '\n': output[chars++] = 'n'; break;
+        case '\r': output[chars++] = 'r'; break;
+        case '\t': output[chars++] = 't'; break;
+        default:
+#ifdef Py_UNICODE_WIDE
+            if (c >= 0x10000) {
+                /* UTF-16 surrogate pair */
+                Py_UNICODE v = c - 0x10000;
+                c = 0xd800 | ((v >> 10) & 0x3ff);
+                output[chars++] = 'u';
+                output[chars++] = "0123456789abcdef"[(c >> 12) & 0xf];
+                output[chars++] = "0123456789abcdef"[(c >>  8) & 0xf];
+                output[chars++] = "0123456789abcdef"[(c >>  4) & 0xf];
+                output[chars++] = "0123456789abcdef"[(c      ) & 0xf];
+                c = 0xdc00 | (v & 0x3ff);
+                output[chars++] = '\\';
+            }
+#endif
+            output[chars++] = 'u';
+            output[chars++] = "0123456789abcdef"[(c >> 12) & 0xf];
+            output[chars++] = "0123456789abcdef"[(c >>  8) & 0xf];
+            output[chars++] = "0123456789abcdef"[(c >>  4) & 0xf];
+            output[chars++] = "0123456789abcdef"[(c      ) & 0xf];
+    }
+    return chars;
+}
+
+static PyObject *
+ascii_escape_unicode(PyObject *pystr)
+{
+    /* Take a PyUnicode pystr and return a new ASCII-only escaped PyString */
+    Py_ssize_t i;
+    Py_ssize_t input_chars;
+    Py_ssize_t output_size;
+    Py_ssize_t max_output_size;
+    Py_ssize_t chars;
+    PyObject *rval;
+    char *output;
+    Py_UNICODE *input_unicode;
+
+    input_chars = PyUnicode_GET_SIZE(pystr);
+    input_unicode = PyUnicode_AS_UNICODE(pystr);
+
+    /* One char input can be up to 6 chars output, estimate 4 of these */
+    output_size = 2 + (MIN_EXPANSION * 4) + input_chars;
+    max_output_size = 2 + (input_chars * MAX_EXPANSION);
+    rval = PyString_FromStringAndSize(NULL, output_size);
+    if (rval == NULL) {
+        return NULL;
+    }
+    output = PyString_AS_STRING(rval);
+    chars = 0;
+    output[chars++] = '"';
+    for (i = 0; i < input_chars; i++) {
+        Py_UNICODE c = input_unicode[i];
+        if (S_CHAR(c)) {
+            output[chars++] = (char)c;
+        }
+        else {
+            chars = ascii_escape_char(c, output, chars);
+        }
+        if (output_size - chars < (1 + MAX_EXPANSION)) {
+            /* There's more than four, so let's resize by a lot */
+            Py_ssize_t new_output_size = output_size * 2;
+            /* This is an upper bound */
+            if (new_output_size > max_output_size) {
+                new_output_size = max_output_size;
+            }
+            /* Make sure that the output size changed before resizing */
+            if (new_output_size != output_size) {
+                output_size = new_output_size;
+                if (_PyString_Resize(&rval, output_size) == -1) {
+                    return NULL;
+                }
+                output = PyString_AS_STRING(rval);
+            }
+        }
+    }
+    output[chars++] = '"';
+    if (_PyString_Resize(&rval, chars) == -1) {
+        return NULL;
+    }
+    return rval;
+}
+
+static PyObject *
+ascii_escape_str(PyObject *pystr)
+{
+    /* Take a PyString pystr and return a new ASCII-only escaped PyString */
+    Py_ssize_t i;
+    Py_ssize_t input_chars;
+    Py_ssize_t output_size;
+    Py_ssize_t chars;
+    PyObject *rval;
+    char *output;
+    char *input_str;
+
+    input_chars = PyString_GET_SIZE(pystr);
+    input_str = PyString_AS_STRING(pystr);
+
+    /* Fast path for a string that's already ASCII */
+    for (i = 0; i < input_chars; i++) {
+        Py_UNICODE c = (Py_UNICODE)(unsigned char)input_str[i];
+        if (!S_CHAR(c)) {
+            /* If we have to escape something, scan the string for unicode */
+            Py_ssize_t j;
+            for (j = i; j < input_chars; j++) {
+                c = (Py_UNICODE)(unsigned char)input_str[j];
+                if (c > 0x7f) {
+                    /* We hit a non-ASCII character, bail to unicode mode */
+                    PyObject *uni;
+                    uni = PyUnicode_DecodeUTF8(input_str, input_chars, "strict");
+                    if (uni == NULL) {
+                        return NULL;
+                    }
+                    rval = ascii_escape_unicode(uni);
+                    Py_DECREF(uni);
+                    return rval;
+                }
+            }
+            break;
+        }
+    }
+
+    if (i == input_chars) {
+        /* Input is already ASCII */
+        output_size = 2 + input_chars;
+    }
+    else {
+        /* One char input can be up to 6 chars output, estimate 4 of these */
+        output_size = 2 + (MIN_EXPANSION * 4) + input_chars;
+    }
+    rval = PyString_FromStringAndSize(NULL, output_size);
+    if (rval == NULL) {
+        return NULL;
+    }
+    output = PyString_AS_STRING(rval);
+    output[0] = '"';
+
+    /* We know that everything up to i is ASCII already */
+    chars = i + 1;
+    memcpy(&output[1], input_str, i);
+
+    for (; i < input_chars; i++) {
+        Py_UNICODE c = (Py_UNICODE)(unsigned char)input_str[i];
+        if (S_CHAR(c)) {
+            output[chars++] = (char)c;
+        }
+        else {
+            chars = ascii_escape_char(c, output, chars);
+        }
+        /* An ASCII char can't possibly expand to a surrogate! */
+        if (output_size - chars < (1 + MIN_EXPANSION)) {
+            /* There's more than four, so let's resize by a lot */
+            output_size *= 2;
+            if (output_size > 2 + (input_chars * MIN_EXPANSION)) {
+                output_size = 2 + (input_chars * MIN_EXPANSION);
+            }
+            if (_PyString_Resize(&rval, output_size) == -1) {
+                return NULL;
+            }
+            output = PyString_AS_STRING(rval);
+        }
+    }
+    output[chars++] = '"';
+    if (_PyString_Resize(&rval, chars) == -1) {
+        return NULL;
+    }
+    return rval;
+}
+
+static void
+raise_errmsg(char *msg, PyObject *s, Py_ssize_t end)
+{
+    /* Use the Python function simplejson.decoder.errmsg to raise a nice
+    looking ValueError exception */
+    static PyObject *errmsg_fn = NULL;
+    PyObject *pymsg;
+    if (errmsg_fn == NULL) {
+        PyObject *decoder = PyImport_ImportModule("simplejson.decoder");
+        if (decoder == NULL)
+            return;
+        errmsg_fn = PyObject_GetAttrString(decoder, "errmsg");
+        Py_DECREF(decoder);
+        if (errmsg_fn == NULL)
+            return;
+    }
+    pymsg = PyObject_CallFunction(errmsg_fn, "(zOO&)", msg, s, _convertPyInt_FromSsize_t, &end);
+    if (pymsg) {
+        PyErr_SetObject(PyExc_ValueError, pymsg);
+        Py_DECREF(pymsg);
+    }
+}
+
+static PyObject *
+join_list_unicode(PyObject *lst)
+{
+    /* return u''.join(lst) */
+    static PyObject *joinfn = NULL;
+    if (joinfn == NULL) {
+        PyObject *ustr = PyUnicode_FromUnicode(NULL, 0);
+        if (ustr == NULL)
+            return NULL;
+
+        joinfn = PyObject_GetAttrString(ustr, "join");
+        Py_DECREF(ustr);
+        if (joinfn == NULL)
+            return NULL;
+    }
+    return PyObject_CallFunctionObjArgs(joinfn, lst, NULL);
+}
+
+static PyObject *
+join_list_string(PyObject *lst)
+{
+    /* return ''.join(lst) */
+    static PyObject *joinfn = NULL;
+    if (joinfn == NULL) {
+        PyObject *ustr = PyString_FromStringAndSize(NULL, 0);
+        if (ustr == NULL)
+            return NULL;
+
+        joinfn = PyObject_GetAttrString(ustr, "join");
+        Py_DECREF(ustr);
+        if (joinfn == NULL)
+            return NULL;
+    }
+    return PyObject_CallFunctionObjArgs(joinfn, lst, NULL);
+}
+
+static PyObject *
+_build_rval_index_tuple(PyObject *rval, Py_ssize_t idx) {
+    /* return (rval, idx) tuple, stealing reference to rval */
+    PyObject *tpl;
+    PyObject *pyidx;
+    /*
+    steal a reference to rval, returns (rval, idx)
+    */
+    if (rval == NULL) {
+        return NULL;
+    }
+    pyidx = PyInt_FromSsize_t(idx);
+    if (pyidx == NULL) {
+        Py_DECREF(rval);
+        return NULL;
+    }
+    tpl = PyTuple_New(2);
+    if (tpl == NULL) {
+        Py_DECREF(pyidx);
+        Py_DECREF(rval);
+        return NULL;
+    }
+    PyTuple_SET_ITEM(tpl, 0, rval);
+    PyTuple_SET_ITEM(tpl, 1, pyidx);
+    return tpl;
+}
+
+static PyObject *
+scanstring_str(PyObject *pystr, Py_ssize_t end, char *encoding, int strict, Py_ssize_t *next_end_ptr)
+{
+    /* Read the JSON string from PyString pystr.
+    end is the index of the first character after the quote.
+    encoding is the encoding of pystr (must be an ASCII superset)
+    if strict is zero then literal control characters are allowed
+    *next_end_ptr is a return-by-reference index of the character
+        after the end quote
+
+    Return value is a new PyString (if ASCII-only) or PyUnicode
+    */
+    PyObject *rval;
+    Py_ssize_t len = PyString_GET_SIZE(pystr);
+    Py_ssize_t begin = end - 1;
+    Py_ssize_t next = begin;
+    int has_unicode = 0;
+    char *buf = PyString_AS_STRING(pystr);
+    PyObject *chunks = PyList_New(0);
+    if (chunks == NULL) {
+        goto bail;
+    }
+    if (end < 0 || len <= end) {
+        PyErr_SetString(PyExc_ValueError, "end is out of bounds");
+        goto bail;
+    }
+    while (1) {
+        /* Find the end of the string or the next escape */
+        Py_UNICODE c = 0;
+        PyObject *chunk = NULL;
+        for (next = end; next < len; next++) {
+            c = (unsigned char)buf[next];
+            if (c == '"' || c == '\\') {
+                break;
+            }
+            else if (strict && c <= 0x1f) {
+                raise_errmsg("Invalid control character at", pystr, next);
+                goto bail;
+            }
+            else if (c > 0x7f) {
+                has_unicode = 1;
+            }
+        }
+        if (!(c == '"' || c == '\\')) {
+            raise_errmsg("Unterminated string starting at", pystr, begin);
+            goto bail;
+        }
+        /* Pick up this chunk if it's not zero length */
+        if (next != end) {
+            PyObject *strchunk = PyString_FromStringAndSize(&buf[end], next - end);
+            if (strchunk == NULL) {
+                goto bail;
+            }
+            if (has_unicode) {
+                chunk = PyUnicode_FromEncodedObject(strchunk, encoding, NULL);
+                Py_DECREF(strchunk);
+                if (chunk == NULL) {
+                    goto bail;
+                }
+            }
+            else {
+                chunk = strchunk;
+            }
+            if (PyList_Append(chunks, chunk)) {
+                Py_DECREF(chunk);
+                goto bail;
+            }
+            Py_DECREF(chunk);
+        }
+        next++;
+        if (c == '"') {
+            end = next;
+            break;
+        }
+        if (next == len) {
+            raise_errmsg("Unterminated string starting at", pystr, begin);
+            goto bail;
+        }
+        c = buf[next];
+        if (c != 'u') {
+            /* Non-unicode backslash escapes */
+            end = next + 1;
+            switch (c) {
+                case '"': break;
+                case '\\': break;
+                case '/': break;
+                case 'b': c = '\b'; break;
+                case 'f': c = '\f'; break;
+                case 'n': c = '\n'; break;
+                case 'r': c = '\r'; break;
+                case 't': c = '\t'; break;
+                default: c = 0;
+            }
+            if (c == 0) {
+                raise_errmsg("Invalid \\escape", pystr, end - 2);
+                goto bail;
+            }
+        }
+        else {
+            c = 0;
+            next++;
+            end = next + 4;
+            if (end >= len) {
+                raise_errmsg("Invalid \\uXXXX escape", pystr, next - 1);
+                goto bail;
+            }
+            /* Decode 4 hex digits */
+            for (; next < end; next++) {
+                Py_UNICODE digit = buf[next];
+                c <<= 4;
+                switch (digit) {
+                    case '0': case '1': case '2': case '3': case '4':
+                    case '5': case '6': case '7': case '8': case '9':
+                        c |= (digit - '0'); break;
+                    case 'a': case 'b': case 'c': case 'd': case 'e':
+                    case 'f':
+                        c |= (digit - 'a' + 10); break;
+                    case 'A': case 'B': case 'C': case 'D': case 'E':
+                    case 'F':
+                        c |= (digit - 'A' + 10); break;
+                    default:
+                        raise_errmsg("Invalid \\uXXXX escape", pystr, end - 5);
+                        goto bail;
+                }
+            }
+#ifdef Py_UNICODE_WIDE
+            /* Surrogate pair */
+            if ((c & 0xfc00) == 0xd800) {
+                Py_UNICODE c2 = 0;
+                if (end + 6 >= len) {
+                    raise_errmsg("Unpaired high surrogate", pystr, end - 5);
+                    goto bail;
+                }
+                if (buf[next++] != '\\' || buf[next++] != 'u') {
+                    raise_errmsg("Unpaired high surrogate", pystr, end - 5);
+                    goto bail;
+                }
+                end += 6;
+                /* Decode 4 hex digits */
+                for (; next < end; next++) {
+                    c2 <<= 4;
+                    Py_UNICODE digit = buf[next];
+                    switch (digit) {
+                        case '0': case '1': case '2': case '3': case '4':
+                        case '5': case '6': case '7': case '8': case '9':
+                            c2 |= (digit - '0'); break;
+                        case 'a': case 'b': case 'c': case 'd': case 'e':
+                        case 'f':
+                            c2 |= (digit - 'a' + 10); break;
+                        case 'A': case 'B': case 'C': case 'D': case 'E':
+                        case 'F':
+                            c2 |= (digit - 'A' + 10); break;
+                        default:
+                            raise_errmsg("Invalid \\uXXXX escape", pystr, end - 5);
+                            goto bail;
+                    }
+                }
+                if ((c2 & 0xfc00) != 0xdc00) {
+                    raise_errmsg("Unpaired high surrogate", pystr, end - 5);
+                    goto bail;
+                }
+                c = 0x10000 + (((c - 0xd800) << 10) | (c2 - 0xdc00));
+            }
+            else if ((c & 0xfc00) == 0xdc00) {
+                raise_errmsg("Unpaired low surrogate", pystr, end - 5);
+                goto bail;
+            }
+#endif
+        }
+        if (c > 0x7f) {
+            has_unicode = 1;
+        }
+        if (has_unicode) {
+            chunk = PyUnicode_FromUnicode(&c, 1);
+            if (chunk == NULL) {
+                goto bail;
+            }
+        }
+        else {
+            char c_char = Py_CHARMASK(c);
+            chunk = PyString_FromStringAndSize(&c_char, 1);
+            if (chunk == NULL) {
+                goto bail;
+            }
+        }
+        if (PyList_Append(chunks, chunk)) {
+            Py_DECREF(chunk);
+            goto bail;
+        }
+        Py_DECREF(chunk);
+    }
+
+    rval = join_list_string(chunks);
+    if (rval == NULL) {
+        goto bail;
+    }
+    Py_CLEAR(chunks);
+    *next_end_ptr = end;
+    return rval;
+bail:
+    *next_end_ptr = -1;
+    Py_XDECREF(chunks);
+    return NULL;
+}
+
+
+static PyObject *
+scanstring_unicode(PyObject *pystr, Py_ssize_t end, int strict, Py_ssize_t *next_end_ptr)
+{
+    /* Read the JSON string from PyUnicode pystr.
+    end is the index of the first character after the quote.
+    if strict is zero then literal control characters are allowed
+    *next_end_ptr is a return-by-reference index of the character
+        after the end quote
+
+    Return value is a new PyUnicode
+    */
+    PyObject *rval;
+    Py_ssize_t len = PyUnicode_GET_SIZE(pystr);
+    Py_ssize_t begin = end - 1;
+    Py_ssize_t next = begin;
+    const Py_UNICODE *buf = PyUnicode_AS_UNICODE(pystr);
+    PyObject *chunks = PyList_New(0);
+    if (chunks == NULL) {
+        goto bail;
+    }
+    if (end < 0 || len <= end) {
+        PyErr_SetString(PyExc_ValueError, "end is out of bounds");
+        goto bail;
+    }
+    while (1) {
+        /* Find the end of the string or the next escape */
+        Py_UNICODE c = 0;
+        PyObject *chunk = NULL;
+        for (next = end; next < len; next++) {
+            c = buf[next];
+            if (c == '"' || c == '\\') {
+                break;
+            }
+            else if (strict && c <= 0x1f) {
+                raise_errmsg("Invalid control character at", pystr, next);
+                goto bail;
+            }
+        }
+        if (!(c == '"' || c == '\\')) {
+            raise_errmsg("Unterminated string starting at", pystr, begin);
+            goto bail;
+        }
+        /* Pick up this chunk if it's not zero length */
+        if (next != end) {
+            chunk = PyUnicode_FromUnicode(&buf[end], next - end);
+            if (chunk == NULL) {
+                goto bail;
+            }
+            if (PyList_Append(chunks, chunk)) {
+                Py_DECREF(chunk);
+                goto bail;
+            }
+            Py_DECREF(chunk);
+        }
+        next++;
+        if (c == '"') {
+            end = next;
+            break;
+        }
+        if (next == len) {
+            raise_errmsg("Unterminated string starting at", pystr, begin);
+            goto bail;
+        }
+        c = buf[next];
+        if (c != 'u') {
+            /* Non-unicode backslash escapes */
+            end = next + 1;
+            switch (c) {
+                case '"': break;
+                case '\\': break;
+                case '/': break;
+                case 'b': c = '\b'; break;
+                case 'f': c = '\f'; break;
+                case 'n': c = '\n'; break;
+                case 'r': c = '\r'; break;
+                case 't': c = '\t'; break;
+                default: c = 0;
+            }
+            if (c == 0) {
+                raise_errmsg("Invalid \\escape", pystr, end - 2);
+                goto bail;
+            }
+        }
+        else {
+            c = 0;
+            next++;
+            end = next + 4;
+            if (end >= len) {
+                raise_errmsg("Invalid \\uXXXX escape", pystr, next - 1);
+                goto bail;
+            }
+            /* Decode 4 hex digits */
+            for (; next < end; next++) {
+                Py_UNICODE digit = buf[next];
+                c <<= 4;
+                switch (digit) {
+                    case '0': case '1': case '2': case '3': case '4':
+                    case '5': case '6': case '7': case '8': case '9':
+                        c |= (digit - '0'); break;
+                    case 'a': case 'b': case 'c': case 'd': case 'e':
+                    case 'f':
+                        c |= (digit - 'a' + 10); break;
+                    case 'A': case 'B': case 'C': case 'D': case 'E':
+                    case 'F':
+                        c |= (digit - 'A' + 10); break;
+                    default:
+                        raise_errmsg("Invalid \\uXXXX escape", pystr, end - 5);
+                        goto bail;
+                }
+            }
+#ifdef Py_UNICODE_WIDE
+            /* Surrogate pair */
+            if ((c & 0xfc00) == 0xd800) {
+                Py_UNICODE c2 = 0;
+                if (end + 6 >= len) {
+                    raise_errmsg("Unpaired high surrogate", pystr, end - 5);
+                    goto bail;
+                }
+                if (buf[next++] != '\\' || buf[next++] != 'u') {
+                    raise_errmsg("Unpaired high surrogate", pystr, end - 5);
+                    goto bail;
+                }
+                end += 6;
+                /* Decode 4 hex digits */
+                for (; next < end; next++) {
+                    c2 <<= 4;
+                    Py_UNICODE digit = buf[next];
+                    switch (digit) {
+                        case '0': case '1': case '2': case '3': case '4':
+                        case '5': case '6': case '7': case '8': case '9':
+                            c2 |= (digit - '0'); break;
+                        case 'a': case 'b': case 'c': case 'd': case 'e':
+                        case 'f':
+                            c2 |= (digit - 'a' + 10); break;
+                        case 'A': case 'B': case 'C': case 'D': case 'E':
+                        case 'F':
+                            c2 |= (digit - 'A' + 10); break;
+                        default:
+                            raise_errmsg("Invalid \\uXXXX escape", pystr, end - 5);
+                            goto bail;
+                    }
+                }
+                if ((c2 & 0xfc00) != 0xdc00) {
+                    raise_errmsg("Unpaired high surrogate", pystr, end - 5);
+                    goto bail;
+                }
+                c = 0x10000 + (((c - 0xd800) << 10) | (c2 - 0xdc00));
+            }
+            else if ((c & 0xfc00) == 0xdc00) {
+                raise_errmsg("Unpaired low surrogate", pystr, end - 5);
+                goto bail;
+            }
+#endif
+        }
+        chunk = PyUnicode_FromUnicode(&c, 1);
+        if (chunk == NULL) {
+            goto bail;
+        }
+        if (PyList_Append(chunks, chunk)) {
+            Py_DECREF(chunk);
+            goto bail;
+        }
+        Py_DECREF(chunk);
+    }
+
+    rval = join_list_unicode(chunks);
+    if (rval == NULL) {
+        goto bail;
+    }
+    Py_DECREF(chunks);
+    *next_end_ptr = end;
+    return rval;
+bail:
+    *next_end_ptr = -1;
+    Py_XDECREF(chunks);
+    return NULL;
+}
+
+PyDoc_STRVAR(pydoc_scanstring,
+    "scanstring(basestring, end, encoding, strict=True) -> (str, end)\n"
+    "\n"
+    "Scan the string s for a JSON string. End is the index of the\n"
+    "character in s after the quote that started the JSON string.\n"
+    "Unescapes all valid JSON string escape sequences and raises ValueError\n"
+    "on attempt to decode an invalid string. If strict is False then literal\n"
+    "control characters are allowed in the string.\n"
+    "\n"
+    "Returns a tuple of the decoded string and the index of the character in s\n"
+    "after the end quote."
+);
+
+static PyObject *
+py_scanstring(PyObject* self UNUSED, PyObject *args)
+{
+    PyObject *pystr;
+    PyObject *rval;
+    Py_ssize_t end;
+    Py_ssize_t next_end = -1;
+    char *encoding = NULL;
+    int strict = 1;
+    if (!PyArg_ParseTuple(args, "OO&|zi:scanstring", &pystr, _convertPyInt_AsSsize_t, &end, &encoding, &strict)) {
+        return NULL;
+    }
+    if (encoding == NULL) {
+        encoding = DEFAULT_ENCODING;
+    }
+    if (PyString_Check(pystr)) {
+        rval = scanstring_str(pystr, end, encoding, strict, &next_end);
+    }
+    else if (PyUnicode_Check(pystr)) {
+        rval = scanstring_unicode(pystr, end, strict, &next_end);
+    }
+    else {
+        PyErr_Format(PyExc_TypeError,
+                     "first argument must be a string, not %.80s",
+                     Py_TYPE(pystr)->tp_name);
+        return NULL;
+    }
+    return _build_rval_index_tuple(rval, next_end);
+}
+
+PyDoc_STRVAR(pydoc_encode_basestring_ascii,
+    "encode_basestring_ascii(basestring) -> str\n"
+    "\n"
+    "Return an ASCII-only JSON representation of a Python string"
+);
+
+static PyObject *
+py_encode_basestring_ascii(PyObject* self UNUSED, PyObject *pystr)
+{
+    /* Return an ASCII-only JSON representation of a Python string */
+    /* METH_O */
+    if (PyString_Check(pystr)) {
+        return ascii_escape_str(pystr);
+    }
+    else if (PyUnicode_Check(pystr)) {
+        return ascii_escape_unicode(pystr);
+    }
+    else {
+        PyErr_Format(PyExc_TypeError,
+                     "first argument must be a string, not %.80s",
+                     Py_TYPE(pystr)->tp_name);
+        return NULL;
+    }
+}
+
+static void
+scanner_dealloc(PyObject *self)
+{
+    /* Deallocate scanner object */
+    scanner_clear(self);
+    Py_TYPE(self)->tp_free(self);
+}
+
+static int
+scanner_traverse(PyObject *self, visitproc visit, void *arg)
+{
+    PyScannerObject *s;
+    assert(PyScanner_Check(self));
+    s = (PyScannerObject *)self;
+    Py_VISIT(s->encoding);
+    Py_VISIT(s->strict);
+    Py_VISIT(s->object_hook);
+    Py_VISIT(s->parse_float);
+    Py_VISIT(s->parse_int);
+    Py_VISIT(s->parse_constant);
+    return 0;
+}
+
+static int
+scanner_clear(PyObject *self)
+{
+    PyScannerObject *s;
+    assert(PyScanner_Check(self));
+    s = (PyScannerObject *)self;
+    Py_CLEAR(s->encoding);
+    Py_CLEAR(s->strict);
+    Py_CLEAR(s->object_hook);
+    Py_CLEAR(s->parse_float);
+    Py_CLEAR(s->parse_int);
+    Py_CLEAR(s->parse_constant);
+    return 0;
+}
+
+static PyObject *
+_parse_object_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) {
+    /* Read a JSON object from PyString pystr.
+    idx is the index of the first character after the opening curly brace.
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the closing curly brace.
+
+    Returns a new PyObject (usually a dict, but object_hook can change that)
+    */
+    char *str = PyString_AS_STRING(pystr);
+    Py_ssize_t end_idx = PyString_GET_SIZE(pystr) - 1;
+    PyObject *rval = PyDict_New();
+    PyObject *key = NULL;
+    PyObject *val = NULL;
+    char *encoding = PyString_AS_STRING(s->encoding);
+    int strict = PyObject_IsTrue(s->strict);
+    Py_ssize_t next_idx;
+    if (rval == NULL)
+        return NULL;
+
+    /* skip whitespace after { */
+    while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+    /* only loop if the object is non-empty */
+    if (idx <= end_idx && str[idx] != '}') {
+        while (idx <= end_idx) {
+            /* read key */
+            if (str[idx] != '"') {
+                raise_errmsg("Expecting property name", pystr, idx);
+                goto bail;
+            }
+            key = scanstring_str(pystr, idx + 1, encoding, strict, &next_idx);
+            if (key == NULL)
+                goto bail;
+            idx = next_idx;
+
+            /* skip whitespace between key and : delimiter, read :, skip whitespace */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+            if (idx > end_idx || str[idx] != ':') {
+                raise_errmsg("Expecting : delimiter", pystr, idx);
+                goto bail;
+            }
+            idx++;
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+            /* read any JSON data type */
+            val = scan_once_str(s, pystr, idx, &next_idx);
+            if (val == NULL)
+                goto bail;
+
+            if (PyDict_SetItem(rval, key, val) == -1)
+                goto bail;
+
+            Py_CLEAR(key);
+            Py_CLEAR(val);
+            idx = next_idx;
+
+            /* skip whitespace before } or , */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+            /* bail if the object is closed or we didn't get the , delimiter */
+            if (idx > end_idx) break;
+            if (str[idx] == '}') {
+                break;
+            }
+            else if (str[idx] != ',') {
+                raise_errmsg("Expecting , delimiter", pystr, idx);
+                goto bail;
+            }
+            idx++;
+
+            /* skip whitespace after , delimiter */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+        }
+    }
+    /* verify that idx < end_idx, str[idx] should be '}' */
+    if (idx > end_idx || str[idx] != '}') {
+        raise_errmsg("Expecting object", pystr, end_idx);
+        goto bail;
+    }
+    /* if object_hook is not None: rval = object_hook(rval) */
+    if (s->object_hook != Py_None) {
+        val = PyObject_CallFunctionObjArgs(s->object_hook, rval, NULL);
+        if (val == NULL)
+            goto bail;
+        Py_DECREF(rval);
+        rval = val;
+        val = NULL;
+    }
+    *next_idx_ptr = idx + 1;
+    return rval;
+bail:
+    Py_XDECREF(key);
+    Py_XDECREF(val);
+    Py_DECREF(rval);
+    return NULL;
+}
+
+static PyObject *
+_parse_object_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) {
+    /* Read a JSON object from PyUnicode pystr.
+    idx is the index of the first character after the opening curly brace.
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the closing curly brace.
+
+    Returns a new PyObject (usually a dict, but object_hook can change that)
+    */
+    Py_UNICODE *str = PyUnicode_AS_UNICODE(pystr);
+    Py_ssize_t end_idx = PyUnicode_GET_SIZE(pystr) - 1;
+    PyObject *val = NULL;
+    PyObject *rval = PyDict_New();
+    PyObject *key = NULL;
+    int strict = PyObject_IsTrue(s->strict);
+    Py_ssize_t next_idx;
+    if (rval == NULL)
+        return NULL;
+
+    /* skip whitespace after { */
+    while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+    /* only loop if the object is non-empty */
+    if (idx <= end_idx && str[idx] != '}') {
+        while (idx <= end_idx) {
+            /* read key */
+            if (str[idx] != '"') {
+                raise_errmsg("Expecting property name", pystr, idx);
+                goto bail;
+            }
+            key = scanstring_unicode(pystr, idx + 1, strict, &next_idx);
+            if (key == NULL)
+                goto bail;
+            idx = next_idx;
+
+            /* skip whitespace between key and : delimiter, read :, skip whitespace */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+            if (idx > end_idx || str[idx] != ':') {
+                raise_errmsg("Expecting : delimiter", pystr, idx);
+                goto bail;
+            }
+            idx++;
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+            /* read any JSON term */
+            val = scan_once_unicode(s, pystr, idx, &next_idx);
+            if (val == NULL)
+                goto bail;
+
+            if (PyDict_SetItem(rval, key, val) == -1)
+                goto bail;
+
+            Py_CLEAR(key);
+            Py_CLEAR(val);
+            idx = next_idx;
+
+            /* skip whitespace before } or , */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+            /* bail if the object is closed or we didn't get the , delimiter */
+            if (idx > end_idx) break;
+            if (str[idx] == '}') {
+                break;
+            }
+            else if (str[idx] != ',') {
+                raise_errmsg("Expecting , delimiter", pystr, idx);
+                goto bail;
+            }
+            idx++;
+
+            /* skip whitespace after , delimiter */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+        }
+    }
+
+    /* verify that idx < end_idx, str[idx] should be '}' */
+    if (idx > end_idx || str[idx] != '}') {
+        raise_errmsg("Expecting object", pystr, end_idx);
+        goto bail;
+    }
+
+    /* if object_hook is not None: rval = object_hook(rval) */
+    if (s->object_hook != Py_None) {
+        val = PyObject_CallFunctionObjArgs(s->object_hook, rval, NULL);
+        if (val == NULL)
+            goto bail;
+        Py_DECREF(rval);
+        rval = val;
+        val = NULL;
+    }
+    *next_idx_ptr = idx + 1;
+    return rval;
+bail:
+    Py_XDECREF(key);
+    Py_XDECREF(val);
+    Py_DECREF(rval);
+    return NULL;
+}
+
+static PyObject *
+_parse_array_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) {
+    /* Read a JSON array from PyString pystr.
+    idx is the index of the first character after the opening brace.
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the closing brace.
+
+    Returns a new PyList
+    */
+    char *str = PyString_AS_STRING(pystr);
+    Py_ssize_t end_idx = PyString_GET_SIZE(pystr) - 1;
+    PyObject *val = NULL;
+    PyObject *rval = PyList_New(0);
+    Py_ssize_t next_idx;
+    if (rval == NULL)
+        return NULL;
+
+    /* skip whitespace after [ */
+    while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+    /* only loop if the array is non-empty */
+    if (idx <= end_idx && str[idx] != ']') {
+        while (idx <= end_idx) {
+
+            /* read any JSON term and de-tuplefy the (rval, idx) */
+            val = scan_once_str(s, pystr, idx, &next_idx);
+            if (val == NULL)
+                goto bail;
+
+            if (PyList_Append(rval, val) == -1)
+                goto bail;
+
+            Py_CLEAR(val);
+            idx = next_idx;
+
+            /* skip whitespace between term and , */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+            /* bail if the array is closed or we didn't get the , delimiter */
+            if (idx > end_idx) break;
+            if (str[idx] == ']') {
+                break;
+            }
+            else if (str[idx] != ',') {
+                raise_errmsg("Expecting , delimiter", pystr, idx);
+                goto bail;
+            }
+            idx++;
+
+            /* skip whitespace after , */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+        }
+    }
+
+    /* verify that idx < end_idx, str[idx] should be ']' */
+    if (idx > end_idx || str[idx] != ']') {
+        raise_errmsg("Expecting object", pystr, end_idx);
+        goto bail;
+    }
+    *next_idx_ptr = idx + 1;
+    return rval;
+bail:
+    Py_XDECREF(val);
+    Py_DECREF(rval);
+    return NULL;
+}
+
+static PyObject *
+_parse_array_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) {
+    /* Read a JSON array from PyString pystr.
+    idx is the index of the first character after the opening brace.
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the closing brace.
+
+    Returns a new PyList
+    */
+    Py_UNICODE *str = PyUnicode_AS_UNICODE(pystr);
+    Py_ssize_t end_idx = PyUnicode_GET_SIZE(pystr) - 1;
+    PyObject *val = NULL;
+    PyObject *rval = PyList_New(0);
+    Py_ssize_t next_idx;
+    if (rval == NULL)
+        return NULL;
+
+    /* skip whitespace after [ */
+    while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+    /* only loop if the array is non-empty */
+    if (idx <= end_idx && str[idx] != ']') {
+        while (idx <= end_idx) {
+
+            /* read any JSON term  */
+            val = scan_once_unicode(s, pystr, idx, &next_idx);
+            if (val == NULL)
+                goto bail;
+
+            if (PyList_Append(rval, val) == -1)
+                goto bail;
+
+            Py_CLEAR(val);
+            idx = next_idx;
+
+            /* skip whitespace between term and , */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+            /* bail if the array is closed or we didn't get the , delimiter */
+            if (idx > end_idx) break;
+            if (str[idx] == ']') {
+                break;
+            }
+            else if (str[idx] != ',') {
+                raise_errmsg("Expecting , delimiter", pystr, idx);
+                goto bail;
+            }
+            idx++;
+
+            /* skip whitespace after , */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+        }
+    }
+
+    /* verify that idx < end_idx, str[idx] should be ']' */
+    if (idx > end_idx || str[idx] != ']') {
+        raise_errmsg("Expecting object", pystr, end_idx);
+        goto bail;
+    }
+    *next_idx_ptr = idx + 1;
+    return rval;
+bail:
+    Py_XDECREF(val);
+    Py_DECREF(rval);
+    return NULL;
+}
+
+static PyObject *
+_parse_constant(PyScannerObject *s, char *constant, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) {
+    /* Read a JSON constant from PyString pystr.
+    constant is the constant string that was found
+        ("NaN", "Infinity", "-Infinity").
+    idx is the index of the first character of the constant
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the constant.
+
+    Returns the result of parse_constant
+    */
+    PyObject *cstr;
+    PyObject *rval;
+    /* constant is "NaN", "Infinity", or "-Infinity" */
+    cstr = PyString_InternFromString(constant);
+    if (cstr == NULL)
+        return NULL;
+
+    /* rval = parse_constant(constant) */
+    rval = PyObject_CallFunctionObjArgs(s->parse_constant, cstr, NULL);
+    idx += PyString_GET_SIZE(cstr);
+    Py_DECREF(cstr);
+    *next_idx_ptr = idx;
+    return rval;
+}
+
+static PyObject *
+_match_number_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_ssize_t *next_idx_ptr) {
+    /* Read a JSON number from PyString pystr.
+    idx is the index of the first character of the number
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the number.
+
+    Returns a new PyObject representation of that number:
+        PyInt, PyLong, or PyFloat.
+        May return other types if parse_int or parse_float are set
+    */
+    char *str = PyString_AS_STRING(pystr);
+    Py_ssize_t end_idx = PyString_GET_SIZE(pystr) - 1;
+    Py_ssize_t idx = start;
+    int is_float = 0;
+    PyObject *rval;
+    PyObject *numstr;
+
+    /* read a sign if it's there, make sure it's not the end of the string */
+    if (str[idx] == '-') {
+        idx++;
+        if (idx > end_idx) {
+            PyErr_SetNone(PyExc_StopIteration);
+            return NULL;
+        }
+    }
+
+    /* read as many integer digits as we find as long as it doesn't start with 0 */
+    if (str[idx] >= '1' && str[idx] <= '9') {
+        idx++;
+        while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++;
+    }
+    /* if it starts with 0 we only expect one integer digit */
+    else if (str[idx] == '0') {
+        idx++;
+    }
+    /* no integer digits, error */
+    else {
+        PyErr_SetNone(PyExc_StopIteration);
+        return NULL;
+    }
+
+    /* if the next char is '.' followed by a digit then read all float digits */
+    if (idx < end_idx && str[idx] == '.' && str[idx + 1] >= '0' && str[idx + 1] <= '9') {
+        is_float = 1;
+        idx += 2;
+        while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++;
+    }
+
+    /* if the next char is 'e' or 'E' then maybe read the exponent (or backtrack) */
+    if (idx < end_idx && (str[idx] == 'e' || str[idx] == 'E')) {
+
+        /* save the index of the 'e' or 'E' just in case we need to backtrack */
+        Py_ssize_t e_start = idx;
+        idx++;
+
+        /* read an exponent sign if present */
+        if (idx < end_idx && (str[idx] == '-' || str[idx] == '+')) idx++;
+
+        /* read all digits */
+        while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++;
+
+        /* if we got a digit, then parse as float. if not, backtrack */
+        if (str[idx - 1] >= '0' && str[idx - 1] <= '9') {
+            is_float = 1;
+        }
+        else {
+            idx = e_start;
+        }
+    }
+
+    /* copy the section we determined to be a number */
+    numstr = PyString_FromStringAndSize(&str[start], idx - start);
+    if (numstr == NULL)
+        return NULL;
+    if (is_float) {
+        /* parse as a float using a fast path if available, otherwise call user defined method */
+        if (s->parse_float != (PyObject *)&PyFloat_Type) {
+            rval = PyObject_CallFunctionObjArgs(s->parse_float, numstr, NULL);
+        }
+        else {
+            rval = PyFloat_FromDouble(PyOS_ascii_atof(PyString_AS_STRING(numstr)));
+        }
+    }
+    else {
+        /* parse as an int using a fast path if available, otherwise call user defined method */
+        if (s->parse_int != (PyObject *)&PyInt_Type) {
+            rval = PyObject_CallFunctionObjArgs(s->parse_int, numstr, NULL);
+        }
+        else {
+            rval = PyInt_FromString(PyString_AS_STRING(numstr), NULL, 10);
+        }
+    }
+    Py_DECREF(numstr);
+    *next_idx_ptr = idx;
+    return rval;
+}
+
+static PyObject *
+_match_number_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_ssize_t *next_idx_ptr) {
+    /* Read a JSON number from PyUnicode pystr.
+    idx is the index of the first character of the number
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the number.
+
+    Returns a new PyObject representation of that number:
+        PyInt, PyLong, or PyFloat.
+        May return other types if parse_int or parse_float are set
+    */
+    Py_UNICODE *str = PyUnicode_AS_UNICODE(pystr);
+    Py_ssize_t end_idx = PyUnicode_GET_SIZE(pystr) - 1;
+    Py_ssize_t idx = start;
+    int is_float = 0;
+    PyObject *rval;
+    PyObject *numstr;
+
+    /* read a sign if it's there, make sure it's not the end of the string */
+    if (str[idx] == '-') {
+        idx++;
+        if (idx > end_idx) {
+            PyErr_SetNone(PyExc_StopIteration);
+            return NULL;
+        }
+    }
+
+    /* read as many integer digits as we find as long as it doesn't start with 0 */
+    if (str[idx] >= '1' && str[idx] <= '9') {
+        idx++;
+        while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++;
+    }
+    /* if it starts with 0 we only expect one integer digit */
+    else if (str[idx] == '0') {
+        idx++;
+    }
+    /* no integer digits, error */
+    else {
+        PyErr_SetNone(PyExc_StopIteration);
+        return NULL;
+    }
+
+    /* if the next char is '.' followed by a digit then read all float digits */
+    if (idx < end_idx && str[idx] == '.' && str[idx + 1] >= '0' && str[idx + 1] <= '9') {
+        is_float = 1;
+        idx += 2;
+        while (idx < end_idx && str[idx] >= '0' && str[idx] <= '9') idx++;
+    }
+
+    /* if the next char is 'e' or 'E' then maybe read the exponent (or backtrack) */
+    if (idx < end_idx && (str[idx] == 'e' || str[idx] == 'E')) {
+        Py_ssize_t e_start = idx;
+        idx++;
+
+        /* read an exponent sign if present */
+        if (idx < end_idx && (str[idx] == '-' || str[idx] == '+')) idx++;
+
+        /* read all digits */
+        while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++;
+
+        /* if we got a digit, then parse as float. if not, backtrack */
+        if (str[idx - 1] >= '0' && str[idx - 1] <= '9') {
+            is_float = 1;
+        }
+        else {
+            idx = e_start;
+        }
+    }
+
+    /* copy the section we determined to be a number */
+    numstr = PyUnicode_FromUnicode(&str[start], idx - start);
+    if (numstr == NULL)
+        return NULL;
+    if (is_float) {
+        /* parse as a float using a fast path if available, otherwise call user defined method */
+        if (s->parse_float != (PyObject *)&PyFloat_Type) {
+            rval = PyObject_CallFunctionObjArgs(s->parse_float, numstr, NULL);
+        }
+        else {
+            rval = PyFloat_FromString(numstr, NULL);
+        }
+    }
+    else {
+        /* no fast path for unicode -> int, just call */
+        rval = PyObject_CallFunctionObjArgs(s->parse_int, numstr, NULL);
+    }
+    Py_DECREF(numstr);
+    *next_idx_ptr = idx;
+    return rval;
+}
+
+static PyObject *
+scan_once_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr)
+{
+    /* Read one JSON term (of any kind) from PyString pystr.
+    idx is the index of the first character of the term
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the number.
+
+    Returns a new PyObject representation of the term.
+    */
+    char *str = PyString_AS_STRING(pystr);
+    Py_ssize_t length = PyString_GET_SIZE(pystr);
+    if (idx >= length) {
+        PyErr_SetNone(PyExc_StopIteration);
+        return NULL;
+    }
+    switch (str[idx]) {
+        case '"':
+            /* string */
+            return scanstring_str(pystr, idx + 1,
+                PyString_AS_STRING(s->encoding),
+                PyObject_IsTrue(s->strict),
+                next_idx_ptr);
+        case '{':
+            /* object */
+            return _parse_object_str(s, pystr, idx + 1, next_idx_ptr);
+        case '[':
+            /* array */
+            return _parse_array_str(s, pystr, idx + 1, next_idx_ptr);
+        case 'n':
+            /* null */
+            if ((idx + 3 < length) && str[idx + 1] == 'u' && str[idx + 2] == 'l' && str[idx + 3] == 'l') {
+                Py_INCREF(Py_None);
+                *next_idx_ptr = idx + 4;
+                return Py_None;
+            }
+            break;
+        case 't':
+            /* true */
+            if ((idx + 3 < length) && str[idx + 1] == 'r' && str[idx + 2] == 'u' && str[idx + 3] == 'e') {
+                Py_INCREF(Py_True);
+                *next_idx_ptr = idx + 4;
+                return Py_True;
+            }
+            break;
+        case 'f':
+            /* false */
+            if ((idx + 4 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'l' && str[idx + 3] == 's' && str[idx + 4] == 'e') {
+                Py_INCREF(Py_False);
+                *next_idx_ptr = idx + 5;
+                return Py_False;
+            }
+            break;
+        case 'N':
+            /* NaN */
+            if ((idx + 2 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'N') {
+                return _parse_constant(s, "NaN", idx, next_idx_ptr);
+            }
+            break;
+        case 'I':
+            /* Infinity */
+            if ((idx + 7 < length) && str[idx + 1] == 'n' && str[idx + 2] == 'f' && str[idx + 3] == 'i' && str[idx + 4] == 'n' && str[idx + 5] == 'i' && str[idx + 6] == 't' && str[idx + 7] == 'y') {
+                return _parse_constant(s, "Infinity", idx, next_idx_ptr);
+            }
+            break;
+        case '-':
+            /* -Infinity */
+            if ((idx + 8 < length) && str[idx + 1] == 'I' && str[idx + 2] == 'n' && str[idx + 3] == 'f' && str[idx + 4] == 'i' && str[idx + 5] == 'n' && str[idx + 6] == 'i' && str[idx + 7] == 't' && str[idx + 8] == 'y') {
+                return _parse_constant(s, "-Infinity", idx, next_idx_ptr);
+            }
+            break;
+    }
+    /* Didn't find a string, object, array, or named constant. Look for a number. */
+    return _match_number_str(s, pystr, idx, next_idx_ptr);
+}
+
+static PyObject *
+scan_once_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr)
+{
+    /* Read one JSON term (of any kind) from PyUnicode pystr.
+    idx is the index of the first character of the term
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the number.
+
+    Returns a new PyObject representation of the term.
+    */
+    Py_UNICODE *str = PyUnicode_AS_UNICODE(pystr);
+    Py_ssize_t length = PyUnicode_GET_SIZE(pystr);
+    if (idx >= length) {
+        PyErr_SetNone(PyExc_StopIteration);
+        return NULL;
+    }
+    switch (str[idx]) {
+        case '"':
+            /* string */
+            return scanstring_unicode(pystr, idx + 1,
+                PyObject_IsTrue(s->strict),
+                next_idx_ptr);
+        case '{':
+            /* object */
+            return _parse_object_unicode(s, pystr, idx + 1, next_idx_ptr);
+        case '[':
+            /* array */
+            return _parse_array_unicode(s, pystr, idx + 1, next_idx_ptr);
+        case 'n':
+            /* null */
+            if ((idx + 3 < length) && str[idx + 1] == 'u' && str[idx + 2] == 'l' && str[idx + 3] == 'l') {
+                Py_INCREF(Py_None);
+                *next_idx_ptr = idx + 4;
+                return Py_None;
+            }
+            break;
+        case 't':
+            /* true */
+            if ((idx + 3 < length) && str[idx + 1] == 'r' && str[idx + 2] == 'u' && str[idx + 3] == 'e') {
+                Py_INCREF(Py_True);
+                *next_idx_ptr = idx + 4;
+                return Py_True;
+            }
+            break;
+        case 'f':
+            /* false */
+            if ((idx + 4 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'l' && str[idx + 3] == 's' && str[idx + 4] == 'e') {
+                Py_INCREF(Py_False);
+                *next_idx_ptr = idx + 5;
+                return Py_False;
+            }
+            break;
+        case 'N':
+            /* NaN */
+            if ((idx + 2 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'N') {
+                return _parse_constant(s, "NaN", idx, next_idx_ptr);
+            }
+            break;
+        case 'I':
+            /* Infinity */
+            if ((idx + 7 < length) && str[idx + 1] == 'n' && str[idx + 2] == 'f' && str[idx + 3] == 'i' && str[idx + 4] == 'n' && str[idx + 5] == 'i' && str[idx + 6] == 't' && str[idx + 7] == 'y') {
+                return _parse_constant(s, "Infinity", idx, next_idx_ptr);
+            }
+            break;
+        case '-':
+            /* -Infinity */
+            if ((idx + 8 < length) && str[idx + 1] == 'I' && str[idx + 2] == 'n' && str[idx + 3] == 'f' && str[idx + 4] == 'i' && str[idx + 5] == 'n' && str[idx + 6] == 'i' && str[idx + 7] == 't' && str[idx + 8] == 'y') {
+                return _parse_constant(s, "-Infinity", idx, next_idx_ptr);
+            }
+            break;
+    }
+    /* Didn't find a string, object, array, or named constant. Look for a number. */
+    return _match_number_unicode(s, pystr, idx, next_idx_ptr);
+}
+
+static PyObject *
+scanner_call(PyObject *self, PyObject *args, PyObject *kwds)
+{
+    /* Python callable interface to scan_once_{str,unicode} */
+    PyObject *pystr;
+    PyObject *rval;
+    Py_ssize_t idx;
+    Py_ssize_t next_idx = -1;
+    static char *kwlist[] = {"string", "idx", NULL};
+    PyScannerObject *s;
+    assert(PyScanner_Check(self));
+    s = (PyScannerObject *)self;
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO&:scan_once", kwlist, &pystr, _convertPyInt_AsSsize_t, &idx))
+        return NULL;
+
+    if (PyString_Check(pystr)) {
+        rval = scan_once_str(s, pystr, idx, &next_idx);
+    }
+    else if (PyUnicode_Check(pystr)) {
+        rval = scan_once_unicode(s, pystr, idx, &next_idx);
+    }
+    else {
+        PyErr_Format(PyExc_TypeError,
+                 "first argument must be a string, not %.80s",
+                 Py_TYPE(pystr)->tp_name);
+        return NULL;
+    }
+    return _build_rval_index_tuple(rval, next_idx);
+}
+
+static PyObject *
+scanner_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+    PyScannerObject *s;
+    s = (PyScannerObject *)type->tp_alloc(type, 0);
+    if (s != NULL) {
+        s->encoding = NULL;
+        s->strict = NULL;
+        s->object_hook = NULL;
+        s->parse_float = NULL;
+        s->parse_int = NULL;
+        s->parse_constant = NULL;
+    }
+    return (PyObject *)s;
+}
+
+static int
+scanner_init(PyObject *self, PyObject *args, PyObject *kwds)
+{
+    /* Initialize Scanner object */
+    PyObject *ctx;
+    static char *kwlist[] = {"context", NULL};
+    PyScannerObject *s;
+
+    assert(PyScanner_Check(self));
+    s = (PyScannerObject *)self;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:make_scanner", kwlist, &ctx))
+        return -1;
+
+    /* PyString_AS_STRING is used on encoding */
+    s->encoding = PyObject_GetAttrString(ctx, "encoding");
+    if (s->encoding == Py_None) {
+        Py_DECREF(Py_None);
+        s->encoding = PyString_InternFromString(DEFAULT_ENCODING);
+    }
+    else if (PyUnicode_Check(s->encoding)) {
+        PyObject *tmp = PyUnicode_AsEncodedString(s->encoding, NULL, NULL);
+        Py_DECREF(s->encoding);
+        s->encoding = tmp;
+    }
+    if (s->encoding == NULL || !PyString_Check(s->encoding))
+        goto bail;
+
+    /* All of these will fail "gracefully" so we don't need to verify them */
+    s->strict = PyObject_GetAttrString(ctx, "strict");
+    if (s->strict == NULL)
+        goto bail;
+    s->object_hook = PyObject_GetAttrString(ctx, "object_hook");
+    if (s->object_hook == NULL)
+        goto bail;
+    s->parse_float = PyObject_GetAttrString(ctx, "parse_float");
+    if (s->parse_float == NULL)
+        goto bail;
+    s->parse_int = PyObject_GetAttrString(ctx, "parse_int");
+    if (s->parse_int == NULL)
+        goto bail;
+    s->parse_constant = PyObject_GetAttrString(ctx, "parse_constant");
+    if (s->parse_constant == NULL)
+        goto bail;
+
+    return 0;
+
+bail:
+    Py_CLEAR(s->encoding);
+    Py_CLEAR(s->strict);
+    Py_CLEAR(s->object_hook);
+    Py_CLEAR(s->parse_float);
+    Py_CLEAR(s->parse_int);
+    Py_CLEAR(s->parse_constant);
+    return -1;
+}
+
+PyDoc_STRVAR(scanner_doc, "JSON scanner object");
+
+static
+PyTypeObject PyScannerType = {
+    PyObject_HEAD_INIT(NULL)
+    0,                    /* tp_internal */
+    "simplejson._speedups.Scanner",       /* tp_name */
+    sizeof(PyScannerObject), /* tp_basicsize */
+    0,                    /* tp_itemsize */
+    scanner_dealloc, /* tp_dealloc */
+    0,                    /* tp_print */
+    0,                    /* tp_getattr */
+    0,                    /* tp_setattr */
+    0,                    /* tp_compare */
+    0,                    /* tp_repr */
+    0,                    /* tp_as_number */
+    0,                    /* tp_as_sequence */
+    0,                    /* tp_as_mapping */
+    0,                    /* tp_hash */
+    scanner_call,         /* tp_call */
+    0,                    /* tp_str */
+    0,/* PyObject_GenericGetAttr, */                    /* tp_getattro */
+    0,/* PyObject_GenericSetAttr, */                    /* tp_setattro */
+    0,                    /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,   /* tp_flags */
+    scanner_doc,          /* tp_doc */
+    scanner_traverse,                    /* tp_traverse */
+    scanner_clear,                    /* tp_clear */
+    0,                    /* tp_richcompare */
+    0,                    /* tp_weaklistoffset */
+    0,                    /* tp_iter */
+    0,                    /* tp_iternext */
+    0,                    /* tp_methods */
+    scanner_members,                    /* tp_members */
+    0,                    /* tp_getset */
+    0,                    /* tp_base */
+    0,                    /* tp_dict */
+    0,                    /* tp_descr_get */
+    0,                    /* tp_descr_set */
+    0,                    /* tp_dictoffset */
+    scanner_init,                    /* tp_init */
+    0,/* PyType_GenericAlloc, */        /* tp_alloc */
+    scanner_new,          /* tp_new */
+    0,/* PyObject_GC_Del, */              /* tp_free */
+};
+
+static PyObject *
+encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+    PyEncoderObject *s;
+    s = (PyEncoderObject *)type->tp_alloc(type, 0);
+    if (s != NULL) {
+        s->markers = NULL;
+        s->defaultfn = NULL;
+        s->encoder = NULL;
+        s->indent = NULL;
+        s->key_separator = NULL;
+        s->item_separator = NULL;
+        s->sort_keys = NULL;
+        s->skipkeys = NULL;
+    }
+    return (PyObject *)s;
+}
+
+static int
+encoder_init(PyObject *self, PyObject *args, PyObject *kwds)
+{
+    /* initialize Encoder object */
+    static char *kwlist[] = {"markers", "default", "encoder", "indent", "key_separator", "item_separator", "sort_keys", "skipkeys", "allow_nan", NULL};
+
+    PyEncoderObject *s;
+    PyObject *allow_nan;
+
+    assert(PyEncoder_Check(self));
+    s = (PyEncoderObject *)self;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOOOOOOOO:make_encoder", kwlist,
+        &s->markers, &s->defaultfn, &s->encoder, &s->indent, &s->key_separator, &s->item_separator, &s->sort_keys, &s->skipkeys, &allow_nan))
+        return -1;
+
+    Py_INCREF(s->markers);
+    Py_INCREF(s->defaultfn);
+    Py_INCREF(s->encoder);
+    Py_INCREF(s->indent);
+    Py_INCREF(s->key_separator);
+    Py_INCREF(s->item_separator);
+    Py_INCREF(s->sort_keys);
+    Py_INCREF(s->skipkeys);
+    s->fast_encode = (PyCFunction_Check(s->encoder) && PyCFunction_GetFunction(s->encoder) == (PyCFunction)py_encode_basestring_ascii);
+    s->allow_nan = PyObject_IsTrue(allow_nan);
+    return 0;
+}
+
+static PyObject *
+encoder_call(PyObject *self, PyObject *args, PyObject *kwds)
+{
+    /* Python callable interface to encode_listencode_obj */
+    static char *kwlist[] = {"obj", "_current_indent_level", NULL};
+    PyObject *obj;
+    PyObject *rval;
+    Py_ssize_t indent_level;
+    PyEncoderObject *s;
+    assert(PyEncoder_Check(self));
+    s = (PyEncoderObject *)self;
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO&:_iterencode", kwlist,
+        &obj, _convertPyInt_AsSsize_t, &indent_level))
+        return NULL;
+    rval = PyList_New(0);
+    if (rval == NULL)
+        return NULL;
+    if (encoder_listencode_obj(s, rval, obj, indent_level)) {
+        Py_DECREF(rval);
+        return NULL;
+    }
+    return rval;
+}
+
+static PyObject *
+_encoded_const(PyObject *obj)
+{
+    /* Return the JSON string representation of None, True, False */
+    if (obj == Py_None) {
+        static PyObject *s_null = NULL;
+        if (s_null == NULL) {
+            s_null = PyString_InternFromString("null");
+        }
+        Py_INCREF(s_null);
+        return s_null;
+    }
+    else if (obj == Py_True) {
+        static PyObject *s_true = NULL;
+        if (s_true == NULL) {
+            s_true = PyString_InternFromString("true");
+        }
+        Py_INCREF(s_true);
+        return s_true;
+    }
+    else if (obj == Py_False) {
+        static PyObject *s_false = NULL;
+        if (s_false == NULL) {
+            s_false = PyString_InternFromString("false");
+        }
+        Py_INCREF(s_false);
+        return s_false;
+    }
+    else {
+        PyErr_SetString(PyExc_ValueError, "not a const");
+        return NULL;
+    }
+}
+
+static PyObject *
+encoder_encode_float(PyEncoderObject *s, PyObject *obj)
+{
+    /* Return the JSON representation of a PyFloat */
+    double i = PyFloat_AS_DOUBLE(obj);
+    if (!Py_IS_FINITE(i)) {
+        if (!s->allow_nan) {
+            PyErr_SetString(PyExc_ValueError, "Out of range float values are not JSON compliant");
+            return NULL;
+        }
+        if (i > 0) {
+            return PyString_FromString("Infinity");
+        }
+        else if (i < 0) {
+            return PyString_FromString("-Infinity");
+        }
+        else {
+            return PyString_FromString("NaN");
+        }
+    }
+    /* Use a better float format here? */
+    return PyObject_Repr(obj);
+}
+
+static PyObject *
+encoder_encode_string(PyEncoderObject *s, PyObject *obj)
+{
+    /* Return the JSON representation of a string */
+    if (s->fast_encode)
+        return py_encode_basestring_ascii(NULL, obj);
+    else
+        return PyObject_CallFunctionObjArgs(s->encoder, obj, NULL);
+}
+
+static int
+_steal_list_append(PyObject *lst, PyObject *stolen)
+{
+    /* Append stolen and then decrement its reference count */
+    int rval = PyList_Append(lst, stolen);
+    Py_DECREF(stolen);
+    return rval;
+}
+
+static int
+encoder_listencode_obj(PyEncoderObject *s, PyObject *rval, PyObject *obj, Py_ssize_t indent_level)
+{
+    /* Encode Python object obj to a JSON term, rval is a PyList */
+    PyObject *newobj;
+    int rv;
+
+    if (obj == Py_None || obj == Py_True || obj == Py_False) {
+        PyObject *cstr = _encoded_const(obj);
+        if (cstr == NULL)
+            return -1;
+        return _steal_list_append(rval, cstr);
+    }
+    else if (PyString_Check(obj) || PyUnicode_Check(obj))
+    {
+        PyObject *encoded = encoder_encode_string(s, obj);
+        if (encoded == NULL)
+            return -1;
+        return _steal_list_append(rval, encoded);
+    }
+    else if (PyInt_Check(obj) || PyLong_Check(obj)) {
+        PyObject *encoded = PyObject_Str(obj);
+        if (encoded == NULL)
+            return -1;
+        return _steal_list_append(rval, encoded);
+    }
+    else if (PyFloat_Check(obj)) {
+        PyObject *encoded = encoder_encode_float(s, obj);
+        if (encoded == NULL)
+            return -1;
+        return _steal_list_append(rval, encoded);
+    }
+    else if (PyList_Check(obj) || PyTuple_Check(obj)) {
+        return encoder_listencode_list(s, rval, obj, indent_level);
+    }
+    else if (PyDict_Check(obj)) {
+        return encoder_listencode_dict(s, rval, obj, indent_level);
+    }
+    else {
+        PyObject *ident = NULL;
+        if (s->markers != Py_None) {
+            int has_key;
+            ident = PyLong_FromVoidPtr(obj);
+            if (ident == NULL)
+                return -1;
+            has_key = PyDict_Contains(s->markers, ident);
+            if (has_key) {
+                if (has_key != -1)
+                    PyErr_SetString(PyExc_ValueError, "Circular reference detected");
+                Py_DECREF(ident);
+                return -1;
+            }
+            if (PyDict_SetItem(s->markers, ident, obj)) {
+                Py_DECREF(ident);
+                return -1;
+            }
+        }
+        newobj = PyObject_CallFunctionObjArgs(s->defaultfn, obj, NULL);
+        if (newobj == NULL) {
+            Py_XDECREF(ident);
+            return -1;
+        }
+        rv = encoder_listencode_obj(s, rval, newobj, indent_level);
+        Py_DECREF(newobj);
+        if (rv) {
+            Py_XDECREF(ident);
+            return -1;
+        }
+        if (ident != NULL) {
+            if (PyDict_DelItem(s->markers, ident)) {
+                Py_XDECREF(ident);
+                return -1;
+            }
+            Py_XDECREF(ident);
+        }
+        return rv;
+    }
+}
+
+static int
+encoder_listencode_dict(PyEncoderObject *s, PyObject *rval, PyObject *dct, Py_ssize_t indent_level)
+{
+    /* Encode Python dict dct a JSON term, rval is a PyList */
+    static PyObject *open_dict = NULL;
+    static PyObject *close_dict = NULL;
+    static PyObject *empty_dict = NULL;
+    PyObject *kstr = NULL;
+    PyObject *ident = NULL;
+    PyObject *key, *value;
+    Py_ssize_t pos;
+    int skipkeys;
+    Py_ssize_t idx;
+
+    if (open_dict == NULL || close_dict == NULL || empty_dict == NULL) {
+        open_dict = PyString_InternFromString("{");
+        close_dict = PyString_InternFromString("}");
+        empty_dict = PyString_InternFromString("{}");
+        if (open_dict == NULL || close_dict == NULL || empty_dict == NULL)
+            return -1;
+    }
+    if (PyDict_Size(dct) == 0)
+        return PyList_Append(rval, empty_dict);
+
+    if (s->markers != Py_None) {
+        int has_key;
+        ident = PyLong_FromVoidPtr(dct);
+        if (ident == NULL)
+            goto bail;
+        has_key = PyDict_Contains(s->markers, ident);
+        if (has_key) {
+            if (has_key != -1)
+                PyErr_SetString(PyExc_ValueError, "Circular reference detected");
+            goto bail;
+        }
+        if (PyDict_SetItem(s->markers, ident, dct)) {
+            goto bail;
+        }
+    }
+
+    if (PyList_Append(rval, open_dict))
+        goto bail;
+
+    if (s->indent != Py_None) {
+        /* TODO: DOES NOT RUN */
+        indent_level += 1;
+        /*
+            newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
+            separator = _item_separator + newline_indent
+            buf += newline_indent
+        */
+    }
+
+    /* TODO: C speedup not implemented for sort_keys */
+
+    pos = 0;
+    skipkeys = PyObject_IsTrue(s->skipkeys);
+    idx = 0;
+    while (PyDict_Next(dct, &pos, &key, &value)) {
+        PyObject *encoded;
+
+        if (PyString_Check(key) || PyUnicode_Check(key)) {
+            Py_INCREF(key);
+            kstr = key;
+        }
+        else if (PyFloat_Check(key)) {
+            kstr = encoder_encode_float(s, key);
+            if (kstr == NULL)
+                goto bail;
+        }
+        else if (PyInt_Check(key) || PyLong_Check(key)) {
+            kstr = PyObject_Str(key);
+            if (kstr == NULL)
+                goto bail;
+        }
+        else if (key == Py_True || key == Py_False || key == Py_None) {
+            kstr = _encoded_const(key);
+            if (kstr == NULL)
+                goto bail;
+        }
+        else if (skipkeys) {
+            continue;
+        }
+        else {
+            /* TODO: include repr of key */
+            PyErr_SetString(PyExc_ValueError, "keys must be a string");
+            goto bail;
+        }
+
+        if (idx) {
+            if (PyList_Append(rval, s->item_separator))
+                goto bail;
+        }
+
+        encoded = encoder_encode_string(s, kstr);
+        Py_CLEAR(kstr);
+        if (encoded == NULL)
+            goto bail;
+        if (PyList_Append(rval, encoded)) {
+            Py_DECREF(encoded);
+            goto bail;
+        }
+        Py_DECREF(encoded);
+        if (PyList_Append(rval, s->key_separator))
+            goto bail;
+        if (encoder_listencode_obj(s, rval, value, indent_level))
+            goto bail;
+        idx += 1;
+    }
+    if (ident != NULL) {
+        if (PyDict_DelItem(s->markers, ident))
+            goto bail;
+        Py_CLEAR(ident);
+    }
+    if (s->indent != Py_None) {
+        /* TODO: DOES NOT RUN */
+        indent_level -= 1;
+        /*
+            yield '\n' + (' ' * (_indent * _current_indent_level))
+        */
+    }
+    if (PyList_Append(rval, close_dict))
+        goto bail;
+    return 0;
+
+bail:
+    Py_XDECREF(kstr);
+    Py_XDECREF(ident);
+    return -1;
+}
+
+
+static int
+encoder_listencode_list(PyEncoderObject *s, PyObject *rval, PyObject *seq, Py_ssize_t indent_level)
+{
+    /* Encode Python list seq to a JSON term, rval is a PyList */
+    static PyObject *open_array = NULL;
+    static PyObject *close_array = NULL;
+    static PyObject *empty_array = NULL;
+    PyObject *ident = NULL;
+    PyObject *s_fast = NULL;
+    Py_ssize_t num_items;
+    PyObject **seq_items;
+    Py_ssize_t i;
+
+    if (open_array == NULL || close_array == NULL || empty_array == NULL) {
+        open_array = PyString_InternFromString("[");
+        close_array = PyString_InternFromString("]");
+        empty_array = PyString_InternFromString("[]");
+        if (open_array == NULL || close_array == NULL || empty_array == NULL)
+            return -1;
+    }
+    ident = NULL;
+    s_fast = PySequence_Fast(seq, "_iterencode_list needs a sequence");
+    if (s_fast == NULL)
+        return -1;
+    num_items = PySequence_Fast_GET_SIZE(s_fast);
+    if (num_items == 0) {
+        Py_DECREF(s_fast);
+        return PyList_Append(rval, empty_array);
+    }
+
+    if (s->markers != Py_None) {
+        int has_key;
+        ident = PyLong_FromVoidPtr(seq);
+        if (ident == NULL)
+            goto bail;
+        has_key = PyDict_Contains(s->markers, ident);
+        if (has_key) {
+            if (has_key != -1)
+                PyErr_SetString(PyExc_ValueError, "Circular reference detected");
+            goto bail;
+        }
+        if (PyDict_SetItem(s->markers, ident, seq)) {
+            goto bail;
+        }
+    }
+
+    seq_items = PySequence_Fast_ITEMS(s_fast);
+    if (PyList_Append(rval, open_array))
+        goto bail;
+    if (s->indent != Py_None) {
+        /* TODO: DOES NOT RUN */
+        indent_level += 1;
+        /*
+            newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
+            separator = _item_separator + newline_indent
+            buf += newline_indent
+        */
+    }
+    for (i = 0; i < num_items; i++) {
+        PyObject *obj = seq_items[i];
+        if (i) {
+            if (PyList_Append(rval, s->item_separator))
+                goto bail;
+        }
+        if (encoder_listencode_obj(s, rval, obj, indent_level))
+            goto bail;
+    }
+    if (ident != NULL) {
+        if (PyDict_DelItem(s->markers, ident))
+            goto bail;
+        Py_CLEAR(ident);
+    }
+    if (s->indent != Py_None) {
+        /* TODO: DOES NOT RUN */
+        indent_level -= 1;
+        /*
+            yield '\n' + (' ' * (_indent * _current_indent_level))
+        */
+    }
+    if (PyList_Append(rval, close_array))
+        goto bail;
+    Py_DECREF(s_fast);
+    return 0;
+
+bail:
+    Py_XDECREF(ident);
+    Py_DECREF(s_fast);
+    return -1;
+}
+
+static void
+encoder_dealloc(PyObject *self)
+{
+    /* Deallocate Encoder */
+    encoder_clear(self);
+    Py_TYPE(self)->tp_free(self);
+}
+
+static int
+encoder_traverse(PyObject *self, visitproc visit, void *arg)
+{
+    PyEncoderObject *s;
+    assert(PyEncoder_Check(self));
+    s = (PyEncoderObject *)self;
+    Py_VISIT(s->markers);
+    Py_VISIT(s->defaultfn);
+    Py_VISIT(s->encoder);
+    Py_VISIT(s->indent);
+    Py_VISIT(s->key_separator);
+    Py_VISIT(s->item_separator);
+    Py_VISIT(s->sort_keys);
+    Py_VISIT(s->skipkeys);
+    return 0;
+}
+
+static int
+encoder_clear(PyObject *self)
+{
+    /* Deallocate Encoder */
+    PyEncoderObject *s;
+    assert(PyEncoder_Check(self));
+    s = (PyEncoderObject *)self;
+    Py_CLEAR(s->markers);
+    Py_CLEAR(s->defaultfn);
+    Py_CLEAR(s->encoder);
+    Py_CLEAR(s->indent);
+    Py_CLEAR(s->key_separator);
+    Py_CLEAR(s->item_separator);
+    Py_CLEAR(s->sort_keys);
+    Py_CLEAR(s->skipkeys);
+    return 0;
+}
+
+PyDoc_STRVAR(encoder_doc, "_iterencode(obj, _current_indent_level) -> iterable");
+
+static
+PyTypeObject PyEncoderType = {
+    PyObject_HEAD_INIT(NULL)
+    0,                    /* tp_internal */
+    "simplejson._speedups.Encoder",       /* tp_name */
+    sizeof(PyEncoderObject), /* tp_basicsize */
+    0,                    /* tp_itemsize */
+    encoder_dealloc, /* tp_dealloc */
+    0,                    /* tp_print */
+    0,                    /* tp_getattr */
+    0,                    /* tp_setattr */
+    0,                    /* tp_compare */
+    0,                    /* tp_repr */
+    0,                    /* tp_as_number */
+    0,                    /* tp_as_sequence */
+    0,                    /* tp_as_mapping */
+    0,                    /* tp_hash */
+    encoder_call,         /* tp_call */
+    0,                    /* tp_str */
+    0,                    /* tp_getattro */
+    0,                    /* tp_setattro */
+    0,                    /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,   /* tp_flags */
+    encoder_doc,          /* tp_doc */
+    encoder_traverse,     /* tp_traverse */
+    encoder_clear,        /* tp_clear */
+    0,                    /* tp_richcompare */
+    0,                    /* tp_weaklistoffset */
+    0,                    /* tp_iter */
+    0,                    /* tp_iternext */
+    0,                    /* tp_methods */
+    encoder_members,      /* tp_members */
+    0,                    /* tp_getset */
+    0,                    /* tp_base */
+    0,                    /* tp_dict */
+    0,                    /* tp_descr_get */
+    0,                    /* tp_descr_set */
+    0,                    /* tp_dictoffset */
+    encoder_init,         /* tp_init */
+    0,                    /* tp_alloc */
+    encoder_new,          /* tp_new */
+    0,                    /* tp_free */
+};
+
+static PyMethodDef speedups_methods[] = {
+    {"encode_basestring_ascii",
+        (PyCFunction)py_encode_basestring_ascii,
+        METH_O,
+        pydoc_encode_basestring_ascii},
+    {"scanstring",
+        (PyCFunction)py_scanstring,
+        METH_VARARGS,
+        pydoc_scanstring},
+    {NULL, NULL, 0, NULL}
+};
+
+PyDoc_STRVAR(module_doc,
+"simplejson speedups\n");
+
+void
+init_speedups(void)
+{
+    PyObject *m;
+    PyScannerType.tp_new = PyType_GenericNew;
+    if (PyType_Ready(&PyScannerType) < 0)
+        return;
+    PyEncoderType.tp_new = PyType_GenericNew;
+    if (PyType_Ready(&PyEncoderType) < 0)
+        return;
+    m = Py_InitModule3("_speedups", speedups_methods, module_doc);
+    Py_INCREF((PyObject*)&PyScannerType);
+    PyModule_AddObject(m, "make_scanner", (PyObject*)&PyScannerType);
+    Py_INCREF((PyObject*)&PyEncoderType);
+    PyModule_AddObject(m, "make_encoder", (PyObject*)&PyEncoderType);
+}
diff --git a/ovsdb/simplejson/decoder.py b/ovsdb/simplejson/decoder.py
new file mode 100644 (file)
index 0000000..b769ea4
--- /dev/null
@@ -0,0 +1,354 @@
+"""Implementation of JSONDecoder
+"""
+import re
+import sys
+import struct
+
+from simplejson.scanner import make_scanner
+try:
+    from simplejson._speedups import scanstring as c_scanstring
+except ImportError:
+    c_scanstring = None
+
+__all__ = ['JSONDecoder']
+
+FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
+
+def _floatconstants():
+    _BYTES = '7FF80000000000007FF0000000000000'.decode('hex')
+    if sys.byteorder != 'big':
+        _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1]
+    nan, inf = struct.unpack('dd', _BYTES)
+    return nan, inf, -inf
+
+NaN, PosInf, NegInf = _floatconstants()
+
+
+def linecol(doc, pos):
+    lineno = doc.count('\n', 0, pos) + 1
+    if lineno == 1:
+        colno = pos
+    else:
+        colno = pos - doc.rindex('\n', 0, pos)
+    return lineno, colno
+
+
+def errmsg(msg, doc, pos, end=None):
+    # Note that this function is called from _speedups
+    lineno, colno = linecol(doc, pos)
+    if end is None:
+        #fmt = '{0}: line {1} column {2} (char {3})'
+        #return fmt.format(msg, lineno, colno, pos)
+        fmt = '%s: line %d column %d (char %d)'
+        return fmt % (msg, lineno, colno, pos)
+    endlineno, endcolno = linecol(doc, end)
+    #fmt = '{0}: line {1} column {2} - line {3} column {4} (char {5} - {6})'
+    #return fmt.format(msg, lineno, colno, endlineno, endcolno, pos, end)
+    fmt = '%s: line %d column %d - line %d column %d (char %d - %d)'
+    return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end)
+
+
+_CONSTANTS = {
+    '-Infinity': NegInf,
+    'Infinity': PosInf,
+    'NaN': NaN,
+}
+
+STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)
+BACKSLASH = {
+    '"': u'"', '\\': u'\\', '/': u'/',
+    'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t',
+}
+
+DEFAULT_ENCODING = "utf-8"
+
+def py_scanstring(s, end, encoding=None, strict=True, _b=BACKSLASH, _m=STRINGCHUNK.match):
+    """Scan the string s for a JSON string. End is the index of the
+    character in s after the quote that started the JSON string.
+    Unescapes all valid JSON string escape sequences and raises ValueError
+    on attempt to decode an invalid string. If strict is False then literal
+    control characters are allowed in the string.
+    
+    Returns a tuple of the decoded string and the index of the character in s
+    after the end quote."""
+    if encoding is None:
+        encoding = DEFAULT_ENCODING
+    chunks = []
+    _append = chunks.append
+    begin = end - 1
+    while 1:
+        chunk = _m(s, end)
+        if chunk is None:
+            raise ValueError(
+                errmsg("Unterminated string starting at", s, begin))
+        end = chunk.end()
+        content, terminator = chunk.groups()
+        # Content is contains zero or more unescaped string characters
+        if content:
+            if not isinstance(content, unicode):
+                content = unicode(content, encoding)
+            _append(content)
+        # Terminator is the end of string, a literal control character,
+        # or a backslash denoting that an escape sequence follows
+        if terminator == '"':
+            break
+        elif terminator != '\\':
+            if strict:
+                msg = "Invalid control character %r at" % (terminator,)
+                #msg = "Invalid control character {0!r} at".format(terminator)
+                raise ValueError(errmsg(msg, s, end))
+            else:
+                _append(terminator)
+                continue
+        try:
+            esc = s[end]
+        except IndexError:
+            raise ValueError(
+                errmsg("Unterminated string starting at", s, begin))
+        # If not a unicode escape sequence, must be in the lookup table
+        if esc != 'u':
+            try:
+                char = _b[esc]
+            except KeyError:
+                msg = "Invalid \\escape: " + repr(esc)
+                raise ValueError(errmsg(msg, s, end))
+            end += 1
+        else:
+            # Unicode escape sequence
+            esc = s[end + 1:end + 5]
+            next_end = end + 5
+            if len(esc) != 4:
+                msg = "Invalid \\uXXXX escape"
+                raise ValueError(errmsg(msg, s, end))
+            uni = int(esc, 16)
+            # Check for surrogate pair on UCS-4 systems
+            if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535:
+                msg = "Invalid \\uXXXX\\uXXXX surrogate pair"
+                if not s[end + 5:end + 7] == '\\u':
+                    raise ValueError(errmsg(msg, s, end))
+                esc2 = s[end + 7:end + 11]
+                if len(esc2) != 4:
+                    raise ValueError(errmsg(msg, s, end))
+                uni2 = int(esc2, 16)
+                uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00))
+                next_end += 6
+            char = unichr(uni)
+            end = next_end
+        # Append the unescaped character
+        _append(char)
+    return u''.join(chunks), end
+
+
+# Use speedup if available
+scanstring = c_scanstring or py_scanstring
+
+WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)
+WHITESPACE_STR = ' \t\n\r'
+
+def JSONObject((s, end), encoding, strict, scan_once, object_hook, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
+    pairs = {}
+    # Use a slice to prevent IndexError from being raised, the following
+    # check will raise a more specific ValueError if the string is empty
+    nextchar = s[end:end + 1]
+    # Normally we expect nextchar == '"'
+    if nextchar != '"':
+        if nextchar in _ws:
+            end = _w(s, end).end()
+            nextchar = s[end:end + 1]
+        # Trivial empty object
+        if nextchar == '}':
+            return pairs, end + 1
+        elif nextchar != '"':
+            raise ValueError(errmsg("Expecting property name", s, end))
+    end += 1
+    while True:
+        key, end = scanstring(s, end, encoding, strict)
+
+        # To skip some function call overhead we optimize the fast paths where
+        # the JSON key separator is ": " or just ":".
+        if s[end:end + 1] != ':':
+            end = _w(s, end).end()
+            if s[end:end + 1] != ':':
+                raise ValueError(errmsg("Expecting : delimiter", s, end))
+
+        end += 1
+
+        try:
+            if s[end] in _ws:
+                end += 1
+                if s[end] in _ws:
+                    end = _w(s, end + 1).end()
+        except IndexError:
+            pass
+
+        try:
+            value, end = scan_once(s, end)
+        except StopIteration:
+            raise ValueError(errmsg("Expecting object", s, end))
+        pairs[key] = value
+
+        try:
+            nextchar = s[end]
+            if nextchar in _ws:
+                end = _w(s, end + 1).end()
+                nextchar = s[end]
+        except IndexError:
+            nextchar = ''
+        end += 1
+
+        if nextchar == '}':
+            break
+        elif nextchar != ',':
+            raise ValueError(errmsg("Expecting , delimiter", s, end - 1))
+
+        try:
+            nextchar = s[end]
+            if nextchar in _ws:
+                end += 1
+                nextchar = s[end]
+                if nextchar in _ws:
+                    end = _w(s, end + 1).end()
+                    nextchar = s[end]
+        except IndexError:
+            nextchar = ''
+
+        end += 1
+        if nextchar != '"':
+            raise ValueError(errmsg("Expecting property name", s, end - 1))
+
+    if object_hook is not None:
+        pairs = object_hook(pairs)
+    return pairs, end
+
+def JSONArray((s, end), scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
+    values = []
+    nextchar = s[end:end + 1]
+    if nextchar in _ws:
+        end = _w(s, end + 1).end()
+        nextchar = s[end:end + 1]
+    # Look-ahead for trivial empty array
+    if nextchar == ']':
+        return values, end + 1
+    _append = values.append
+    while True:
+        try:
+            value, end = scan_once(s, end)
+        except StopIteration:
+            raise ValueError(errmsg("Expecting object", s, end))
+        _append(value)
+        nextchar = s[end:end + 1]
+        if nextchar in _ws:
+            end = _w(s, end + 1).end()
+            nextchar = s[end:end + 1]
+        end += 1
+        if nextchar == ']':
+            break
+        elif nextchar != ',':
+            raise ValueError(errmsg("Expecting , delimiter", s, end))
+
+        try:
+            if s[end] in _ws:
+                end += 1
+                if s[end] in _ws:
+                    end = _w(s, end + 1).end()
+        except IndexError:
+            pass
+
+    return values, end
+
+class JSONDecoder(object):
+    """Simple JSON <http://json.org> decoder
+
+    Performs the following translations in decoding by default:
+
+    +---------------+-------------------+
+    | JSON          | Python            |
+    +===============+===================+
+    | object        | dict              |
+    +---------------+-------------------+
+    | array         | list              |
+    +---------------+-------------------+
+    | string        | unicode           |
+    +---------------+-------------------+
+    | number (int)  | int, long         |
+    +---------------+-------------------+
+    | number (real) | float             |
+    +---------------+-------------------+
+    | true          | True              |
+    +---------------+-------------------+
+    | false         | False             |
+    +---------------+-------------------+
+    | null          | None              |
+    +---------------+-------------------+
+
+    It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as
+    their corresponding ``float`` values, which is outside the JSON spec.
+
+    """
+
+    def __init__(self, encoding=None, object_hook=None, parse_float=None,
+            parse_int=None, parse_constant=None, strict=True):
+        """``encoding`` determines the encoding used to interpret any ``str``
+        objects decoded by this instance (utf-8 by default).  It has no
+        effect when decoding ``unicode`` objects.
+
+        Note that currently only encodings that are a superset of ASCII work,
+        strings of other encodings should be passed in as ``unicode``.
+
+        ``object_hook``, if specified, will be called with the result
+        of every JSON object decoded and its return value will be used in
+        place of the given ``dict``.  This can be used to provide custom
+        deserializations (e.g. to support JSON-RPC class hinting).
+
+        ``parse_float``, if specified, will be called with the string
+        of every JSON float to be decoded. By default this is equivalent to
+        float(num_str). This can be used to use another datatype or parser
+        for JSON floats (e.g. decimal.Decimal).
+
+        ``parse_int``, if specified, will be called with the string
+        of every JSON int to be decoded. By default this is equivalent to
+        int(num_str). This can be used to use another datatype or parser
+        for JSON integers (e.g. float).
+
+        ``parse_constant``, if specified, will be called with one of the
+        following strings: -Infinity, Infinity, NaN.
+        This can be used to raise an exception if invalid JSON numbers
+        are encountered.
+
+        """
+        self.encoding = encoding
+        self.object_hook = object_hook
+        self.parse_float = parse_float or float
+        self.parse_int = parse_int or int
+        self.parse_constant = parse_constant or _CONSTANTS.__getitem__
+        self.strict = strict
+        self.parse_object = JSONObject
+        self.parse_array = JSONArray
+        self.parse_string = scanstring
+        self.scan_once = make_scanner(self)
+
+    def decode(self, s, _w=WHITESPACE.match):
+        """Return the Python representation of ``s`` (a ``str`` or ``unicode``
+        instance containing a JSON document)
+
+        """
+        obj, end = self.raw_decode(s, idx=_w(s, 0).end())
+        end = _w(s, end).end()
+        if end != len(s):
+            raise ValueError(errmsg("Extra data", s, end, len(s)))
+        return obj
+
+    def raw_decode(self, s, idx=0):
+        """Decode a JSON document from ``s`` (a ``str`` or ``unicode`` beginning
+        with a JSON document) and return a 2-tuple of the Python
+        representation and the index in ``s`` where the document ended.
+
+        This can be used to decode a JSON document from a string that may
+        have extraneous data at the end.
+
+        """
+        try:
+            obj, end = self.scan_once(s, idx)
+        except StopIteration:
+            raise ValueError("No JSON object could be decoded")
+        return obj, end
diff --git a/ovsdb/simplejson/encoder.py b/ovsdb/simplejson/encoder.py
new file mode 100644 (file)
index 0000000..cf58290
--- /dev/null
@@ -0,0 +1,440 @@
+"""Implementation of JSONEncoder
+"""
+import re
+
+try:
+    from simplejson._speedups import encode_basestring_ascii as c_encode_basestring_ascii
+except ImportError:
+    c_encode_basestring_ascii = None
+try:
+    from simplejson._speedups import make_encoder as c_make_encoder
+except ImportError:
+    c_make_encoder = None
+
+ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]')
+ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')
+HAS_UTF8 = re.compile(r'[\x80-\xff]')
+ESCAPE_DCT = {
+    '\\': '\\\\',
+    '"': '\\"',
+    '\b': '\\b',
+    '\f': '\\f',
+    '\n': '\\n',
+    '\r': '\\r',
+    '\t': '\\t',
+}
+for i in range(0x20):
+    #ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i))
+    ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))
+
+# Assume this produces an infinity on all machines (probably not guaranteed)
+INFINITY = float('1e66666')
+FLOAT_REPR = repr
+
+def encode_basestring(s):
+    """Return a JSON representation of a Python string
+
+    """
+    def replace(match):
+        return ESCAPE_DCT[match.group(0)]
+    return '"' + ESCAPE.sub(replace, s) + '"'
+
+
+def py_encode_basestring_ascii(s):
+    """Return an ASCII-only JSON representation of a Python string
+
+    """
+    if isinstance(s, str) and HAS_UTF8.search(s) is not None:
+        s = s.decode('utf-8')
+    def replace(match):
+        s = match.group(0)
+        try:
+            return ESCAPE_DCT[s]
+        except KeyError:
+            n = ord(s)
+            if n < 0x10000:
+                #return '\\u{0:04x}'.format(n)
+                return '\\u%04x' % (n,)
+            else:
+                # surrogate pair
+                n -= 0x10000
+                s1 = 0xd800 | ((n >> 10) & 0x3ff)
+                s2 = 0xdc00 | (n & 0x3ff)
+                #return '\\u{0:04x}\\u{1:04x}'.format(s1, s2)
+                return '\\u%04x\\u%04x' % (s1, s2)
+    return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"'
+
+
+encode_basestring_ascii = c_encode_basestring_ascii or py_encode_basestring_ascii
+
+class JSONEncoder(object):
+    """Extensible JSON <http://json.org> encoder for Python data structures.
+
+    Supports the following objects and types by default:
+
+    +-------------------+---------------+
+    | Python            | JSON          |
+    +===================+===============+
+    | dict              | object        |
+    +-------------------+---------------+
+    | list, tuple       | array         |
+    +-------------------+---------------+
+    | str, unicode      | string        |
+    +-------------------+---------------+
+    | int, long, float  | number        |
+    +-------------------+---------------+
+    | True              | true          |
+    +-------------------+---------------+
+    | False             | false         |
+    +-------------------+---------------+
+    | None              | null          |
+    +-------------------+---------------+
+
+    To extend this to recognize other objects, subclass and implement a
+    ``.default()`` method with another method that returns a serializable
+    object for ``o`` if possible, otherwise it should call the superclass
+    implementation (to raise ``TypeError``).
+
+    """
+    item_separator = ', '
+    key_separator = ': '
+    def __init__(self, skipkeys=False, ensure_ascii=True,
+            check_circular=True, allow_nan=True, sort_keys=False,
+            indent=None, separators=None, encoding='utf-8', default=None):
+        """Constructor for JSONEncoder, with sensible defaults.
+
+        If skipkeys is false, then it is a TypeError to attempt
+        encoding of keys that are not str, int, long, float or None.  If
+        skipkeys is True, such items are simply skipped.
+
+        If ensure_ascii is true, the output is guaranteed to be str
+        objects with all incoming unicode characters escaped.  If
+        ensure_ascii is false, the output will be unicode object.
+
+        If check_circular is true, then lists, dicts, and custom encoded
+        objects will be checked for circular references during encoding to
+        prevent an infinite recursion (which would cause an OverflowError).
+        Otherwise, no such check takes place.
+
+        If allow_nan is true, then NaN, Infinity, and -Infinity will be
+        encoded as such.  This behavior is not JSON specification compliant,
+        but is consistent with most JavaScript based encoders and decoders.
+        Otherwise, it will be a ValueError to encode such floats.
+
+        If sort_keys is true, then the output of dictionaries will be
+        sorted by key; this is useful for regression tests to ensure
+        that JSON serializations can be compared on a day-to-day basis.
+
+        If indent is a non-negative integer, then JSON array
+        elements and object members will be pretty-printed with that
+        indent level.  An indent level of 0 will only insert newlines.
+        None is the most compact representation.
+
+        If specified, separators should be a (item_separator, key_separator)
+        tuple.  The default is (', ', ': ').  To get the most compact JSON
+        representation you should specify (',', ':') to eliminate whitespace.
+
+        If specified, default is a function that gets called for objects
+        that can't otherwise be serialized.  It should return a JSON encodable
+        version of the object or raise a ``TypeError``.
+
+        If encoding is not None, then all input strings will be
+        transformed into unicode using that encoding prior to JSON-encoding.
+        The default is UTF-8.
+
+        """
+
+        self.skipkeys = skipkeys
+        self.ensure_ascii = ensure_ascii
+        self.check_circular = check_circular
+        self.allow_nan = allow_nan
+        self.sort_keys = sort_keys
+        self.indent = indent
+        if separators is not None:
+            self.item_separator, self.key_separator = separators
+        if default is not None:
+            self.default = default
+        self.encoding = encoding
+
+    def default(self, o):
+        """Implement this method in a subclass such that it returns
+        a serializable object for ``o``, or calls the base implementation
+        (to raise a ``TypeError``).
+
+        For example, to support arbitrary iterators, you could
+        implement default like this::
+
+            def default(self, o):
+                try:
+                    iterable = iter(o)
+                except TypeError:
+                    pass
+                else:
+                    return list(iterable)
+                return JSONEncoder.default(self, o)
+
+        """
+        raise TypeError(repr(o) + " is not JSON serializable")
+
+    def encode(self, o):
+        """Return a JSON string representation of a Python data structure.
+
+        >>> JSONEncoder().encode({"foo": ["bar", "baz"]})
+        '{"foo": ["bar", "baz"]}'
+
+        """
+        # This is for extremely simple cases and benchmarks.
+        if isinstance(o, basestring):
+            if isinstance(o, str):
+                _encoding = self.encoding
+                if (_encoding is not None
+                        and not (_encoding == 'utf-8')):
+                    o = o.decode(_encoding)
+            if self.ensure_ascii:
+                return encode_basestring_ascii(o)
+            else:
+                return encode_basestring(o)
+        # This doesn't pass the iterator directly to ''.join() because the
+        # exceptions aren't as detailed.  The list call should be roughly
+        # equivalent to the PySequence_Fast that ''.join() would do.
+        chunks = self.iterencode(o, _one_shot=True)
+        if not isinstance(chunks, (list, tuple)):
+            chunks = list(chunks)
+        return ''.join(chunks)
+
+    def iterencode(self, o, _one_shot=False):
+        """Encode the given object and yield each string
+        representation as available.
+
+        For example::
+
+            for chunk in JSONEncoder().iterencode(bigobject):
+                mysocket.write(chunk)
+
+        """
+        if self.check_circular:
+            markers = {}
+        else:
+            markers = None
+        if self.ensure_ascii:
+            _encoder = encode_basestring_ascii
+        else:
+            _encoder = encode_basestring
+        if self.encoding != 'utf-8':
+            def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding):
+                if isinstance(o, str):
+                    o = o.decode(_encoding)
+                return _orig_encoder(o)
+
+        def floatstr(o, allow_nan=self.allow_nan, _repr=FLOAT_REPR, _inf=INFINITY, _neginf=-INFINITY):
+            # Check for specials.  Note that this type of test is processor- and/or
+            # platform-specific, so do tests which don't depend on the internals.
+
+            if o != o:
+                text = 'NaN'
+            elif o == _inf:
+                text = 'Infinity'
+            elif o == _neginf:
+                text = '-Infinity'
+            else:
+                return _repr(o)
+
+            if not allow_nan:
+                raise ValueError(
+                    "Out of range float values are not JSON compliant: " +
+                    repr(o))
+
+            return text
+
+
+        if _one_shot and c_make_encoder is not None and not self.indent and not self.sort_keys:
+            _iterencode = c_make_encoder(
+                markers, self.default, _encoder, self.indent,
+                self.key_separator, self.item_separator, self.sort_keys,
+                self.skipkeys, self.allow_nan)
+        else:
+            _iterencode = _make_iterencode(
+                markers, self.default, _encoder, self.indent, floatstr,
+                self.key_separator, self.item_separator, self.sort_keys,
+                self.skipkeys, _one_shot)
+        return _iterencode(o, 0)
+
+def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, _key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
+        ## HACK: hand-optimized bytecode; turn globals into locals
+        False=False,
+        True=True,
+        ValueError=ValueError,
+        basestring=basestring,
+        dict=dict,
+        float=float,
+        id=id,
+        int=int,
+        isinstance=isinstance,
+        list=list,
+        long=long,
+        str=str,
+        tuple=tuple,
+    ):
+
+    def _iterencode_list(lst, _current_indent_level):
+        if not lst:
+            yield '[]'
+            return
+        if markers is not None:
+            markerid = id(lst)
+            if markerid in markers:
+                raise ValueError("Circular reference detected")
+            markers[markerid] = lst
+        buf = '['
+        if _indent is not None:
+            _current_indent_level += 1
+            newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
+            separator = _item_separator + newline_indent
+            buf += newline_indent
+        else:
+            newline_indent = None
+            separator = _item_separator
+        first = True
+        for value in lst:
+            if first:
+                first = False
+            else:
+                buf = separator
+            if isinstance(value, basestring):
+                yield buf + _encoder(value)
+            elif value is None:
+                yield buf + 'null'
+            elif value is True:
+                yield buf + 'true'
+            elif value is False:
+                yield buf + 'false'
+            elif isinstance(value, (int, long)):
+                yield buf + str(value)
+            elif isinstance(value, float):
+                yield buf + _floatstr(value)
+            else:
+                yield buf
+                if isinstance(value, (list, tuple)):
+                    chunks = _iterencode_list(value, _current_indent_level)
+                elif isinstance(value, dict):
+                    chunks = _iterencode_dict(value, _current_indent_level)
+                else:
+                    chunks = _iterencode(value, _current_indent_level)
+                for chunk in chunks:
+                    yield chunk
+        if newline_indent is not None:
+            _current_indent_level -= 1
+            yield '\n' + (' ' * (_indent * _current_indent_level))
+        yield ']'
+        if markers is not None:
+            del markers[markerid]
+
+    def _iterencode_dict(dct, _current_indent_level):
+        if not dct:
+            yield '{}'
+            return
+        if markers is not None:
+            markerid = id(dct)
+            if markerid in markers:
+                raise ValueError("Circular reference detected")
+            markers[markerid] = dct
+        yield '{'
+        if _indent is not None:
+            _current_indent_level += 1
+            newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
+            item_separator = _item_separator + newline_indent
+            yield newline_indent
+        else:
+            newline_indent = None
+            item_separator = _item_separator
+        first = True
+        if _sort_keys:
+            items = dct.items()
+            items.sort(key=lambda kv: kv[0])
+        else:
+            items = dct.iteritems()
+        for key, value in items:
+            if isinstance(key, basestring):
+                pass
+            # JavaScript is weakly typed for these, so it makes sense to
+            # also allow them.  Many encoders seem to do something like this.
+            elif isinstance(key, float):
+                key = _floatstr(key)
+            elif key is True:
+                key = 'true'
+            elif key is False:
+                key = 'false'
+            elif key is None:
+                key = 'null'
+            elif isinstance(key, (int, long)):
+                key = str(key)
+            elif _skipkeys:
+                continue
+            else:
+                raise TypeError("key " + repr(key) + " is not a string")
+            if first:
+                first = False
+            else:
+                yield item_separator
+            yield _encoder(key)
+            yield _key_separator
+            if isinstance(value, basestring):
+                yield _encoder(value)
+            elif value is None:
+                yield 'null'
+            elif value is True:
+                yield 'true'
+            elif value is False:
+                yield 'false'
+            elif isinstance(value, (int, long)):
+                yield str(value)
+            elif isinstance(value, float):
+                yield _floatstr(value)
+            else:
+                if isinstance(value, (list, tuple)):
+                    chunks = _iterencode_list(value, _current_indent_level)
+                elif isinstance(value, dict):
+                    chunks = _iterencode_dict(value, _current_indent_level)
+                else:
+                    chunks = _iterencode(value, _current_indent_level)
+                for chunk in chunks:
+                    yield chunk
+        if newline_indent is not None:
+            _current_indent_level -= 1
+            yield '\n' + (' ' * (_indent * _current_indent_level))
+        yield '}'
+        if markers is not None:
+            del markers[markerid]
+
+    def _iterencode(o, _current_indent_level):
+        if isinstance(o, basestring):
+            yield _encoder(o)
+        elif o is None:
+            yield 'null'
+        elif o is True:
+            yield 'true'
+        elif o is False:
+            yield 'false'
+        elif isinstance(o, (int, long)):
+            yield str(o)
+        elif isinstance(o, float):
+            yield _floatstr(o)
+        elif isinstance(o, (list, tuple)):
+            for chunk in _iterencode_list(o, _current_indent_level):
+                yield chunk
+        elif isinstance(o, dict):
+            for chunk in _iterencode_dict(o, _current_indent_level):
+                yield chunk
+        else:
+            if markers is not None:
+                markerid = id(o)
+                if markerid in markers:
+                    raise ValueError("Circular reference detected")
+                markers[markerid] = o
+            o = _default(o)
+            for chunk in _iterencode(o, _current_indent_level):
+                yield chunk
+            if markers is not None:
+                del markers[markerid]
+
+    return _iterencode
diff --git a/ovsdb/simplejson/scanner.py b/ovsdb/simplejson/scanner.py
new file mode 100644 (file)
index 0000000..adbc6ec
--- /dev/null
@@ -0,0 +1,65 @@
+"""JSON token scanner
+"""
+import re
+try:
+    from simplejson._speedups import make_scanner as c_make_scanner
+except ImportError:
+    c_make_scanner = None
+
+__all__ = ['make_scanner']
+
+NUMBER_RE = re.compile(
+    r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
+    (re.VERBOSE | re.MULTILINE | re.DOTALL))
+
+def py_make_scanner(context):
+    parse_object = context.parse_object
+    parse_array = context.parse_array
+    parse_string = context.parse_string
+    match_number = NUMBER_RE.match
+    encoding = context.encoding
+    strict = context.strict
+    parse_float = context.parse_float
+    parse_int = context.parse_int
+    parse_constant = context.parse_constant
+    object_hook = context.object_hook
+
+    def _scan_once(string, idx):
+        try:
+            nextchar = string[idx]
+        except IndexError:
+            raise StopIteration
+
+        if nextchar == '"':
+            return parse_string(string, idx + 1, encoding, strict)
+        elif nextchar == '{':
+            return parse_object((string, idx + 1), encoding, strict, _scan_once, object_hook)
+        elif nextchar == '[':
+            return parse_array((string, idx + 1), _scan_once)
+        elif nextchar == 'n' and string[idx:idx + 4] == 'null':
+            return None, idx + 4
+        elif nextchar == 't' and string[idx:idx + 4] == 'true':
+            return True, idx + 4
+        elif nextchar == 'f' and string[idx:idx + 5] == 'false':
+            return False, idx + 5
+
+        m = match_number(string, idx)
+        if m is not None:
+            integer, frac, exp = m.groups()
+            if frac or exp:
+                res = parse_float(integer + (frac or '') + (exp or ''))
+            else:
+                res = parse_int(integer)
+            return res, m.end()
+        elif nextchar == 'N' and string[idx:idx + 3] == 'NaN':
+            return parse_constant('NaN'), idx + 3
+        elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity':
+            return parse_constant('Infinity'), idx + 8
+        elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
+            return parse_constant('-Infinity'), idx + 9
+        else:
+            raise StopIteration
+
+    return _scan_once
+
+make_scanner = c_make_scanner or py_make_scanner
diff --git a/ovsdb/simplejson/tests/__init__.py b/ovsdb/simplejson/tests/__init__.py
new file mode 100644 (file)
index 0000000..17c9796
--- /dev/null
@@ -0,0 +1,23 @@
+import unittest
+import doctest
+
+def additional_tests():
+    import simplejson
+    import simplejson.encoder
+    import simplejson.decoder
+    suite = unittest.TestSuite()
+    for mod in (simplejson, simplejson.encoder, simplejson.decoder):
+        suite.addTest(doctest.DocTestSuite(mod))
+    suite.addTest(doctest.DocFileSuite('../../index.rst'))
+    return suite
+
+def main():
+    suite = additional_tests()
+    runner = unittest.TextTestRunner()
+    runner.run(suite)
+
+if __name__ == '__main__':
+    import os
+    import sys
+    sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
+    main()
diff --git a/ovsdb/simplejson/tests/test_check_circular.py b/ovsdb/simplejson/tests/test_check_circular.py
new file mode 100644 (file)
index 0000000..af6463d
--- /dev/null
@@ -0,0 +1,30 @@
+from unittest import TestCase
+import simplejson as json
+
+def default_iterable(obj):
+    return list(obj)
+
+class TestCheckCircular(TestCase):
+    def test_circular_dict(self):
+        dct = {}
+        dct['a'] = dct
+        self.assertRaises(ValueError, json.dumps, dct)
+
+    def test_circular_list(self):
+        lst = []
+        lst.append(lst)
+        self.assertRaises(ValueError, json.dumps, lst)
+
+    def test_circular_composite(self):
+        dct2 = {}
+        dct2['a'] = []
+        dct2['a'].append(dct2)
+        self.assertRaises(ValueError, json.dumps, dct2)
+
+    def test_circular_default(self):
+        json.dumps([set()], default=default_iterable)
+        self.assertRaises(TypeError, json.dumps, [set()])
+
+    def test_circular_off_default(self):
+        json.dumps([set()], default=default_iterable, check_circular=False)
+        self.assertRaises(TypeError, json.dumps, [set()], check_circular=False)
diff --git a/ovsdb/simplejson/tests/test_decode.py b/ovsdb/simplejson/tests/test_decode.py
new file mode 100644 (file)
index 0000000..1cd701d
--- /dev/null
@@ -0,0 +1,22 @@
+import decimal
+from unittest import TestCase
+
+import simplejson as json
+
+class TestDecode(TestCase):
+    def test_decimal(self):
+        rval = json.loads('1.1', parse_float=decimal.Decimal)
+        self.assert_(isinstance(rval, decimal.Decimal))
+        self.assertEquals(rval, decimal.Decimal('1.1'))
+
+    def test_float(self):
+        rval = json.loads('1', parse_int=float)
+        self.assert_(isinstance(rval, float))
+        self.assertEquals(rval, 1.0)
+
+    def test_decoder_optimizations(self):
+        # Several optimizations were made that skip over calls to
+        # the whitespace regex, so this test is designed to try and
+        # exercise the uncommon cases. The array cases are already covered.
+        rval = json.loads('{   "key"    :    "value"    ,  "k":"v"    }')
+        self.assertEquals(rval, {"key":"value", "k":"v"})
diff --git a/ovsdb/simplejson/tests/test_default.py b/ovsdb/simplejson/tests/test_default.py
new file mode 100644 (file)
index 0000000..139e42b
--- /dev/null
@@ -0,0 +1,9 @@
+from unittest import TestCase
+
+import simplejson as json
+
+class TestDefault(TestCase):
+    def test_default(self):
+        self.assertEquals(
+            json.dumps(type, default=repr),
+            json.dumps(repr(type)))
diff --git a/ovsdb/simplejson/tests/test_dump.py b/ovsdb/simplejson/tests/test_dump.py
new file mode 100644 (file)
index 0000000..4de37cf
--- /dev/null
@@ -0,0 +1,21 @@
+from unittest import TestCase
+from cStringIO import StringIO
+
+import simplejson as json
+
+class TestDump(TestCase):
+    def test_dump(self):
+        sio = StringIO()
+        json.dump({}, sio)
+        self.assertEquals(sio.getvalue(), '{}')
+
+    def test_dumps(self):
+        self.assertEquals(json.dumps({}), '{}')
+
+    def test_encode_truefalse(self):
+        self.assertEquals(json.dumps(
+                 {True: False, False: True}, sort_keys=True),
+                 '{"false": true, "true": false}')
+        self.assertEquals(json.dumps(
+                {2: 3.0, 4.0: 5L, False: 1, 6L: True, "7": 0}, sort_keys=True),
+                '{"false": 1, "2": 3.0, "4.0": 5, "6": true, "7": 0}')
diff --git a/ovsdb/simplejson/tests/test_encode_basestring_ascii.py b/ovsdb/simplejson/tests/test_encode_basestring_ascii.py
new file mode 100644 (file)
index 0000000..7128495
--- /dev/null
@@ -0,0 +1,38 @@
+from unittest import TestCase
+
+import simplejson.encoder
+
+CASES = [
+    (u'/\\"\ucafe\ubabe\uab98\ufcde\ubcda\uef4a\x08\x0c\n\r\t`1~!@#$%^&*()_+-=[]{}|;:\',./<>?', '"/\\\\\\"\\ucafe\\ubabe\\uab98\\ufcde\\ubcda\\uef4a\\b\\f\\n\\r\\t`1~!@#$%^&*()_+-=[]{}|;:\',./<>?"'),
+    (u'\u0123\u4567\u89ab\ucdef\uabcd\uef4a', '"\\u0123\\u4567\\u89ab\\ucdef\\uabcd\\uef4a"'),
+    (u'controls', '"controls"'),
+    (u'\x08\x0c\n\r\t', '"\\b\\f\\n\\r\\t"'),
+    (u'{"object with 1 member":["array with 1 element"]}', '"{\\"object with 1 member\\":[\\"array with 1 element\\"]}"'),
+    (u' s p a c e d ', '" s p a c e d "'),
+    (u'\U0001d120', '"\\ud834\\udd20"'),
+    (u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
+    ('\xce\xb1\xce\xa9', '"\\u03b1\\u03a9"'),
+    (u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
+    ('\xce\xb1\xce\xa9', '"\\u03b1\\u03a9"'),
+    (u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
+    (u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
+    (u"`1~!@#$%^&*()_+-={':[,]}|;.</>?", '"`1~!@#$%^&*()_+-={\':[,]}|;.</>?"'),
+    (u'\x08\x0c\n\r\t', '"\\b\\f\\n\\r\\t"'),
+    (u'\u0123\u4567\u89ab\ucdef\uabcd\uef4a', '"\\u0123\\u4567\\u89ab\\ucdef\\uabcd\\uef4a"'),
+]
+
+class TestEncodeBaseStringAscii(TestCase):
+    def test_py_encode_basestring_ascii(self):
+        self._test_encode_basestring_ascii(simplejson.encoder.py_encode_basestring_ascii)
+
+    def test_c_encode_basestring_ascii(self):
+        if not simplejson.encoder.c_encode_basestring_ascii:
+            return
+        self._test_encode_basestring_ascii(simplejson.encoder.c_encode_basestring_ascii)
+
+    def _test_encode_basestring_ascii(self, encode_basestring_ascii):
+        fname = encode_basestring_ascii.__name__
+        for input_string, expect in CASES:
+            result = encode_basestring_ascii(input_string)
+            self.assertEquals(result, expect,
+                '%r != %r for %s(%r)' % (result, expect, fname, input_string))
diff --git a/ovsdb/simplejson/tests/test_fail.py b/ovsdb/simplejson/tests/test_fail.py
new file mode 100644 (file)
index 0000000..002eea0
--- /dev/null
@@ -0,0 +1,76 @@
+from unittest import TestCase
+
+import simplejson as json
+
+# Fri Dec 30 18:57:26 2005
+JSONDOCS = [
+    # http://json.org/JSON_checker/test/fail1.json
+    '"A JSON payload should be an object or array, not a string."',
+    # http://json.org/JSON_checker/test/fail2.json
+    '["Unclosed array"',
+    # http://json.org/JSON_checker/test/fail3.json
+    '{unquoted_key: "keys must be quoted}',
+    # http://json.org/JSON_checker/test/fail4.json
+    '["extra comma",]',
+    # http://json.org/JSON_checker/test/fail5.json
+    '["double extra comma",,]',
+    # http://json.org/JSON_checker/test/fail6.json
+    '[   , "<-- missing value"]',
+    # http://json.org/JSON_checker/test/fail7.json
+    '["Comma after the close"],',
+    # http://json.org/JSON_checker/test/fail8.json
+    '["Extra close"]]',
+    # http://json.org/JSON_checker/test/fail9.json
+    '{"Extra comma": true,}',
+    # http://json.org/JSON_checker/test/fail10.json
+    '{"Extra value after close": true} "misplaced quoted value"',
+    # http://json.org/JSON_checker/test/fail11.json
+    '{"Illegal expression": 1 + 2}',
+    # http://json.org/JSON_checker/test/fail12.json
+    '{"Illegal invocation": alert()}',
+    # http://json.org/JSON_checker/test/fail13.json
+    '{"Numbers cannot have leading zeroes": 013}',
+    # http://json.org/JSON_checker/test/fail14.json
+    '{"Numbers cannot be hex": 0x14}',
+    # http://json.org/JSON_checker/test/fail15.json
+    '["Illegal backslash escape: \\x15"]',
+    # http://json.org/JSON_checker/test/fail16.json
+    '["Illegal backslash escape: \\\'"]',
+    # http://json.org/JSON_checker/test/fail17.json
+    '["Illegal backslash escape: \\017"]',
+    # http://json.org/JSON_checker/test/fail18.json
+    '[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]',
+    # http://json.org/JSON_checker/test/fail19.json
+    '{"Missing colon" null}',
+    # http://json.org/JSON_checker/test/fail20.json
+    '{"Double colon":: null}',
+    # http://json.org/JSON_checker/test/fail21.json
+    '{"Comma instead of colon", null}',
+    # http://json.org/JSON_checker/test/fail22.json
+    '["Colon instead of comma": false]',
+    # http://json.org/JSON_checker/test/fail23.json
+    '["Bad value", truth]',
+    # http://json.org/JSON_checker/test/fail24.json
+    "['single quote']",
+    # http://code.google.com/p/simplejson/issues/detail?id=3
+    u'["A\u001FZ control characters in string"]',
+]
+
+SKIPS = {
+    1: "why not have a string payload?",
+    18: "spec doesn't specify any nesting limitations",
+}
+
+class TestFail(TestCase):
+    def test_failures(self):
+        for idx, doc in enumerate(JSONDOCS):
+            idx = idx + 1
+            if idx in SKIPS:
+                json.loads(doc)
+                continue
+            try:
+                json.loads(doc)
+            except ValueError:
+                pass
+            else:
+                self.fail("Expected failure for fail%d.json: %r" % (idx, doc))
diff --git a/ovsdb/simplejson/tests/test_float.py b/ovsdb/simplejson/tests/test_float.py
new file mode 100644 (file)
index 0000000..1a2b98a
--- /dev/null
@@ -0,0 +1,15 @@
+import math
+from unittest import TestCase
+
+import simplejson as json
+
+class TestFloat(TestCase):
+    def test_floats(self):
+        for num in [1617161771.7650001, math.pi, math.pi**100, math.pi**-100, 3.1]:
+            self.assertEquals(float(json.dumps(num)), num)
+            self.assertEquals(json.loads(json.dumps(num)), num)
+
+    def test_ints(self):
+        for num in [1, 1L, 1<<32, 1<<64]:
+            self.assertEquals(json.dumps(num), str(num))
+            self.assertEquals(int(json.dumps(num)), num)
diff --git a/ovsdb/simplejson/tests/test_indent.py b/ovsdb/simplejson/tests/test_indent.py
new file mode 100644 (file)
index 0000000..66e19b9
--- /dev/null
@@ -0,0 +1,41 @@
+from unittest import TestCase
+
+import simplejson as json
+import textwrap
+
+class TestIndent(TestCase):
+    def test_indent(self):
+        h = [['blorpie'], ['whoops'], [], 'd-shtaeou', 'd-nthiouh', 'i-vhbjkhnth',
+             {'nifty': 87}, {'field': 'yes', 'morefield': False} ]
+
+        expect = textwrap.dedent("""\
+        [
+          [
+            "blorpie"
+          ],
+          [
+            "whoops"
+          ],
+          [],
+          "d-shtaeou",
+          "d-nthiouh",
+          "i-vhbjkhnth",
+          {
+            "nifty": 87
+          },
+          {
+            "field": "yes",
+            "morefield": false
+          }
+        ]""")
+
+
+        d1 = json.dumps(h)
+        d2 = json.dumps(h, indent=2, sort_keys=True, separators=(',', ': '))
+
+        h1 = json.loads(d1)
+        h2 = json.loads(d2)
+
+        self.assertEquals(h1, h)
+        self.assertEquals(h2, h)
+        self.assertEquals(d2, expect)
diff --git a/ovsdb/simplejson/tests/test_pass1.py b/ovsdb/simplejson/tests/test_pass1.py
new file mode 100644 (file)
index 0000000..c3d6302
--- /dev/null
@@ -0,0 +1,76 @@
+from unittest import TestCase
+
+import simplejson as json
+
+# from http://json.org/JSON_checker/test/pass1.json
+JSON = r'''
+[
+    "JSON Test Pattern pass1",
+    {"object with 1 member":["array with 1 element"]},
+    {},
+    [],
+    -42,
+    true,
+    false,
+    null,
+    {
+        "integer": 1234567890,
+        "real": -9876.543210,
+        "e": 0.123456789e-12,
+        "E": 1.234567890E+34,
+        "":  23456789012E666,
+        "zero": 0,
+        "one": 1,
+        "space": " ",
+        "quote": "\"",
+        "backslash": "\\",
+        "controls": "\b\f\n\r\t",
+        "slash": "/ & \/",
+        "alpha": "abcdefghijklmnopqrstuvwyz",
+        "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ",
+        "digit": "0123456789",
+        "special": "`1~!@#$%^&*()_+-={':[,]}|;.</>?",
+        "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A",
+        "true": true,
+        "false": false,
+        "null": null,
+        "array":[  ],
+        "object":{  },
+        "address": "50 St. James Street",
+        "url": "http://www.JSON.org/",
+        "comment": "// /* <!-- --",
+        "# -- --> */": " ",
+        " s p a c e d " :[1,2 , 3
+
+,
+
+4 , 5        ,          6           ,7        ],
+        "compact": [1,2,3,4,5,6,7],
+        "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}",
+        "quotes": "&#34; \u0022 %22 0x22 034 &#x22;",
+        "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?"
+: "A key can be any string"
+    },
+    0.5 ,98.6
+,
+99.44
+,
+
+1066
+
+
+,"rosebud"]
+'''
+
+class TestPass1(TestCase):
+    def test_parse(self):
+        # test in/out equivalence and parsing
+        res = json.loads(JSON)
+        out = json.dumps(res)
+        self.assertEquals(res, json.loads(out))
+        try:
+            json.dumps(res, allow_nan=False)
+        except ValueError:
+            pass
+        else:
+            self.fail("23456789012E666 should be out of range")
diff --git a/ovsdb/simplejson/tests/test_pass2.py b/ovsdb/simplejson/tests/test_pass2.py
new file mode 100644 (file)
index 0000000..de4ee00
--- /dev/null
@@ -0,0 +1,14 @@
+from unittest import TestCase
+import simplejson as json
+
+# from http://json.org/JSON_checker/test/pass2.json
+JSON = r'''
+[[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]]
+'''
+
+class TestPass2(TestCase):
+    def test_parse(self):
+        # test in/out equivalence and parsing
+        res = json.loads(JSON)
+        out = json.dumps(res)
+        self.assertEquals(res, json.loads(out))
diff --git a/ovsdb/simplejson/tests/test_pass3.py b/ovsdb/simplejson/tests/test_pass3.py
new file mode 100644 (file)
index 0000000..f591aba
--- /dev/null
@@ -0,0 +1,20 @@
+from unittest import TestCase
+
+import simplejson as json
+
+# from http://json.org/JSON_checker/test/pass3.json
+JSON = r'''
+{
+    "JSON Test Pattern pass3": {
+        "The outermost value": "must be an object or array.",
+        "In this test": "It is an object."
+    }
+}
+'''
+
+class TestPass3(TestCase):
+    def test_parse(self):
+        # test in/out equivalence and parsing
+        res = json.loads(JSON)
+        out = json.dumps(res)
+        self.assertEquals(res, json.loads(out))
diff --git a/ovsdb/simplejson/tests/test_recursion.py b/ovsdb/simplejson/tests/test_recursion.py
new file mode 100644 (file)
index 0000000..97422a6
--- /dev/null
@@ -0,0 +1,67 @@
+from unittest import TestCase
+
+import simplejson as json
+
+class JSONTestObject:
+    pass
+
+
+class RecursiveJSONEncoder(json.JSONEncoder):
+    recurse = False
+    def default(self, o):
+        if o is JSONTestObject:
+            if self.recurse:
+                return [JSONTestObject]
+            else:
+                return 'JSONTestObject'
+        return json.JSONEncoder.default(o)
+
+
+class TestRecursion(TestCase):
+    def test_listrecursion(self):
+        x = []
+        x.append(x)
+        try:
+            json.dumps(x)
+        except ValueError:
+            pass
+        else:
+            self.fail("didn't raise ValueError on list recursion")
+        x = []
+        y = [x]
+        x.append(y)
+        try:
+            json.dumps(x)
+        except ValueError:
+            pass
+        else:
+            self.fail("didn't raise ValueError on alternating list recursion")
+        y = []
+        x = [y, y]
+        # ensure that the marker is cleared
+        json.dumps(x)
+
+    def test_dictrecursion(self):
+        x = {}
+        x["test"] = x
+        try:
+            json.dumps(x)
+        except ValueError:
+            pass
+        else:
+            self.fail("didn't raise ValueError on dict recursion")
+        x = {}
+        y = {"a": x, "b": x}
+        # ensure that the marker is cleared
+        json.dumps(x)
+
+    def test_defaultrecursion(self):
+        enc = RecursiveJSONEncoder()
+        self.assertEquals(enc.encode(JSONTestObject), '"JSONTestObject"')
+        enc.recurse = True
+        try:
+            enc.encode(JSONTestObject)
+        except ValueError:
+            pass
+        else:
+            self.fail("didn't raise ValueError on default recursion")
diff --git a/ovsdb/simplejson/tests/test_scanstring.py b/ovsdb/simplejson/tests/test_scanstring.py
new file mode 100644 (file)
index 0000000..b08dec7
--- /dev/null
@@ -0,0 +1,111 @@
+import sys
+import decimal
+from unittest import TestCase
+
+import simplejson as json
+import simplejson.decoder
+
+class TestScanString(TestCase):
+    def test_py_scanstring(self):
+        self._test_scanstring(simplejson.decoder.py_scanstring)
+
+    def test_c_scanstring(self):
+        if not simplejson.decoder.c_scanstring:
+            return
+        self._test_scanstring(simplejson.decoder.c_scanstring)
+
+    def _test_scanstring(self, scanstring):
+        self.assertEquals(
+            scanstring('"z\\ud834\\udd20x"', 1, None, True),
+            (u'z\U0001d120x', 16))
+
+        if sys.maxunicode == 65535:
+            self.assertEquals(
+                scanstring(u'"z\U0001d120x"', 1, None, True),
+                (u'z\U0001d120x', 6))
+        else:
+            self.assertEquals(
+                scanstring(u'"z\U0001d120x"', 1, None, True),
+                (u'z\U0001d120x', 5))
+
+        self.assertEquals(
+            scanstring('"\\u007b"', 1, None, True),
+            (u'{', 8))
+
+        self.assertEquals(
+            scanstring('"A JSON payload should be an object or array, not a string."', 1, None, True),
+            (u'A JSON payload should be an object or array, not a string.', 60))
+
+        self.assertEquals(
+            scanstring('["Unclosed array"', 2, None, True),
+            (u'Unclosed array', 17))
+
+        self.assertEquals(
+            scanstring('["extra comma",]', 2, None, True),
+            (u'extra comma', 14))
+
+        self.assertEquals(
+            scanstring('["double extra comma",,]', 2, None, True),
+            (u'double extra comma', 21))
+
+        self.assertEquals(
+            scanstring('["Comma after the close"],', 2, None, True),
+            (u'Comma after the close', 24))
+
+        self.assertEquals(
+            scanstring('["Extra close"]]', 2, None, True),
+            (u'Extra close', 14))
+
+        self.assertEquals(
+            scanstring('{"Extra comma": true,}', 2, None, True),
+            (u'Extra comma', 14))
+
+        self.assertEquals(
+            scanstring('{"Extra value after close": true} "misplaced quoted value"', 2, None, True),
+            (u'Extra value after close', 26))
+
+        self.assertEquals(
+            scanstring('{"Illegal expression": 1 + 2}', 2, None, True),
+            (u'Illegal expression', 21))
+
+        self.assertEquals(
+            scanstring('{"Illegal invocation": alert()}', 2, None, True),
+            (u'Illegal invocation', 21))
+
+        self.assertEquals(
+            scanstring('{"Numbers cannot have leading zeroes": 013}', 2, None, True),
+            (u'Numbers cannot have leading zeroes', 37))
+
+        self.assertEquals(
+            scanstring('{"Numbers cannot be hex": 0x14}', 2, None, True),
+            (u'Numbers cannot be hex', 24))
+
+        self.assertEquals(
+            scanstring('[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]', 21, None, True),
+            (u'Too deep', 30))
+
+        self.assertEquals(
+            scanstring('{"Missing colon" null}', 2, None, True),
+            (u'Missing colon', 16))
+
+        self.assertEquals(
+            scanstring('{"Double colon":: null}', 2, None, True),
+            (u'Double colon', 15))
+
+        self.assertEquals(
+            scanstring('{"Comma instead of colon", null}', 2, None, True),
+            (u'Comma instead of colon', 25))
+
+        self.assertEquals(
+            scanstring('["Colon instead of comma": false]', 2, None, True),
+            (u'Colon instead of comma', 25))
+
+        self.assertEquals(
+            scanstring('["Bad value", truth]', 2, None, True),
+            (u'Bad value', 12))
+
+    def test_issue3623(self):
+        self.assertRaises(ValueError, json.decoder.scanstring, "xxx", 1,
+                          "xxx")
+        self.assertRaises(UnicodeDecodeError,
+                          json.encoder.encode_basestring_ascii, "xx\xff")
diff --git a/ovsdb/simplejson/tests/test_separators.py b/ovsdb/simplejson/tests/test_separators.py
new file mode 100644 (file)
index 0000000..8fa0dac
--- /dev/null
@@ -0,0 +1,42 @@
+import textwrap
+from unittest import TestCase
+
+import simplejson as json
+
+
+class TestSeparators(TestCase):
+    def test_separators(self):
+        h = [['blorpie'], ['whoops'], [], 'd-shtaeou', 'd-nthiouh', 'i-vhbjkhnth',
+             {'nifty': 87}, {'field': 'yes', 'morefield': False} ]
+
+        expect = textwrap.dedent("""\
+        [
+          [
+            "blorpie"
+          ] ,
+          [
+            "whoops"
+          ] ,
+          [] ,
+          "d-shtaeou" ,
+          "d-nthiouh" ,
+          "i-vhbjkhnth" ,
+          {
+            "nifty" : 87
+          } ,
+          {
+            "field" : "yes" ,
+            "morefield" : false
+          }
+        ]""")
+
+
+        d1 = json.dumps(h)
+        d2 = json.dumps(h, indent=2, sort_keys=True, separators=(' ,', ' : '))
+
+        h1 = json.loads(d1)
+        h2 = json.loads(d2)
+
+        self.assertEquals(h1, h)
+        self.assertEquals(h2, h)
+        self.assertEquals(d2, expect)
diff --git a/ovsdb/simplejson/tests/test_unicode.py b/ovsdb/simplejson/tests/test_unicode.py
new file mode 100644 (file)
index 0000000..6f4384a
--- /dev/null
@@ -0,0 +1,64 @@
+from unittest import TestCase
+
+import simplejson as json
+
+class TestUnicode(TestCase):
+    def test_encoding1(self):
+        encoder = json.JSONEncoder(encoding='utf-8')
+        u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
+        s = u.encode('utf-8')
+        ju = encoder.encode(u)
+        js = encoder.encode(s)
+        self.assertEquals(ju, js)
+
+    def test_encoding2(self):
+        u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
+        s = u.encode('utf-8')
+        ju = json.dumps(u, encoding='utf-8')
+        js = json.dumps(s, encoding='utf-8')
+        self.assertEquals(ju, js)
+
+    def test_encoding3(self):
+        u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
+        j = json.dumps(u)
+        self.assertEquals(j, '"\\u03b1\\u03a9"')
+
+    def test_encoding4(self):
+        u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
+        j = json.dumps([u])
+        self.assertEquals(j, '["\\u03b1\\u03a9"]')
+
+    def test_encoding5(self):
+        u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
+        j = json.dumps(u, ensure_ascii=False)
+        self.assertEquals(j, u'"%s"' % (u,))
+
+    def test_encoding6(self):
+        u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
+        j = json.dumps([u], ensure_ascii=False)
+        self.assertEquals(j, u'["%s"]' % (u,))
+
+    def test_big_unicode_encode(self):
+        u = u'\U0001d120'
+        self.assertEquals(json.dumps(u), '"\\ud834\\udd20"')
+        self.assertEquals(json.dumps(u, ensure_ascii=False), u'"\U0001d120"')
+
+    def test_big_unicode_decode(self):
+        u = u'z\U0001d120x'
+        self.assertEquals(json.loads('"' + u + '"'), u)
+        self.assertEquals(json.loads('"z\\ud834\\udd20x"'), u)
+
+    def test_unicode_decode(self):
+        for i in range(0, 0xd7ff):
+            u = unichr(i)
+            s = '"\\u%04x"' % (i,)
+            self.assertEquals(json.loads(s), u)
+
+    def test_default_encoding(self):
+        self.assertEquals(json.loads(u'{"a": "\xe9"}'.encode('utf-8')),
+            {'a': u'\xe9'})
+
+    def test_unicode_preservation(self):
+        self.assertEquals(type(json.loads(u'""')), unicode)
+        self.assertEquals(type(json.loads(u'"a"')), unicode)
+        self.assertEquals(type(json.loads(u'["a"]')[0]), unicode)
\ No newline at end of file
diff --git a/ovsdb/simplejson/tool.py b/ovsdb/simplejson/tool.py
new file mode 100644 (file)
index 0000000..9044331
--- /dev/null
@@ -0,0 +1,37 @@
+r"""Command-line tool to validate and pretty-print JSON
+
+Usage::
+
+    $ echo '{"json":"obj"}' | python -m simplejson.tool
+    {
+        "json": "obj"
+    }
+    $ echo '{ 1.2:3.4}' | python -m simplejson.tool
+    Expecting property name: line 1 column 2 (char 2)
+
+"""
+import sys
+import simplejson
+
+def main():
+    if len(sys.argv) == 1:
+        infile = sys.stdin
+        outfile = sys.stdout
+    elif len(sys.argv) == 2:
+        infile = open(sys.argv[1], 'rb')
+        outfile = sys.stdout
+    elif len(sys.argv) == 3:
+        infile = open(sys.argv[1], 'rb')
+        outfile = open(sys.argv[2], 'wb')
+    else:
+        raise SystemExit(sys.argv[0] + " [infile [outfile]]")
+    try:
+        obj = simplejson.load(infile)
+    except ValueError, e:
+        raise SystemExit(e)
+    simplejson.dump(obj, outfile, sort_keys=True, indent=4)
+    outfile.write('\n')
+
+
+if __name__ == '__main__':
+    main()
diff --git a/ovsdb/table.c b/ovsdb/table.c
new file mode 100644 (file)
index 0000000..b520580
--- /dev/null
@@ -0,0 +1,230 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "table.h"
+
+#include <assert.h>
+
+#include "json.h"
+#include "column.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "ovsdb-types.h"
+#include "row.h"
+
+static void
+add_column(struct ovsdb_table_schema *ts, struct ovsdb_column *column)
+{
+    assert(!shash_find(&ts->columns, column->name));
+    column->index = shash_count(&ts->columns);
+    shash_add(&ts->columns, column->name, column);
+}
+
+struct ovsdb_table_schema *
+ovsdb_table_schema_create(const char *name, const char *comment, bool mutable)
+{
+    struct ovsdb_column *uuid, *version;
+    struct ovsdb_table_schema *ts;
+
+    ts = xzalloc(sizeof *ts);
+    ts->name = xstrdup(name);
+    ts->comment = comment ? xstrdup(comment) : NULL;
+    ts->mutable = mutable;
+    shash_init(&ts->columns);
+
+    uuid = ovsdb_column_create(
+        "_uuid", "Unique identifier for this row.",
+        false, true, &ovsdb_type_uuid);
+    add_column(ts, uuid);
+    assert(uuid->index == OVSDB_COL_UUID);
+
+    version = ovsdb_column_create(
+        "_version", "Unique identifier for this version of this row.",
+        false, false, &ovsdb_type_uuid);
+    add_column(ts, version);
+    assert(version->index == OVSDB_COL_VERSION);
+
+    return ts;
+}
+
+void
+ovsdb_table_schema_destroy(struct ovsdb_table_schema *ts)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &ts->columns) {
+        ovsdb_column_destroy(node->data);
+    }
+    shash_destroy(&ts->columns);
+    free(ts->comment);
+    free(ts->name);
+    free(ts);
+}
+
+struct ovsdb_error *
+ovsdb_table_schema_from_json(const struct json *json, const char *name,
+                             struct ovsdb_table_schema **tsp)
+{
+    struct ovsdb_table_schema *ts;
+    const struct json *comment, *columns, *mutable;
+    struct shash_node *node;
+    struct ovsdb_parser parser;
+    struct ovsdb_error *error;
+
+    *tsp = NULL;
+
+    ovsdb_parser_init(&parser, json, "table schema for table %s", name);
+    comment = ovsdb_parser_member(&parser, "comment", OP_STRING | OP_OPTIONAL);
+    columns = ovsdb_parser_member(&parser, "columns", OP_OBJECT);
+    mutable = ovsdb_parser_member(&parser, "mutable",
+                                  OP_TRUE | OP_FALSE | OP_OPTIONAL);
+    error = ovsdb_parser_finish(&parser);
+    if (error) {
+        return error;
+    }
+
+    if (shash_is_empty(json_object(columns))) {
+        return ovsdb_syntax_error(json, NULL,
+                                  "table must have at least one column");
+    }
+
+    ts = ovsdb_table_schema_create(name,
+                                   comment ? json_string(comment) : NULL,
+                                   mutable ? json_boolean(mutable) : true);
+    SHASH_FOR_EACH (node, json_object(columns)) {
+        struct ovsdb_column *column;
+
+        if (node->name[0] == '_') {
+            error = ovsdb_syntax_error(json, NULL, "names beginning with "
+                                       "\"_\" are reserved");
+        } else if (!ovsdb_parser_is_id(node->name)) {
+            error = ovsdb_syntax_error(json, NULL, "name must be a valid id");
+        } else {
+            error = ovsdb_column_from_json(node->data, node->name, &column);
+        }
+        if (error) {
+            ovsdb_table_schema_destroy(ts);
+            return error;
+        }
+
+        add_column(ts, column);
+    }
+    *tsp = ts;
+    return 0;
+}
+
+struct json *
+ovsdb_table_schema_to_json(const struct ovsdb_table_schema *ts)
+{
+    struct json *json, *columns;
+    struct shash_node *node;
+
+    json = json_object_create();
+    if (ts->comment) {
+        json_object_put_string(json, "comment", ts->comment);
+    }
+    if (!ts->mutable) {
+        json_object_put(json, "mutable", json_boolean_create(false));
+    }
+
+    columns = json_object_create();
+
+    SHASH_FOR_EACH (node, &ts->columns) {
+        struct ovsdb_column *column = node->data;
+        if (node->name[0] != '_') {
+            json_object_put(columns, column->name,
+                            ovsdb_column_to_json(column));
+        }
+    }
+    json_object_put(json, "columns", columns);
+
+    return json;
+}
+
+const struct ovsdb_column *
+ovsdb_table_schema_get_column(const struct ovsdb_table_schema *ts,
+                              const char *name)
+{
+    return shash_find_data(&ts->columns, name);
+}
+\f
+struct ovsdb_table *
+ovsdb_table_create(struct ovsdb_table_schema *ts)
+{
+    struct ovsdb_table *table;
+
+    table = xmalloc(sizeof *table);
+    table->schema = ts;
+    hmap_init(&table->rows);
+
+    return table;
+}
+
+void
+ovsdb_table_destroy(struct ovsdb_table *table)
+{
+    if (table) {
+        struct ovsdb_row *row, *next;
+
+        HMAP_FOR_EACH_SAFE (row, next, struct ovsdb_row, hmap_node,
+                            &table->rows) {
+            ovsdb_row_destroy(row);
+        }
+        hmap_destroy(&table->rows);
+
+        ovsdb_table_schema_destroy(table->schema);
+        free(table);
+    }
+}
+
+static const struct ovsdb_row *
+ovsdb_table_get_row__(const struct ovsdb_table *table, const struct uuid *uuid,
+                      size_t hash)
+{
+    struct ovsdb_row *row;
+
+    HMAP_FOR_EACH_WITH_HASH (row, struct ovsdb_row, hmap_node, hash,
+                             &table->rows) {
+        if (uuid_equals(ovsdb_row_get_uuid(row), uuid)) {
+            return row;
+        }
+    }
+
+    return NULL;
+}
+
+const struct ovsdb_row *
+ovsdb_table_get_row(const struct ovsdb_table *table, const struct uuid *uuid)
+{
+    return ovsdb_table_get_row__(table, uuid, uuid_hash(uuid));
+}
+
+/* This is probably not the function you want.  Use ovsdb_txn_row_modify()
+ * instead. */
+bool
+ovsdb_table_put_row(struct ovsdb_table *table, struct ovsdb_row *row)
+{
+    const struct uuid *uuid = ovsdb_row_get_uuid(row);
+    size_t hash = uuid_hash(uuid);
+
+    if (!ovsdb_table_get_row__(table, uuid, hash)) {
+        hmap_insert(&table->rows, &row->hmap_node, hash);
+        return true;
+    } else {
+        return false;
+    }
+}
diff --git a/ovsdb/table.h b/ovsdb/table.h
new file mode 100644 (file)
index 0000000..9e36c91
--- /dev/null
@@ -0,0 +1,63 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_TABLE_H
+#define OVSDB_TABLE_H 1
+
+#include <stdbool.h>
+#include "compiler.h"
+#include "hmap.h"
+#include "shash.h"
+
+struct json;
+struct uuid;
+
+/* Schema for a database table. */
+struct ovsdb_table_schema {
+    char *name;
+    char *comment;
+    bool mutable;
+    struct shash columns;       /* Contains "struct ovsdb_column *"s. */
+};
+
+struct ovsdb_table_schema *ovsdb_table_schema_create(const char *name,
+                                                     const char *comment,
+                                                     bool mutable);
+void ovsdb_table_schema_destroy(struct ovsdb_table_schema *);
+
+struct ovsdb_error *ovsdb_table_schema_from_json(const struct json *,
+                                                 const char *name,
+                                                 struct ovsdb_table_schema **)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_table_schema_to_json(const struct ovsdb_table_schema *);
+
+const struct ovsdb_column *ovsdb_table_schema_get_column(
+    const struct ovsdb_table_schema *, const char *name);
+\f
+/* Database table. */
+
+struct ovsdb_table {
+    struct ovsdb_table_schema *schema;
+    struct hmap rows;           /* Contains "struct ovsdb_row"s. */
+};
+
+struct ovsdb_table *ovsdb_table_create(struct ovsdb_table_schema *);
+void ovsdb_table_destroy(struct ovsdb_table *);
+
+const struct ovsdb_row *ovsdb_table_get_row(const struct ovsdb_table *,
+                                            const struct uuid *);
+bool ovsdb_table_put_row(struct ovsdb_table *, struct ovsdb_row *);
+
+#endif /* ovsdb/table.h */
diff --git a/ovsdb/transaction.c b/ovsdb/transaction.c
new file mode 100644 (file)
index 0000000..d5e3601
--- /dev/null
@@ -0,0 +1,283 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "transaction.h"
+
+#include <assert.h>
+
+#include "hash.h"
+#include "hmap.h"
+#include "json.h"
+#include "ovsdb-error.h"
+#include "ovsdb.h"
+#include "row.h"
+#include "table.h"
+#include "uuid.h"
+
+struct ovsdb_txn {
+    struct ovsdb *db;
+    struct hmap txn_tables;     /* Contains "struct ovsdb_txn_table"s. */
+};
+
+/* A table modified by a transaction. */
+struct ovsdb_txn_table {
+    struct hmap_node hmap_node; /* Element in ovsdb_txn's txn_tables hmap. */
+    struct ovsdb_table *table;
+    struct hmap txn_rows;       /* Contains "struct ovsdb_txn_row"s. */
+};
+
+/* A row modified by the transaction:
+ *
+ *      - A row added by a transaction will have null 'old' and non-null 'new'.
+ *
+ *      - A row deleted by a transaction will have non-null 'old' and null
+ *        'new'.
+ *
+ *      - A row modified by a transaction will have non-null 'old' and 'new'.
+ *
+ *      - 'old' and 'new' both null is invalid.  It would indicate that a row
+ *        was added then deleted within a single transaction, but we instead
+ *        handle that case by deleting the txn_row entirely.
+ */
+struct ovsdb_txn_row {
+    struct hmap_node hmap_node; /* In ovsdb_txn_table's txn_rows hmap. */
+    struct ovsdb_row *old;      /* The old row. */
+    struct ovsdb_row *new;      /* The new row. */
+};
+
+struct ovsdb_txn *
+ovsdb_txn_create(struct ovsdb *db)
+{
+    struct ovsdb_txn *txn = xmalloc(sizeof *txn);
+    txn->db = db;
+    hmap_init(&txn->txn_tables);
+    return txn;
+}
+
+static void
+ovsdb_txn_destroy(struct ovsdb_txn *txn, void (*cb)(struct ovsdb_txn_row *))
+{
+    struct ovsdb_txn_table *txn_table, *next_txn_table;
+
+    HMAP_FOR_EACH_SAFE (txn_table, next_txn_table,
+                        struct ovsdb_txn_table, hmap_node, &txn->txn_tables)
+    {
+        struct ovsdb_txn_row *txn_row, *next_txn_row;
+
+        HMAP_FOR_EACH_SAFE (txn_row, next_txn_row,
+                            struct ovsdb_txn_row, hmap_node,
+                            &txn_table->txn_rows)
+        {
+            if (txn_row->new) {
+                txn_row->new->txn_row = NULL;
+            }
+            cb(txn_row);
+            free(txn_row);
+        }
+
+        hmap_destroy(&txn_table->txn_rows);
+        free(txn_table);
+    }
+    hmap_destroy(&txn->txn_tables);
+    free(txn);
+}
+
+static void
+ovsdb_txn_row_abort(struct ovsdb_txn_row *txn_row)
+{
+    struct ovsdb_row *old = txn_row->old;
+    struct ovsdb_row *new = txn_row->new;
+
+    if (!old) {
+        hmap_remove(&new->table->rows, &new->hmap_node);
+    } else if (!new) {
+        hmap_insert(&old->table->rows, &old->hmap_node, ovsdb_row_hash(old));
+    } else {
+        hmap_replace(&new->table->rows, &new->hmap_node, &old->hmap_node);
+    }
+    ovsdb_row_destroy(new);
+}
+
+void
+ovsdb_txn_abort(struct ovsdb_txn *txn)
+{
+    ovsdb_txn_destroy(txn, ovsdb_txn_row_abort);
+}
+
+static void
+ovsdb_txn_row_commit(struct ovsdb_txn_row *txn_row)
+{
+    ovsdb_row_destroy(txn_row->old);
+}
+
+struct ovsdb_error *
+ovsdb_txn_commit(struct ovsdb_txn *txn, bool durable)
+{
+    struct ovsdb_replica *replica;
+    struct ovsdb_error *error;
+
+    LIST_FOR_EACH (replica, struct ovsdb_replica, node, &txn->db->replicas) {
+        error = (replica->class->commit)(replica, txn, durable);
+        if (error) {
+            /* We don't support two-phase commit so only the first replica is
+             * allowed to report an error. */
+            assert(&replica->node == txn->db->replicas.next);
+
+            ovsdb_txn_abort(txn);
+            return error;
+        }
+    }
+
+    txn->db->run_triggers = true;
+    ovsdb_txn_destroy(txn, ovsdb_txn_row_commit);
+    return NULL;
+}
+
+void
+ovsdb_txn_for_each_change(const struct ovsdb_txn *txn,
+                          ovsdb_txn_row_cb_func *cb, void *aux)
+{
+    struct ovsdb_txn_table *t;
+    struct ovsdb_txn_row *r;
+
+    HMAP_FOR_EACH (t, struct ovsdb_txn_table, hmap_node, &txn->txn_tables) {
+        HMAP_FOR_EACH (r, struct ovsdb_txn_row, hmap_node, &t->txn_rows) {
+            if (!cb(r->old, r->new, aux)) {
+                break;
+            }
+        }
+   }
+}
+
+static struct ovsdb_txn_table *
+ovsdb_txn_get_txn_table__(struct ovsdb_txn *txn,
+                          const struct ovsdb_table *table,
+                          uint32_t hash)
+{
+    struct ovsdb_txn_table *txn_table;
+
+    HMAP_FOR_EACH_IN_BUCKET (txn_table, struct ovsdb_txn_table, hmap_node,
+                             hash, &txn->txn_tables) {
+        if (txn_table->table == table) {
+            return txn_table;
+        }
+    }
+
+    return NULL;
+}
+
+static struct ovsdb_txn_table *
+ovsdb_txn_get_txn_table(struct ovsdb_txn *txn, const struct ovsdb_table *table)
+{
+    return ovsdb_txn_get_txn_table__(txn, table, hash_pointer(table, 0));
+}
+
+static struct ovsdb_txn_table *
+ovsdb_txn_create_txn_table(struct ovsdb_txn *txn,
+                           struct ovsdb_table *table)
+{
+    uint32_t hash = hash_pointer(table, 0);
+    struct ovsdb_txn_table *txn_table;
+
+    txn_table = ovsdb_txn_get_txn_table__(txn, table, hash);
+    if (!txn_table) {
+        txn_table = xmalloc(sizeof *txn_table);
+        txn_table->table = table;
+        hmap_init(&txn_table->txn_rows);
+        hmap_insert(&txn->txn_tables, &txn_table->hmap_node, hash);
+    }
+    return txn_table;
+}
+
+static struct ovsdb_txn_row *
+ovsdb_txn_row_create(struct ovsdb_txn_table *txn_table,
+                     const struct ovsdb_row *old, struct ovsdb_row *new)
+{
+    uint32_t hash = ovsdb_row_hash(old ? old : new);
+    struct ovsdb_txn_row *txn_row;
+
+    txn_row = xmalloc(sizeof *txn_row);
+    txn_row->old = (struct ovsdb_row *) old;
+    txn_row->new = new;
+    hmap_insert(&txn_table->txn_rows, &txn_row->hmap_node, hash);
+
+    return txn_row;
+}
+
+struct ovsdb_row *
+ovsdb_txn_row_modify(struct ovsdb_txn *txn, const struct ovsdb_row *ro_row_)
+{
+    struct ovsdb_row *ro_row = (struct ovsdb_row *) ro_row_;
+
+    if (ro_row->txn_row) {
+        assert(ro_row == ro_row->txn_row->new);
+        return ro_row;
+    } else {
+        struct ovsdb_table *table = ro_row->table;
+        struct ovsdb_txn_table *txn_table;
+        struct ovsdb_row *rw_row;
+
+        txn_table = ovsdb_txn_create_txn_table(txn, table);
+        rw_row = ovsdb_row_clone(ro_row);
+        uuid_generate(ovsdb_row_get_version_rw(rw_row));
+        rw_row->txn_row = ovsdb_txn_row_create(txn_table, ro_row, rw_row);
+        hmap_replace(&table->rows, &ro_row->hmap_node, &rw_row->hmap_node);
+
+        return rw_row;
+    }
+}
+
+void
+ovsdb_txn_row_insert(struct ovsdb_txn *txn, struct ovsdb_row *row)
+{
+    uint32_t hash = ovsdb_row_hash(row);
+    struct ovsdb_table *table = row->table;
+    struct ovsdb_txn_table *txn_table;
+
+    uuid_generate(ovsdb_row_get_version_rw(row));
+
+    txn_table = ovsdb_txn_create_txn_table(txn, table);
+    row->txn_row = ovsdb_txn_row_create(txn_table, NULL, row);
+    hmap_insert(&table->rows, &row->hmap_node, hash);
+}
+
+/* 'row' must be assumed destroyed upon return; the caller must not reference
+ * it again. */
+void
+ovsdb_txn_row_delete(struct ovsdb_txn *txn, const struct ovsdb_row *row_)
+{
+    struct ovsdb_row *row = (struct ovsdb_row *) row_;
+    struct ovsdb_table *table = row->table;
+    struct ovsdb_txn_row *txn_row = row->txn_row;
+    struct ovsdb_txn_table *txn_table;
+
+    hmap_remove(&table->rows, &row->hmap_node);
+
+    if (!txn_row) {
+        txn_table = ovsdb_txn_create_txn_table(txn, table);
+        row->txn_row = ovsdb_txn_row_create(txn_table, row, NULL);
+    } else {
+        assert(txn_row->new == row);
+        if (txn_row->old) {
+            txn_row->new = NULL;
+        } else {
+            txn_table = ovsdb_txn_get_txn_table(txn, table);
+            hmap_remove(&txn_table->txn_rows, &txn_row->hmap_node);
+        }
+        ovsdb_row_destroy(row);
+    }
+}
diff --git a/ovsdb/transaction.h b/ovsdb/transaction.h
new file mode 100644 (file)
index 0000000..048bf74
--- /dev/null
@@ -0,0 +1,42 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_TRANSACTION_H
+#define OVSDB_TRANSACTION_H 1
+
+#include <stdbool.h>
+#include "compiler.h"
+
+struct json;
+struct ovsdb;
+struct ovsdb_table;
+struct uuid;
+
+struct ovsdb_txn *ovsdb_txn_create(struct ovsdb *);
+void ovsdb_txn_abort(struct ovsdb_txn *);
+struct ovsdb_error *ovsdb_txn_commit(struct ovsdb_txn *, bool durable);
+
+struct ovsdb_row *ovsdb_txn_row_modify(struct ovsdb_txn *,
+                                       const struct ovsdb_row *);
+void ovsdb_txn_row_insert(struct ovsdb_txn *, struct ovsdb_row *);
+void ovsdb_txn_row_delete(struct ovsdb_txn *, const struct ovsdb_row *);
+
+typedef bool ovsdb_txn_row_cb_func(const struct ovsdb_row *old,
+                                   const struct ovsdb_row *new,
+                                   void *aux);
+void ovsdb_txn_for_each_change(const struct ovsdb_txn *,
+                               ovsdb_txn_row_cb_func *, void *aux);
+
+#endif /* ovsdb/transaction.h */
diff --git a/ovsdb/trigger.c b/ovsdb/trigger.c
new file mode 100644 (file)
index 0000000..1ecfdca
--- /dev/null
@@ -0,0 +1,129 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "trigger.h"
+
+#include <assert.h>
+#include <limits.h>
+
+#include "json.h"
+#include "jsonrpc.h"
+#include "ovsdb.h"
+#include "poll-loop.h"
+
+static bool ovsdb_trigger_try(struct ovsdb *db, struct ovsdb_trigger *,
+                              long long int now);
+static void ovsdb_trigger_complete(struct ovsdb_trigger *);
+
+void
+ovsdb_trigger_init(struct ovsdb *db, struct ovsdb_trigger *trigger,
+                   struct json *request, struct list *completion,
+                   long long int now)
+{
+    list_push_back(&db->triggers, &trigger->node);
+    trigger->completion = completion;
+    trigger->request = request;
+    trigger->result = NULL;
+    trigger->created = now;
+    trigger->timeout_msec = LLONG_MAX;
+    ovsdb_trigger_try(db, trigger, now);
+}
+
+void
+ovsdb_trigger_destroy(struct ovsdb_trigger *trigger)
+{
+    list_remove(&trigger->node);
+    json_destroy(trigger->request);
+    json_destroy(trigger->result);
+}
+
+bool
+ovsdb_trigger_is_complete(const struct ovsdb_trigger *trigger)
+{
+    return trigger->result != NULL;
+}
+
+struct json *
+ovsdb_trigger_steal_result(struct ovsdb_trigger *trigger)
+{
+    struct json *result = trigger->result;
+    trigger->result = NULL;
+    return result;
+}
+
+void
+ovsdb_trigger_run(struct ovsdb *db, long long int now)
+{
+    struct ovsdb_trigger *t, *next;
+    bool run_triggers;
+
+    run_triggers = db->run_triggers;
+    db->run_triggers = false;
+    LIST_FOR_EACH_SAFE (t, next, struct ovsdb_trigger, node, &db->triggers) {
+        if (run_triggers || now - t->created >= t->timeout_msec) {
+            ovsdb_trigger_try(db, t, now);
+        }
+    }
+}
+
+void
+ovsdb_trigger_wait(struct ovsdb *db, long long int now)
+{
+    if (db->run_triggers) {
+        poll_immediate_wake();
+    } else {
+        long long int deadline = LLONG_MAX;
+        struct ovsdb_trigger *t;
+
+        LIST_FOR_EACH (t, struct ovsdb_trigger, node, &db->triggers) {
+            if (t->created < LLONG_MAX - t->timeout_msec) {
+                long long int t_deadline = t->created + t->timeout_msec;
+                if (deadline > t_deadline) {
+                    deadline = t_deadline;
+                    if (now >= deadline) {
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (deadline < LLONG_MAX) {
+            poll_timer_wait(MIN(deadline - now, INT_MAX));
+        }
+    }
+}
+
+static bool
+ovsdb_trigger_try(struct ovsdb *db, struct ovsdb_trigger *t, long long int now)
+{
+    t->result = ovsdb_execute(db, t->request, now - t->created,
+                              &t->timeout_msec);
+    if (t->result) {
+        ovsdb_trigger_complete(t);
+        return true;
+    } else {
+        return false;
+    }
+}
+
+static void
+ovsdb_trigger_complete(struct ovsdb_trigger *t)
+{
+    assert(t->result != NULL);
+    list_remove(&t->node);
+    list_push_back(t->completion, &t->node);
+}
diff --git a/ovsdb/trigger.h b/ovsdb/trigger.h
new file mode 100644 (file)
index 0000000..521b150
--- /dev/null
@@ -0,0 +1,44 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_TRIGGER_H
+#define OVSDB_TRIGGER_H 1
+
+#include "list.h"
+
+struct ovsdb;
+
+struct ovsdb_trigger {
+    struct list node;           /* !result: in struct ovsdb "triggers" list;
+                                 * result: in completion list. */
+    struct list *completion;    /* Completion list. */
+    struct json *request;       /* Database request. */
+    struct json *result;        /* Result (null if none yet). */
+    long long int created;      /* Time created. */
+    long long int timeout_msec; /* Max wait duration. */
+};
+
+void ovsdb_trigger_init(struct ovsdb *, struct ovsdb_trigger *,
+                        struct json *request, struct list *completion,
+                        long long int now);
+void ovsdb_trigger_destroy(struct ovsdb_trigger *);
+
+bool ovsdb_trigger_is_complete(const struct ovsdb_trigger *);
+struct json *ovsdb_trigger_steal_result(struct ovsdb_trigger *);
+
+void ovsdb_trigger_run(struct ovsdb *, long long int now);
+void ovsdb_trigger_wait(struct ovsdb *, long long int now);
+
+#endif /* ovsdb/trigger.h */
index 706aa14..caa2db6 100644 (file)
@@ -5,6 +5,7 @@
 /test-flows
 /test-hash
 /test-hmap
+/test-json
 /test-list
 /test-stp
 /test-type-props
diff --git a/tests/aes128.at b/tests/aes128.at
new file mode 100644 (file)
index 0000000..991f627
--- /dev/null
@@ -0,0 +1,146 @@
+AT_BANNER([AES-128 unit tests])
+
+m4_define([AES128_CHECK], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([aes128])
+   OVS_CHECK_LCOV([test-aes128 $2 $3], [0], [$4
+], [])
+   AT_CLEANUP])
+
+AES128_CHECK(
+  [wikipedia test vector 1],
+  [00010203050607080a0b0c0d0f101112],
+  [506812a45f08c889b97f5980038b8359],
+  [d8f532538289ef7d06b506a4fd5be9c9])
+
+AES128_CHECK(
+  [wikipedia test vector 2],
+  [95A8EE8E89979B9EFDCBC6EB9797528D],
+  [4ec137a426dabf8aa0beb8bc0c2b89d6],
+  [d9b65d1232ba0199cdbd487b2a1fd646])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 0],
+  [10a58869d74be5a374cf867cfb473859],
+  [00000000000000000000000000000000],
+  [6d251e6944b051e04eaa6fb4dbf78465])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 1],
+  [caea65cdbb75e9169ecd22ebe6e54675],
+  [00000000000000000000000000000000],
+  [6e29201190152df4ee058139def610bb])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 2],
+  [a2e2fa9baf7d20822ca9f0542f764a41],
+  [00000000000000000000000000000000],
+  [c3b44b95d9d2f25670eee9a0de099fa3])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 3],
+  [b6364ac4e1de1e285eaf144a2415f7a0],
+  [00000000000000000000000000000000],
+  [5d9b05578fc944b3cf1ccf0e746cd581])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 4],
+  [64cf9c7abc50b888af65f49d521944b2],
+  [00000000000000000000000000000000],
+  [f7efc89d5dba578104016ce5ad659c05])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 5],
+  [47d6742eefcc0465dc96355e851b64d9],
+  [00000000000000000000000000000000],
+  [0306194f666d183624aa230a8b264ae7])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 6],
+  [3eb39790678c56bee34bbcdeccf6cdb5],
+  [00000000000000000000000000000000],
+  [858075d536d79ccee571f7d7204b1f67])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 7],
+  [64110a924f0743d500ccadae72c13427],
+  [00000000000000000000000000000000],
+  [35870c6a57e9e92314bcb8087cde72ce])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 8],
+  [18d8126516f8a12ab1a36d9f04d68e51],
+  [00000000000000000000000000000000],
+  [6c68e9be5ec41e22c825b7c7affb4363])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 9],
+  [f530357968578480b398a3c251cd1093],
+  [00000000000000000000000000000000],
+  [f5df39990fc688f1b07224cc03e86cea])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 10],
+  [da84367f325d42d601b4326964802e8e],
+  [00000000000000000000000000000000],
+  [bba071bcb470f8f6586e5d3add18bc66])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 11],
+  [e37b1c6aa2846f6fdb413f238b089f23],
+  [00000000000000000000000000000000],
+  [43c9f7e62f5d288bb27aa40ef8fe1ea8])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 12],
+  [6c002b682483e0cabcc731c253be5674],
+  [00000000000000000000000000000000],
+  [3580d19cff44f1014a7c966a69059de5])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 13],
+  [143ae8ed6555aba96110ab58893a8ae1],
+  [00000000000000000000000000000000],
+  [806da864dd29d48deafbe764f8202aef])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 14],
+  [b69418a85332240dc82492353956ae0c],
+  [00000000000000000000000000000000],
+  [a303d940ded8f0baff6f75414cac5243])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 15],
+  [71b5c08a1993e1362e4d0ce9b22b78d5],
+  [00000000000000000000000000000000],
+  [c2dabd117f8a3ecabfbb11d12194d9d0])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 16],
+  [e234cdca2606b81f29408d5f6da21206],
+  [00000000000000000000000000000000],
+  [fff60a4740086b3b9c56195b98d91a7b])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 17],
+  [13237c49074a3da078dc1d828bb78c6f],
+  [00000000000000000000000000000000],
+  [8146a08e2357f0caa30ca8c94d1a0544])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 18],
+  [3071a2a48fe6cbd04f1a129098e308f8],
+  [00000000000000000000000000000000],
+  [4b98e06d356deb07ebb824e5713f7be3])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 19],
+  [90f42ec0f68385f2ffc5dfc03a654dce],
+  [00000000000000000000000000000000],
+  [7a20a53d460fc9ce0423a7a0764c6cf2])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 20],
+  [febd9a24d8b65c1c787d50a4ed3619a9],
+  [00000000000000000000000000000000],
+  [f4a70d8af877f9b02b4c40df57d45b17])
index 7659a26..cb962a8 100644 (file)
@@ -8,6 +8,30 @@ TESTSUITE_AT = \
        tests/testsuite.at \
        tests/lcov-pre.at \
        tests/library.at \
+       tests/dir_name.at \
+       tests/aes128.at \
+       tests/uuid.at \
+       tests/json.at \
+       tests/jsonrpc.at \
+       tests/timeval.at \
+       tests/lockfile.at \
+       tests/reconnect.at \
+       tests/ovsdb.at \
+       tests/ovsdb-log.at \
+       tests/ovsdb-types.at \
+       tests/ovsdb-data.at \
+       tests/ovsdb-column.at \
+       tests/ovsdb-table.at \
+       tests/ovsdb-row.at \
+       tests/ovsdb-condition.at \
+       tests/ovsdb-query.at \
+       tests/ovsdb-transaction.at \
+       tests/ovsdb-execution.at \
+       tests/ovsdb-trigger.at \
+       tests/ovsdb-file.at \
+       tests/ovsdb-server.at \
+       tests/ovsdb-monitor.at \
+       tests/ovsdb-idl.at \
        tests/stp.at \
        tests/ovs-vsctl.at \
        tests/lcov-post.at
@@ -15,7 +39,7 @@ TESTSUITE = $(srcdir)/tests/testsuite
 DISTCLEANFILES += tests/atconfig tests/atlocal $(TESTSUITE)
 
 check-local: tests/atconfig tests/atlocal $(TESTSUITE)
-       $(SHELL) '$(TESTSUITE)' -C tests AUTOTEST_PATH='utilities:vswitchd:tests' $(TESTSUITEFLAGS)
+       $(SHELL) '$(TESTSUITE)' -C tests AUTOTEST_PATH='utilities:vswitchd:ovsdb:tests' $(TESTSUITEFLAGS)
 
 clean-local:
        test ! -f '$(TESTSUITE)' || $(SHELL) '$(TESTSUITE)' -C tests --clean
@@ -37,6 +61,10 @@ $(srcdir)/package.m4: $(top_srcdir)/configure.ac
          echo 'm4_define([AT_PACKAGE_BUGREPORT], [@PACKAGE_BUGREPORT@])'; \
        } >'$(srcdir)/package.m4'
 
+noinst_PROGRAMS += tests/test-aes128
+tests_test_aes128_SOURCES = tests/test-aes128.c
+tests_test_aes128_LDADD = lib/libopenvswitch.a
+
 noinst_PROGRAMS += tests/test-classifier
 tests_test_classifier_SOURCES = tests/test-classifier.c
 tests_test_classifier_LDADD = lib/libopenvswitch.a
@@ -45,6 +73,10 @@ noinst_PROGRAMS += tests/test-csum
 tests_test_csum_SOURCES = tests/test-csum.c
 tests_test_csum_LDADD = lib/libopenvswitch.a
 
+noinst_PROGRAMS += tests/test-dir_name
+tests_test_dir_name_SOURCES = tests/test-dir_name.c
+tests_test_dir_name_LDADD = lib/libopenvswitch.a
+
 noinst_PROGRAMS += tests/test-flows
 tests_test_flows_SOURCES = tests/test-flows.c
 tests_test_flows_LDADD = lib/libopenvswitch.a
@@ -58,14 +90,42 @@ noinst_PROGRAMS += tests/test-hmap
 tests_test_hmap_SOURCES = tests/test-hmap.c
 tests_test_hmap_LDADD = lib/libopenvswitch.a
 
+noinst_PROGRAMS += tests/test-json
+tests_test_json_SOURCES = tests/test-json.c
+tests_test_json_LDADD = lib/libopenvswitch.a
+
+noinst_PROGRAMS += tests/test-jsonrpc
+tests_test_jsonrpc_SOURCES = tests/test-jsonrpc.c
+tests_test_jsonrpc_LDADD = lib/libopenvswitch.a
+
 noinst_PROGRAMS += tests/test-list
 tests_test_list_SOURCES = tests/test-list.c
 tests_test_list_LDADD = lib/libopenvswitch.a
 
+noinst_PROGRAMS += tests/test-lockfile
+tests_test_lockfile_SOURCES = tests/test-lockfile.c
+tests_test_lockfile_LDADD = lib/libopenvswitch.a
+
+noinst_PROGRAMS += tests/test-ovsdb
+tests_test_ovsdb_SOURCES = tests/test-ovsdb.c tests/idltest.c tests/idltest.h
+tests_test_ovsdb_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a
+EXTRA_DIST += tests/uuidfilt.pl tests/idltest.ovsidl
+BUILT_SOURCES += tests/idltest.c tests/idltest.h
+noinst_DATA += tests/idltest.ovsschema
+DISTCLEANFILES += tests/idltest.ovsschema
+
+noinst_PROGRAMS += tests/test-reconnect
+tests_test_reconnect_SOURCES = tests/test-reconnect.c
+tests_test_reconnect_LDADD = lib/libopenvswitch.a
+
 noinst_PROGRAMS += tests/test-sha1
 tests_test_sha1_SOURCES = tests/test-sha1.c
 tests_test_sha1_LDADD = lib/libopenvswitch.a
 
+noinst_PROGRAMS += tests/test-timeval
+tests_test_timeval_SOURCES = tests/test-timeval.c
+tests_test_timeval_LDADD = lib/libopenvswitch.a
+
 noinst_PROGRAMS += tests/test-type-props
 tests_test_type_props_SOURCES = tests/test-type-props.c
 
@@ -77,6 +137,10 @@ noinst_PROGRAMS += tests/test-stp
 tests_test_stp_SOURCES = tests/test-stp.c
 tests_test_stp_LDADD = lib/libopenvswitch.a
 
+noinst_PROGRAMS += tests/test-uuid
+tests_test_uuid_SOURCES = tests/test-uuid.c
+tests_test_uuid_LDADD = lib/libopenvswitch.a
+
 noinst_PROGRAMS += tests/test-vconn
 tests_test_vconn_SOURCES = tests/test-vconn.c
 tests_test_vconn_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
diff --git a/tests/dir_name.at b/tests/dir_name.at
new file mode 100644 (file)
index 0000000..4555e52
--- /dev/null
@@ -0,0 +1,25 @@
+AT_BANNER([test dir_name function])
+
+m4_define([CHECK_DIR_NAME],
+  [AT_SETUP([dir_name("$1") returns "$2"])
+   AT_KEYWORDS([dir_name])
+   OVS_CHECK_LCOV([test-dir_name "AS_ESCAPE($1)"], [0], [$2
+])
+   AT_CLEANUP])
+
+# These are the test cases given in POSIX for dirname().
+CHECK_DIR_NAME([/usr/lib], [/usr])
+CHECK_DIR_NAME([/usr/], [/])
+CHECK_DIR_NAME([usr], [.])
+CHECK_DIR_NAME([/], [/])
+CHECK_DIR_NAME([.], [.])
+CHECK_DIR_NAME([..], [.])
+CHECK_DIR_NAME([//], [//])      # / is also allowed
+CHECK_DIR_NAME([//foo], [//])   # / is also allowed
+CHECK_DIR_NAME([], [.])
+
+# Additional test cases.
+CHECK_DIR_NAME([dir/file], [dir])
+CHECK_DIR_NAME([dir/file/], [dir])
+CHECK_DIR_NAME([dir/file//], [dir])
+CHECK_DIR_NAME([///foo], [/])
diff --git a/tests/idltest.ovsidl b/tests/idltest.ovsidl
new file mode 100644 (file)
index 0000000..ec5cec3
--- /dev/null
@@ -0,0 +1,29 @@
+//
+// This is an ovsdb-idl schema.  The OVSDB IDL compiler, ovsdb-idlc,
+// can translate it into an OVSDB schema (which simply entails
+// deleting some members from the schema) or C headers or source for
+// use with the IDL at runtime.
+//
+
+{"name": "idltest",
+ "idlPrefix": "idltest_",
+ "idlHeader": "\"tests/idltest.h\"",
+ "tables": {
+   "simple": {
+     "columns": {
+       "i": {"type": "integer"},
+       "r": {"type": "real"},
+       "b": {"type": "boolean"},
+       "s": {"type": "string"},
+       "u": {"type": "uuid"},
+       "ia": {"type": {"key": "integer", "min": 0, "max": "unlimited"}},
+       "ra": {"type": {"key": "real", "min": 0, "max": "unlimited"}},
+       "ba": {"type": {"key": "boolean", "min": 0, "max": "unlimited"}},
+       "sa": {"type": {"key": "string", "min": 0, "max": "unlimited"}},
+       "ua": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}},
+   "selfLink": {
+     "columns": {
+       "i": {"type": "integer"},
+       "k": {"type": {"key": "uuid", "keyRefTable": "selfLink"}},
+       "ka": {"type": {"key": "uuid", "keyRefTable": "selfLink",
+                       "min": 0, "max": "unlimited"}}}}}}
diff --git a/tests/json.at b/tests/json.at
new file mode 100644 (file)
index 0000000..54d4f8c
--- /dev/null
@@ -0,0 +1,297 @@
+m4_define([JSON_CHECK_POSITIVE], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([json positive])
+   AT_CHECK([printf %s "AS_ESCAPE([$2])" > input])
+   OVS_CHECK_LCOV([test-json $4 input], [0], [$3
+], [])
+   AT_CLEANUP])
+
+m4_define([JSON_CHECK_NEGATIVE], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([json negative])
+   AT_CHECK([printf %s "AS_ESCAPE([$2])" > input])
+   OVS_CHECK_LCOV([test-json $4 input], [1], [$3
+])
+   AT_CLEANUP])
+
+AT_BANNER([JSON -- arrays])
+
+JSON_CHECK_POSITIVE([empty array], [[ [   ] ]], [[[]]])
+JSON_CHECK_POSITIVE([single-element array], [[ [ 1 ] ]], [[[1]]])
+JSON_CHECK_POSITIVE([2-element array], [[ [ 1, 2 ] ]], [[[1,2]]])
+JSON_CHECK_POSITIVE([many-element array],
+                    [[ [ 1, 2, 3, 4, 5 ] ]],
+                    [[[1,2,3,4,5]]])
+JSON_CHECK_NEGATIVE([missing comma], [[ [ 1, 2, 3 4, 5 ] ]],
+                    [error: syntax error expecting '@:>@' or ','])
+JSON_CHECK_NEGATIVE([trailing comma not allowed], 
+                    [[[1,2,]]], [error: syntax error expecting value])
+JSON_CHECK_NEGATIVE([doubled comma not allowed], 
+                    [[[1,,2]]], [error: syntax error expecting value])
+
+AT_BANNER([JSON -- strings])
+
+JSON_CHECK_POSITIVE([empty string], [[[ "" ]]], [[[""]]])
+JSON_CHECK_POSITIVE([1-character strings], 
+                    [[[ "a", "b", "c" ]]],
+                    [[["a","b","c"]]])
+JSON_CHECK_POSITIVE([escape sequences], 
+  [[[ " \" \\ \/ \b \f \n \r \t" ]]],
+  [[[" \" \\ / \b \f \n \r \t"]]])
+JSON_CHECK_POSITIVE([Unicode escape sequences], 
+  [[[ " \u0022 \u005c \u002F \u0008 \u000c \u000A \u000d \u0009" ]]],
+  [[[" \" \\ / \b \f \n \r \t"]]])
+JSON_CHECK_POSITIVE([surrogate pairs],
+  [[["\ud834\udd1e"]]],
+  [[["𝄞"]]])
+JSON_CHECK_NEGATIVE([a string by itself is not valid JSON], ["xxx"],
+                    [error: syntax error at beginning of input])
+JSON_CHECK_NEGATIVE([end of line in quoted string],
+                    [[["xxx
+"]]],
+                    [error: U+000A must be escaped in quoted string])
+JSON_CHECK_NEGATIVE([formfeed in quoted string],
+                    [[["xxx\f"]]],
+                    [error: U+000C must be escaped in quoted string])
+JSON_CHECK_NEGATIVE([bad escape in quoted string],
+                    [[["\x12"]]],
+                    [error: bad escape \x])
+JSON_CHECK_NEGATIVE([\u must be followed by 4 hex digits],
+                    [[["\u1x"]]],
+                    [error: malformed \u escape])
+JSON_CHECK_NEGATIVE([isolated leading surrogate not allowed],
+                    [[["\ud834xxx"]]],
+                    [error: malformed escaped surrogate pair])
+JSON_CHECK_NEGATIVE([surrogatess must paired properly],
+                    [[["\ud834\u1234"]]],
+                    [error: second half of escaped surrogate pair is not trailing surrogate])
+JSON_CHECK_NEGATIVE([null bytes not allowed], 
+                    [[["\u0000"]]], 
+                    [error: null bytes not supported in quoted strings])
+
+AT_SETUP([end of input in quoted string])
+AT_KEYWORDS([json negative])
+AT_CHECK([printf '\"xxx' | test-json -], [1],
+  [error: unexpected end of input in quoted string
+])
+AT_CLEANUP
+
+AT_BANNER([JSON -- objects])
+
+JSON_CHECK_POSITIVE([empty object], [[{ }]], [[{}]])
+JSON_CHECK_POSITIVE([simple object],
+                    [[{"b": 2, "a": 1, "c": 3}]],
+                    [[{"a":1,"b":2,"c":3}]])
+JSON_CHECK_NEGATIVE([bad value], [[{"a": }, "b": 2]], 
+                    [error: syntax error expecting value])
+JSON_CHECK_NEGATIVE([missing colon], [[{"b": 2, "a" 1, "c": 3}]],
+                    [error: syntax error parsing object expecting ':'])
+JSON_CHECK_NEGATIVE([missing comma], [[{"b": 2 "a" 1, "c": 3}]],
+                    [error: syntax error expecting '}' or ','])
+JSON_CHECK_NEGATIVE([trailing comma not allowed],
+                    [[{"b": 2, "a": 1, "c": 3, }]],
+                    [[error: syntax error parsing object expecting string]])
+JSON_CHECK_NEGATIVE([doubled comma not allowed],
+                    [[{"b": 2, "a": 1,, "c": 3}]],
+                    [[error: syntax error parsing object expecting string]])
+JSON_CHECK_NEGATIVE([names must be strings],
+                    [[{1: 2}]],
+                    [[error: syntax error parsing object expecting string]])
+
+AT_BANNER([JSON -- literal names])
+
+JSON_CHECK_POSITIVE([null], [[[ null ]]], [[[null]]])
+JSON_CHECK_POSITIVE([false], [[[ false ]]], [[[false]]])
+JSON_CHECK_POSITIVE([true], [[[ true ]]], [[[true]]])
+JSON_CHECK_NEGATIVE([a literal by itself is not valid JSON], [null],
+                    [error: syntax error at beginning of input])
+JSON_CHECK_NEGATIVE([nullify is invalid], [[[ nullify ]]], 
+                    [error: invalid keyword 'nullify'])
+JSON_CHECK_NEGATIVE([nubs is invalid], [[[ nubs ]]],
+                    [error: invalid keyword 'nubs'])
+JSON_CHECK_NEGATIVE([xxx is invalid], [[[ xxx ]]], 
+                    [error: invalid keyword 'xxx'])
+
+AT_BANNER([JSON -- numbers])
+
+JSON_CHECK_POSITIVE(
+  [integers expressed as reals],
+  [[[1.0000000000,
+     2.00000000000000000000000000000000000,
+     2e5,
+     2.1234e4,
+     2.1230e3,
+     0e-10000,
+     0e10000]]],
+  [[[1,2,200000,21234,2123,0,0]]])
+JSON_CHECK_POSITIVE(
+  [large integers], 
+  [[[9223372036854775807, -9223372036854775808]]],
+  [[[9223372036854775807,-9223372036854775808]]])
+JSON_CHECK_POSITIVE(
+  [large integers expressed as reals], 
+  [[[9223372036854775807.0, -9223372036854775808.0,
+     92233720.36854775807e11, -9.223372036854775808e18]]],
+  [[[9223372036854775807,-9223372036854775808,9223372036854775807,-9223372036854775808]]])
+# It seems likely that the following test will fail on some system that
+# rounds slightly differently in arithmetic or in printf, but I'd like
+# to keep it this way until we run into such a system.
+JSON_CHECK_POSITIVE(
+  [large integers that overflow to reals], 
+  [[[9223372036854775807000, -92233720368547758080000]]],
+  [[[9.22337203685478e+21,-9.22337203685478e+22]]])
+
+JSON_CHECK_POSITIVE(
+  [negative zero],
+  [[[-0, -0.0, 1e-9999, -1e-9999]]],
+  [[[0,0,0,0]]])
+
+JSON_CHECK_POSITIVE(
+  [reals], 
+  [[[0.0, 1.0, 2.0, 3.0, 3.5, 81.250]]],
+  [[[0,1,2,3,3.5,81.25]]])
+JSON_CHECK_POSITIVE(
+  [scientific notation],
+  [[[1e3, 1E3, 2.5E2, 1e+3, 125e-3, 3.125e-2, 3125e-05, 1.525878906e-5]]],
+  [[[1000,1000,250,1000,0.125,0.03125,0.03125,1.525878906e-05]]])
+JSON_CHECK_POSITIVE(
+  [negative reals], 
+  [[[-0, -1.0, -2.0, -3.0, -3.5, -8.1250]]],
+  [[[0,-1,-2,-3,-3.5,-8.125]]])
+JSON_CHECK_POSITIVE(
+  [negative scientific notation],
+  [[[-1e3, -1E3, -2.5E2, -1e+3, -125e-3, -3.125e-2, -3125e-05, -1.525878906e-5]]],
+  [[[-1000,-1000,-250,-1000,-0.125,-0.03125,-0.03125,-1.525878906e-05]]])
+JSON_CHECK_POSITIVE(
+  [1e-9999 underflows to 0],
+  [[[1e-9999]]],
+  [[[0]]])
+JSON_CHECK_NEGATIVE([a number by itself is not valid JSON], [1],
+                    [error: syntax error at beginning of input])
+JSON_CHECK_NEGATIVE(
+  [leading zeros not allowed],
+  [[[0123]]],
+  [error: leading zeros not allowed])
+JSON_CHECK_NEGATIVE(
+  [1e9999 is too big],
+  [[[1e9999]]],
+  [error: number outside valid range])
+JSON_CHECK_NEGATIVE(
+  [exponent bigger than INT_MAX],
+  [[[1e9999999999999999999]]],
+  [error: exponent outside valid range])
+JSON_CHECK_NEGATIVE(
+  [decimal point must be followed by digit],
+  [[[1.]]],
+  [error: decimal point must be followed by digit])
+JSON_CHECK_NEGATIVE(
+  [exponent must contain at least one digit (1)],
+  [[[1e]]],
+  [error: exponent must contain at least one digit])
+JSON_CHECK_NEGATIVE(
+  [exponent must contain at least one digit (2)],
+  [[[1e+]]],
+  [error: exponent must contain at least one digit])
+JSON_CHECK_NEGATIVE(
+  [exponent must contain at least one digit (3)],
+  [[[1e-]]],
+  [error: exponent must contain at least one digit])
+
+AT_BANNER([JSON -- RFC 4627 examples])
+
+JSON_CHECK_POSITIVE([RFC 4267 object example],
+[[{
+   "Image": {
+       "Width":  800,
+       "Height": 600,
+       "Title":  "View from 15th Floor",
+       "Thumbnail": {
+           "Url":    "http://www.example.com/image/481989943",
+           "Height": 125,
+           "Width":  "100"
+       },
+       "IDs": [116, 943, 234, 38793]
+     }
+}]],
+[[{"Image":{"Height":600,"IDs":[116,943,234,38793],"Thumbnail":{"Height":125,"Url":"http://www.example.com/image/481989943","Width":"100"},"Title":"View from 15th Floor","Width":800}}]])
+
+JSON_CHECK_POSITIVE([RFC 4267 array example],
+[[[
+   {
+      "precision": "zip",
+      "Latitude":  37.7668,
+      "Longitude": -122.3959,
+      "Address":   "",
+      "City":      "SAN FRANCISCO",
+      "State":     "CA",
+      "Zip":       "94107",
+      "Country":   "US"
+   },
+   {
+      "precision": "zip",
+      "Latitude":  37.371991,
+      "Longitude": -122.026020,
+      "Address":   "",
+      "City":      "SUNNYVALE",
+      "State":     "CA",
+      "Zip":       "94085",
+      "Country":   "US"
+   }
+]]],
+[[[{"Address":"","City":"SAN FRANCISCO","Country":"US","Latitude":37.7668,"Longitude":-122.3959,"State":"CA","Zip":"94107","precision":"zip"},{"Address":"","City":"SUNNYVALE","Country":"US","Latitude":37.371991,"Longitude":-122.02602,"State":"CA","Zip":"94085","precision":"zip"}]]])
+
+AT_BANNER([JSON -- pathological cases])
+
+JSON_CHECK_NEGATIVE([trailing garbage], [[[1]null]],
+                    [error: trailing garbage at end of input])
+JSON_CHECK_NEGATIVE([formfeeds are not valid white space],
+                    [[[\f]]], [error: invalid character U+000c])
+JSON_CHECK_NEGATIVE([';' is not a valid token],
+                    [;], [error: invalid character ';'])
+JSON_CHECK_NEGATIVE([arrays nesting too deep],
+                    [m4_for([i], [0], [1002], [1], [@<:@])dnl
+                     m4_for([i], [0], [1002], [1], [@:>@])],
+                    [error: input exceeds maximum nesting depth 1000])
+JSON_CHECK_NEGATIVE([objects nesting too deep],
+                    [m4_for([i], [0], [1002], [1], [{"x":])dnl
+                     m4_for([i], [0], [1002], [1], [}])],
+                    [error: input exceeds maximum nesting depth 1000])
+
+AT_SETUP([input may not be empty])
+AT_KEYWORDS([json negative])
+AT_CHECK([test-json /dev/null], [1], [error: empty input stream
+])
+AT_CLEANUP
+
+AT_BANNER([JSON -- multiple inputs])
+
+JSON_CHECK_POSITIVE([multiple adjacent objects], [[{}{}{}]], [[{}
+{}
+{}]],
+  [--multiple])
+
+JSON_CHECK_POSITIVE([multiple space-separated objects], [[{}  {}  {}]], [[{}
+{}
+{}]],
+  [--multiple])
+
+JSON_CHECK_POSITIVE([multiple objects on separate lines], [[{}
+{}
+{}]], [[{}
+{}
+{}]],
+  [--multiple])
+
+JSON_CHECK_POSITIVE([multiple objects and arrays], [[{}[]{}[]]], [[{}
+[]
+{}
+[]]],
+  [--multiple])
+
+JSON_CHECK_NEGATIVE([garbage between multiple objects], [[{}x{}]], [[{}
+error: invalid keyword 'x'
+{}]], [--multiple])
+
+JSON_CHECK_NEGATIVE([garbage after multiple objects], [[{}{}x]], [[{}
+{}
+error: invalid keyword 'x']], [--multiple])
diff --git a/tests/jsonrpc.at b/tests/jsonrpc.at
new file mode 100644 (file)
index 0000000..d5ebf94
--- /dev/null
@@ -0,0 +1,45 @@
+AT_BANNER([JSON-RPC])
+
+AT_SETUP([JSON-RPC request and successful reply])
+AT_CHECK([test-jsonrpc --detach --pidfile=$PWD/pid listen punix:socket])
+AT_CHECK([test -s pid])
+AT_CHECK([kill -0 `cat pid`])
+AT_CHECK(
+  [[test-jsonrpc request unix:socket echo '[{"a": "b", "x": null}]']], [0],
+  [[{"error":null,"id":0,"result":[{"a":"b","x":null}]}
+]], [ignore], [test ! -e pid || kill `cat pid`])
+AT_CHECK([kill `cat pid`])
+AT_CLEANUP
+
+AT_SETUP([JSON-RPC request and error reply])
+AT_CHECK([test-jsonrpc --detach --pidfile=$PWD/pid listen punix:socket])
+AT_CHECK([test -s pid])
+AT_CHECK([kill -0 `cat pid`])
+AT_CHECK(
+  [[test-jsonrpc request unix:socket bad-request '[]']], [0],
+  [[{"error":{"error":"unknown method"},"id":0,"result":null}
+]], [ignore], [test ! -e pid || kill `cat pid`])
+AT_CHECK([kill `cat pid`])
+AT_CLEANUP
+
+AT_SETUP([JSON-RPC notification])
+AT_CHECK([test-jsonrpc --detach --pidfile=$PWD/pid listen punix:socket])
+AT_CHECK([test -s pid])
+# When a daemon dies it deletes its pidfile, so make a copy.
+AT_CHECK([cp pid pid2])
+AT_CHECK([kill -0 `cat pid2`])
+OVS_CHECK_LCOV([[test-jsonrpc notify unix:socket shutdown '[]']], [0], [], 
+  [ignore], [kill `cat pid2`])
+AT_CHECK(
+  [pid=`cat pid2`
+   # First try a quick sleep, so that the test completes very quickly
+   # in the normal case.  POSIX doesn't require fractional times to
+   # work, so this might not work.
+   sleep 0.1; if kill -0 $pid; then :; else echo success; exit 0; fi
+   # Then wait up to 2 seconds.
+   sleep 1; if kill -0 $pid; then :; else echo success; exit 0; fi
+   sleep 1; if kill -0 $pid; then :; else echo success; exit 0; fi
+   echo failure; exit 1], [0], [success
+], [ignore])
+AT_CHECK([test ! -e pid])
+AT_CLEANUP
index c48828e..ffcd4b8 100644 (file)
@@ -11,6 +11,7 @@ OVS_CHECK_LCOV([test-csum], [0], [ignore])
 AT_CLEANUP
 
 AT_SETUP([test flow classifier])
+AT_KEYWORDS([slow])
 OVS_CHECK_LCOV([test-classifier], [0], [ignore])
 AT_CLEANUP
 
diff --git a/tests/lockfile.at b/tests/lockfile.at
new file mode 100644 (file)
index 0000000..90a142c
--- /dev/null
@@ -0,0 +1,20 @@
+AT_BANNER([lockfile unit tests])
+
+m4_define([CHECK_LOCKFILE],
+  [AT_SETUP([m4_translit([$1], [_], [ ])])
+   AT_KEYWORDS([lockfile])
+   OVS_CHECK_LCOV([test-lockfile $1], [0], [$1: success (m4_if(
+     [$2], [1], [$2 child], [$2 children]))
+])
+   AT_CLEANUP])
+
+CHECK_LOCKFILE([lock_and_unlock], [0])
+CHECK_LOCKFILE([lock_and_unlock_twice], [0])
+CHECK_LOCKFILE([lock_blocks_same_process], [0])
+CHECK_LOCKFILE([lock_blocks_same_process_twice], [0])
+CHECK_LOCKFILE([lock_blocks_other_process], [1])
+CHECK_LOCKFILE([lock_twice_blocks_other_process], [1])
+CHECK_LOCKFILE([lock_and_unlock_allows_other_process], [1])
+CHECK_LOCKFILE([lock_timeout_gets_the_lock], [1])
+CHECK_LOCKFILE([lock_timeout_runs_out], [1])
+CHECK_LOCKFILE([lock_multiple], [0])
diff --git a/tests/ovsdb-column.at b/tests/ovsdb-column.at
new file mode 100644 (file)
index 0000000..03cd8dc
--- /dev/null
@@ -0,0 +1,18 @@
+AT_BANNER([OVSDB -- columns])
+
+OVSDB_CHECK_POSITIVE([ordinary column],
+  [[parse-column mycol '{"type": "integer"}']],
+  [[{"type":"integer"}]])
+
+OVSDB_CHECK_POSITIVE([immutable column],
+  [[parse-column mycol '{"type": "real", "mutable": false}']],
+  [[{"mutable":false,"type":"real"}]])
+
+OVSDB_CHECK_POSITIVE([ephemeral column],
+  [[parse-column mycol '{"type": "uuid", "ephemeral": true}']],
+  [[{"ephemeral":true,"type":"uuid"}]])
+
+OVSDB_CHECK_POSITIVE([column with comment],
+  [[parse-column mycol '{"type": "boolean",
+                         "comment": "extra information about this column"}']],
+  [[{"comment":"extra information about this column","type":"boolean"}]])
diff --git a/tests/ovsdb-condition.at b/tests/ovsdb-condition.at
new file mode 100644 (file)
index 0000000..3715b6d
--- /dev/null
@@ -0,0 +1,558 @@
+AT_BANNER([OVSDB -- conditions])
+
+OVSDB_CHECK_POSITIVE([null condition],
+  [[parse-conditions \
+    '{"columns": {"name": {"type": "string"}}}' \
+    '[]']],
+  [[[]]])
+
+OVSDB_CHECK_POSITIVE([conditions on scalars],
+  [[parse-conditions \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[["i", "==", 0]]' \
+    '[["i", "!=", 1]]' \
+    '[["i", "<", 2]]' \
+    '[["i", "<=", 3]]' \
+    '[["i", ">", 4]]' \
+    '[["i", ">=", 5]]' \
+    '[["i", "includes", 6]]' \
+    '[["i", "excludes", 7]]' \
+    '[["r", "==", 0.5]]' \
+    '[["r", "!=", 1.5]]' \
+    '[["r", "<", 2.5]]' \
+    '[["r", "<=", 3.5]]' \
+    '[["r", ">", 4.5]]' \
+    '[["r", ">=", 5.5]]' \
+    '[["r", "includes", 6.5]]' \
+    '[["r", "excludes", 7.5]]' \
+    '[["b", "==", true]]' \
+    '[["b", "!=", false]]' \
+    '[["b", "includes", false]]' \
+    '[["b", "excludes", true]]' \
+    '[["s", "==", "a"]]' \
+    '[["s", "!=", "b"]]' \
+    '[["s", "includes", "c"]]' \
+    '[["s", "excludes", "d"]]' \
+    '[["u", "==", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]]' \
+    '[["u", "!=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+    '[["u", "includes", ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]' \
+    '[["u", "excludes", ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]]]']],
+  [[[["i","==",0]]
+[["i","!=",1]]
+[["i","<",2]]
+[["i","<=",3]]
+[["i",">",4]]
+[["i",">=",5]]
+[["i","includes",6]]
+[["i","excludes",7]]
+[["r","==",0.5]]
+[["r","!=",1.5]]
+[["r","<",2.5]]
+[["r","<=",3.5]]
+[["r",">",4.5]]
+[["r",">=",5.5]]
+[["r","includes",6.5]]
+[["r","excludes",7.5]]
+[["b","==",true]]
+[["b","!=",false]]
+[["b","includes",false]]
+[["b","excludes",true]]
+[["s","==","a"]]
+[["s","!=","b"]]
+[["s","includes","c"]]
+[["s","excludes","d"]]
+[["u","==",["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]
+[["u","!=",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]]
+[["u","includes",["uuid","ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]
+[["u","excludes",["uuid","62315898-64e0-40b9-b26f-ff74225303e6"]]]]],
+  [condition])
+
+AT_SETUP([disallowed conditions on scalars])
+AT_KEYWORDS([ovsdb negative condition])
+OVS_CHECK_LCOV([[test-ovsdb parse-conditions \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[["b", ">", true]]' \
+    '[["b", ">=", false]]' \
+    '[["b", "<", false]]' \
+    '[["b", "<=", false]]' \
+    '[["s", ">", "a"]]' \
+    '[["s", ">=", "b"]]' \
+    '[["s", "<", "c"]]' \
+    '[["s", "<=", "d"]]' \
+    '[["u", ">", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]]' \
+    '[["u", ">=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+    '[["u", "<", ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]' \
+    '[["u", "<=", ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]]]']],
+  [1], [],
+  [[test-ovsdb: syntax "["b",">",true]": syntax error: Type mismatch: ">" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b",">=",false]": syntax error: Type mismatch: ">=" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","<",false]": syntax error: Type mismatch: "<" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","<=",false]": syntax error: Type mismatch: "<=" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["s",">","a"]": syntax error: Type mismatch: ">" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s",">=","b"]": syntax error: Type mismatch: ">=" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","<","c"]": syntax error: Type mismatch: "<" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","<=","d"]": syntax error: Type mismatch: "<=" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["u",">",["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]": syntax error: Type mismatch: ">" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u",">=",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]": syntax error: Type mismatch: ">=" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u","<",["uuid","ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]": syntax error: Type mismatch: "<" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u","<=",["uuid","62315898-64e0-40b9-b26f-ff74225303e6"]]": syntax error: Type mismatch: "<=" operator may not be applied to column u of type uuid.
+]])
+AT_CLEANUP
+
+OVSDB_CHECK_POSITIVE([conditions on sets],
+  [[parse-conditions \
+    '{"columns":
+        {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}},
+         "r": {"type": {"key": "real", "min": 0, "max": "unlimited"}},
+         "b": {"type": {"key": "boolean", "min": 0, "max": "unlimited"}},
+        "s": {"type": {"key": "string", "min": 0, "max": "unlimited"}},
+         "u": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}}' \
+    '[["i", "==", ["set", []]]]' \
+    '[["i", "!=", ["set", [1]]]]' \
+    '[["i", "includes", ["set", [1, 2]]]]' \
+    '[["i", "excludes", ["set", [1, 2, 3]]]]' \
+    '[["r", "==", ["set", []]]]' \
+    '[["r", "!=", ["set", [1.5]]]]' \
+    '[["r", "includes", ["set", [1.5, 2.5]]]]' \
+    '[["r", "excludes", ["set", [1.5, 2.5, 3.5]]]]' \
+    '[["b", "==", ["set", [true]]]]' \
+    '[["b", "!=", ["set", [false]]]]' \
+    '[["b", "includes", ["set", [false]]]]' \
+    '[["b", "excludes", ["set", [true, false]]]]' \
+    '[["s", "==", ["set", ["a"]]]]' \
+    '[["s", "!=", ["set", ["a", "b"]]]]' \
+    '[["s", "includes", ["set", ["c"]]]]' \
+    '[["s", "excludes", ["set", ["c", "d"]]]]' \
+    '[["u", "==", 
+       ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]' \
+    '[["u", "==", 
+       ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"],
+                ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]]]' \
+    '[["u", "includes", 
+       ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"],
+                ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"],
+                ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]]]' \
+    '[["u", "excludes", 
+       ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"],
+                ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"],
+                ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"],
+                ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]]]]]' \
+]],
+  [[[["i","==",["set",[]]]]
+[["i","!=",["set",[1]]]]
+[["i","includes",["set",[1,2]]]]
+[["i","excludes",["set",[1,2,3]]]]
+[["r","==",["set",[]]]]
+[["r","!=",["set",[1.5]]]]
+[["r","includes",["set",[1.5,2.5]]]]
+[["r","excludes",["set",[1.5,2.5,3.5]]]]
+[["b","==",["set",[true]]]]
+[["b","!=",["set",[false]]]]
+[["b","includes",["set",[false]]]]
+[["b","excludes",["set",[false,true]]]]
+[["s","==",["set",["a"]]]]
+[["s","!=",["set",["a","b"]]]]
+[["s","includes",["set",["c"]]]]
+[["s","excludes",["set",["c","d"]]]]
+[["u","==",["set",[["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]
+[["u","==",["set",[["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"],["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]
+[["u","includes",["set",[["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"],["uuid","ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"],["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]
+[["u","excludes",["set",[["uuid","62315898-64e0-40b9-b26f-ff74225303e6"],["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"],["uuid","ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"],["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]]],
+  [condition])
+
+OVSDB_CHECK_POSITIVE([condition sorting],
+  [[parse-conditions \
+    '{"columns": {"i": {"type": "integer"}}}' \
+    '[["i", "excludes", 7],
+      ["i", "!=", 8],
+      ["i", "==", 1],
+      ["i", "includes", 2],
+      ["i", "<=", 3],
+      ["i", "<", 4],
+      ["i", ">", 6],
+      ["i", ">=", 5],
+      ["_uuid", "==", ["uuid", "d50e85c6-8ae7-4b16-b69e-4395928bd9be"]]]']],
+  [[[["_uuid","==",["uuid","d50e85c6-8ae7-4b16-b69e-4395928bd9be"]],["i","==",1],["i","includes",2],["i","<=",3],["i","<",4],["i",">=",5],["i",">",6],["i","excludes",7],["i","!=",8]]]])
+
+OVSDB_CHECK_POSITIVE([evaluating null condition],
+  [[evaluate-conditions \
+    '{"columns": {"i": {"type": "integer"}}}' \
+    '[[]]' \
+    '[{"i": 0},
+      {"i": 1},
+      {"i": 2}']]],
+  [condition  0: TTT])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on integers],
+  [[evaluate-conditions \
+    '{"columns": {"i": {"type": "integer"}}}' \
+    '[[["i", "<", 1]],
+      [["i", "<=", 1]],
+      [["i", "==", 1]],
+      [["i", "!=", 1]],
+      [["i", ">=", 1]],
+      [["i", ">", 1]],
+      [["i", "includes", 1]],
+      [["i", "excludes", 1]]]' \
+    '[{"i": 0},
+      {"i": 1},
+      {"i": 2}']]],
+  [condition  0: T--
+condition  1: TT-
+condition  2: -T-
+condition  3: T-T
+condition  4: -TT
+condition  5: --T
+condition  6: -T-
+condition  7: T-T], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on reals],
+  [[evaluate-conditions \
+    '{"columns": {"r": {"type": "real"}}}' \
+    '[[["r", "<", 5.0]],
+      [["r", "<=", 5.0]],
+      [["r", "==", 5.0]],
+      [["r", "!=", 5.0]],
+      [["r", ">=", 5.0]],
+      [["r", ">", 5.0]],
+      [["r", "includes", 5.0]],
+      [["r", "excludes", 5.0]]]' \
+    '[{"r": 0},
+      {"r": 5.0},
+      {"r": 5.1}']]],
+  [condition  0: T--
+condition  1: TT-
+condition  2: -T-
+condition  3: T-T
+condition  4: -TT
+condition  5: --T
+condition  6: -T-
+condition  7: T-T], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on booleans],
+  [[evaluate-conditions \
+    '{"columns": {"b": {"type": "boolean"}}}' \
+    '[[["b", "==", true]],
+      [["b", "!=", true]],
+      [["b", "includes", true]],
+      [["b", "excludes", true]],
+      [["b", "==", false]],
+      [["b", "!=", false]],
+      [["b", "includes", false]],
+      [["b", "excludes", false]]]' \
+    '[{"b": true},
+      {"b": false}']]],
+  [condition  0: T-
+condition  1: -T
+condition  2: T-
+condition  3: -T
+condition  4: -T
+condition  5: T-
+condition  6: -T
+condition  7: T-], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on strings],
+  [[evaluate-conditions \
+    '{"columns": {"s": {"type": "string"}}}' \
+    '[[["s", "==", ""]],
+      [["s", "!=", ""]],
+      [["s", "includes", ""]],
+      [["s", "excludes", ""]],
+      [["s", "==", "foo"]],
+      [["s", "!=", "foo"]],
+      [["s", "includes", "foo"]],
+      [["s", "excludes", "foo"]]]' \
+    '[{"s": ""},
+      {"s": "foo"},
+      {"s": "xxx"}']]],
+  [condition  0: T--
+condition  1: -TT
+condition  2: T--
+condition  3: -TT
+condition  4: -T-
+condition  5: T-T
+condition  6: -T-
+condition  7: T-T], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on UUIDs],
+  [[evaluate-conditions \
+    '{"columns": {"u": {"type": "uuid"}}}' \
+    '[[["u", "==", ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]]],
+      [["u", "!=", ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]]],
+      [["u", "includes", ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]]],
+      [["u", "excludes", ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]]],
+      [["u", "==", ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]]],
+      [["u", "!=", ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]]],
+      [["u", "includes", ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]]],
+      [["u", "excludes", ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]]]]' \
+    '[{"u": ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]},
+      {"u": ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]},
+      {"u": ["uuid", "00000000-0000-0000-0000-000000000000"]}']]],
+  [condition  0: T--
+condition  1: -TT
+condition  2: T--
+condition  3: -TT
+condition  4: -T-
+condition  5: T-T
+condition  6: -T-
+condition  7: T-T], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on sets],
+  [[evaluate-conditions \
+    '{"columns": {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}}}}' \
+    '[[["i", "==", ["set", []]]],
+      [["i", "==", ["set", [0]]]],
+      [["i", "==", ["set", [1]]]],
+      [["i", "==", ["set", [0, 1]]]],
+      [["i", "==", ["set", [2]]]],
+      [["i", "==", ["set", [2, 0]]]],
+      [["i", "==", ["set", [2, 1]]]],
+      [["i", "==", ["set", [2, 1, 0]]]],
+      [["i", "!=", ["set", []]]],
+      [["i", "!=", ["set", [0]]]],
+      [["i", "!=", ["set", [1]]]],
+      [["i", "!=", ["set", [0, 1]]]],
+      [["i", "!=", ["set", [2]]]],
+      [["i", "!=", ["set", [2, 0]]]],
+      [["i", "!=", ["set", [2, 1]]]],
+      [["i", "!=", ["set", [2, 1, 0]]]],
+      [["i", "includes", ["set", []]]],
+      [["i", "includes", ["set", [0]]]],
+      [["i", "includes", ["set", [1]]]],
+      [["i", "includes", ["set", [0, 1]]]],
+      [["i", "includes", ["set", [2]]]],
+      [["i", "includes", ["set", [2, 0]]]],
+      [["i", "includes", ["set", [2, 1]]]],
+      [["i", "includes", ["set", [2, 1, 0]]]],
+      [["i", "excludes", ["set", []]]],
+      [["i", "excludes", ["set", [0]]]],
+      [["i", "excludes", ["set", [1]]]],
+      [["i", "excludes", ["set", [0, 1]]]],
+      [["i", "excludes", ["set", [2]]]],
+      [["i", "excludes", ["set", [2, 0]]]],
+      [["i", "excludes", ["set", [2, 1]]]],
+      [["i", "excludes", ["set", [2, 1, 0]]]]]' \
+    '[{"i": ["set", []]},
+      {"i": ["set", [0]]},
+      {"i": ["set", [1]]},
+      {"i": ["set", [0, 1]]},
+      {"i": ["set", [2]]},
+      {"i": ["set", [2, 0]]},
+      {"i": ["set", [2, 1]]},
+      {"i": ["set", [2, 1, 0]]}]']],
+  [dnl
+condition  0: T---- ---
+condition  1: -T--- ---
+condition  2: --T-- ---
+condition  3: ---T- ---
+condition  4: ----T ---
+condition  5: ----- T--
+condition  6: ----- -T-
+condition  7: ----- --T
+condition  8: -TTTT TTT
+condition  9: T-TTT TTT
+condition 10: TT-TT TTT
+condition 11: TTT-T TTT
+condition 12: TTTT- TTT
+condition 13: TTTTT -TT
+condition 14: TTTTT T-T
+condition 15: TTTTT TT-
+condition 16: TTTTT TTT
+condition 17: -T-T- T-T
+condition 18: --TT- -TT
+condition 19: ---T- --T
+condition 20: ----T TTT
+condition 21: ----- T-T
+condition 22: ----- -TT
+condition 23: ----- --T
+condition 24: TTTTT TTT
+condition 25: T-T-T -T-
+condition 26: TT--T T--
+condition 27: T---T ---
+condition 28: TTTT- ---
+condition 29: T-T-- ---
+condition 30: TT--- ---
+condition 31: T---- ---], [condition])
+
+# This is the same as the "set" test except that it adds values,
+# all of which always match.
+OVSDB_CHECK_POSITIVE([evaluating conditions on maps (1)],
+  [[evaluate-conditions \
+    '{"columns": {"i": {"type": {"key": "integer",
+                                 "value": "boolean",
+                                 "min": 0,
+                                 "max": "unlimited"}}}}' \
+    '[[["i", "==", ["map", []]]],
+      [["i", "==", ["map", [[0, true]]]]],
+      [["i", "==", ["map", [[1, false]]]]],
+      [["i", "==", ["map", [[0, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true]]]]],
+      [["i", "==", ["map", [[2, true], [0, true]]]]],
+      [["i", "==", ["map", [[2, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "!=", ["map", []]]],
+      [["i", "!=", ["map", [[0, true]]]]],
+      [["i", "!=", ["map", [[1, false]]]]],
+      [["i", "!=", ["map", [[0, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true]]]]],
+      [["i", "!=", ["map", [[2, true], [0, true]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "includes", ["map", []]]],
+      [["i", "includes", ["map", [[0, true]]]]],
+      [["i", "includes", ["map", [[1, false]]]]],
+      [["i", "includes", ["map", [[0, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true]]]]],
+      [["i", "includes", ["map", [[2, true], [0, true]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "excludes", ["map", []]]],
+      [["i", "excludes", ["map", [[0, true]]]]],
+      [["i", "excludes", ["map", [[1, false]]]]],
+      [["i", "excludes", ["map", [[0, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [0, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false], [0, true]]]]]]' \
+    '[{"i": ["map", []]},
+      {"i": ["map", [[0, true]]]},
+      {"i": ["map", [[1, false]]]},
+      {"i": ["map", [[0, true], [1, false]]]},
+      {"i": ["map", [[2, true]]]},
+      {"i": ["map", [[2, true], [0, true]]]},
+      {"i": ["map", [[2, true], [1, false]]]},
+      {"i": ["map", [[2, true], [1, false], [0, true]]]}]']],
+  [dnl
+condition  0: T---- ---
+condition  1: -T--- ---
+condition  2: --T-- ---
+condition  3: ---T- ---
+condition  4: ----T ---
+condition  5: ----- T--
+condition  6: ----- -T-
+condition  7: ----- --T
+condition  8: -TTTT TTT
+condition  9: T-TTT TTT
+condition 10: TT-TT TTT
+condition 11: TTT-T TTT
+condition 12: TTTT- TTT
+condition 13: TTTTT -TT
+condition 14: TTTTT T-T
+condition 15: TTTTT TT-
+condition 16: TTTTT TTT
+condition 17: -T-T- T-T
+condition 18: --TT- -TT
+condition 19: ---T- --T
+condition 20: ----T TTT
+condition 21: ----- T-T
+condition 22: ----- -TT
+condition 23: ----- --T
+condition 24: TTTTT TTT
+condition 25: T-T-T -T-
+condition 26: TT--T T--
+condition 27: T---T ---
+condition 28: TTTT- ---
+condition 29: T-T-- ---
+condition 30: TT--- ---
+condition 31: T---- ---], [condition])
+
+# This is the same as the "set" test except that it adds values,
+# and those values don't always match.
+OVSDB_CHECK_POSITIVE([evaluating conditions on maps (2)],
+  [[evaluate-conditions \
+    '{"columns": {"i": {"type": {"key": "integer",
+                                 "value": "boolean",
+                                 "min": 0,
+                                 "max": "unlimited"}}}}' \
+    '[[["i", "==", ["map", []]]],
+      [["i", "==", ["map", [[0, true]]]]],
+      [["i", "==", ["map", [[1, false]]]]],
+      [["i", "==", ["map", [[0, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true]]]]],
+      [["i", "==", ["map", [[2, true], [0, true]]]]],
+      [["i", "==", ["map", [[2, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "!=", ["map", []]]],
+      [["i", "!=", ["map", [[0, true]]]]],
+      [["i", "!=", ["map", [[1, false]]]]],
+      [["i", "!=", ["map", [[0, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true]]]]],
+      [["i", "!=", ["map", [[2, true], [0, true]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "includes", ["map", []]]],
+      [["i", "includes", ["map", [[0, true]]]]],
+      [["i", "includes", ["map", [[1, false]]]]],
+      [["i", "includes", ["map", [[0, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true]]]]],
+      [["i", "includes", ["map", [[2, true], [0, true]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "excludes", ["map", []]]],
+      [["i", "excludes", ["map", [[0, true]]]]],
+      [["i", "excludes", ["map", [[1, false]]]]],
+      [["i", "excludes", ["map", [[0, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [0, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false], [0, true]]]]]]' \
+    '[{"i": ["map", []]},
+      {"i": ["map", [[0, true]]]},
+      {"i": ["map", [[0, false]]]},
+      {"i": ["map", [[1, false]]]},
+      {"i": ["map", [[1, true]]]},
+
+      {"i": ["map", [[0, true], [1, false]]]},
+      {"i": ["map", [[0, true], [1, true]]]},
+      {"i": ["map", [[2, true]]]},
+      {"i": ["map", [[2, false]]]},
+      {"i": ["map", [[2, true], [0, true]]]},
+
+      {"i": ["map", [[2, false], [0, true]]]},
+      {"i": ["map", [[2, true], [1, false]]]},
+      {"i": ["map", [[2, true], [1, true]]]},
+      {"i": ["map", [[2, true], [1, false], [0, true]]]},
+      {"i": ["map", [[2, true], [1, false], [0, false]]]}]']],
+  [dnl
+condition  0: T---- ----- -----
+condition  1: -T--- ----- -----
+condition  2: ---T- ----- -----
+condition  3: ----- T---- -----
+condition  4: ----- --T-- -----
+condition  5: ----- ----T -----
+condition  6: ----- ----- -T---
+condition  7: ----- ----- ---T-
+condition  8: -TTTT TTTTT TTTTT
+condition  9: T-TTT TTTTT TTTTT
+condition 10: TTT-T TTTTT TTTTT
+condition 11: TTTTT -TTTT TTTTT
+condition 12: TTTTT TT-TT TTTTT
+condition 13: TTTTT TTTT- TTTTT
+condition 14: TTTTT TTTTT T-TTT
+condition 15: TTTTT TTTTT TTT-T
+condition 16: TTTTT TTTTT TTTTT
+condition 17: -T--- TT--T T--T-
+condition 18: ---T- T---- -T-TT
+condition 19: ----- T---- ---T-
+condition 20: ----- --T-T -TTTT
+condition 21: ----- ----T ---T-
+condition 22: ----- ----- -T-TT
+condition 23: ----- ----- ---T-
+condition 24: TTTTT TTTTT TTTTT
+condition 25: T-TTT --TT- -TT-T
+condition 26: TTT-T -TTTT T-T--
+condition 27: T-T-T --TT- --T--
+condition 28: TTTTT TT-T- T----
+condition 29: T-TTT ---T- -----
+condition 30: TTT-T -T-T- T----
+condition 31: T-T-T ---T- -----], [condition])
diff --git a/tests/ovsdb-data.at b/tests/ovsdb-data.at
new file mode 100644 (file)
index 0000000..bedaf70
--- /dev/null
@@ -0,0 +1,259 @@
+AT_BANNER([OVSDB -- atoms])
+
+OVSDB_CHECK_POSITIVE([integer atom], 
+  [[parse-atoms '["integer"]' \
+    '[0]' \
+    '[-1]' \
+    '[1e3]' \
+    '[9223372036854775807]' \
+    '[-9223372036854775808]' ]], 
+  [0
+-1
+1000
+9223372036854775807
+-9223372036854775808])
+
+OVSDB_CHECK_POSITIVE([real atom], 
+  [[parse-atoms '["real"]' \
+    '[0]' \
+    '[0.0]' \
+    '[-0.0]' \
+    '[-1.25]' \
+    '[1e3]' \
+    '[1e37]' \
+    '[0.00390625]' ]], 
+  [0
+0
+0
+-1.25
+1000
+1e+37
+0.00390625])
+
+OVSDB_CHECK_POSITIVE([boolean atom],
+  [[parse-atoms '["boolean"]' '[true]' '[false]' ]],
+  [true
+false])
+
+OVSDB_CHECK_POSITIVE([string atom],
+  [[parse-atoms '["string"]' '[""]' '["true"]' '["\"\\\/\b\f\n\r\t"]']],
+  [""
+"true"
+"\"\\/\b\f\n\r\t"])
+
+OVSDB_CHECK_POSITIVE([uuid atom],
+  [[parse-atoms '["uuid"]' '["uuid", "550e8400-e29b-41d4-a716-446655440000"]']],
+  [[["uuid","550e8400-e29b-41d4-a716-446655440000"]]])
+
+OVSDB_CHECK_POSITIVE([integer atom sorting],
+  [[sort-atoms '["integer"]' '[55,0,-1,2,1]']],
+  [[[-1,0,1,2,55]]])
+
+OVSDB_CHECK_POSITIVE([real atom sorting],
+  [[sort-atoms '["real"]' '[1.25,1.23,0.0,-0.0,-1e99]']],
+  [[[-1e+99,0,0,1.23,1.25]]])
+
+OVSDB_CHECK_POSITIVE([boolean atom sorting],
+  [[sort-atoms '["boolean"]' '[true,false,true,false,false]']],
+  [[[false,false,false,true,true]]])
+
+OVSDB_CHECK_POSITIVE([string atom sorting],
+  [[sort-atoms '["string"]' '["abd","abc","\b","xxx"]']],
+  [[["\b","abc","abd","xxx"]]])
+
+OVSDB_CHECK_POSITIVE([uuid atom sorting],
+  [[sort-atoms '["uuid"]' '[
+    ["uuid", "00000000-0000-0000-0000-000000000001"],
+    ["uuid", "00000000-1000-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-1000-0000-000000000000"],
+    ["uuid", "00010000-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-0000-0000-000000000100"],
+    ["uuid", "00000000-0000-0000-0000-000100000000"],
+    ["uuid", "00000000-0000-0010-0000-000000000000"],
+    ["uuid", "00000100-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-0001-0000-000000000000"],
+    ["uuid", "00000000-0000-0000-0000-000001000000"],
+    ["uuid", "01000000-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-0000-0000-000000001000"],
+    ["uuid", "00000000-0000-0000-0000-000010000000"],
+    ["uuid", "00000000-0000-0000-0000-010000000000"],
+    ["uuid", "00000000-0000-0100-0000-000000000000"],
+    ["uuid", "10000000-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-0000-0000-000000000010"],
+    ["uuid", "00000000-0100-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-0000-0100-000000000000"],
+    ["uuid", "00000000-0000-0000-0001-000000000000"],
+    ["uuid", "00000010-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-0000-0010-000000000000"],
+    ["uuid", "00000000-0000-0000-0000-000000010000"],
+    ["uuid", "00000000-0000-0000-1000-000000000000"],
+    ["uuid", "00000000-0000-0000-0000-100000000000"],
+    ["uuid", "00000000-0000-0000-0000-001000000000"],
+    ["uuid", "00000000-0000-0000-0000-000000100000"],
+    ["uuid", "00000000-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0010-0000-0000-000000000000"],
+    ["uuid", "00100000-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0001-0000-0000-000000000000"],
+    ["uuid", "00000001-0000-0000-0000-000000000000"],
+    ["uuid", "00001000-0000-0000-0000-000000000000"]]']],
+  [[[["uuid","00000000-0000-0000-0000-000000000000"],["uuid","00000000-0000-0000-0000-000000000001"],["uuid","00000000-0000-0000-0000-000000000010"],["uuid","00000000-0000-0000-0000-000000000100"],["uuid","00000000-0000-0000-0000-000000001000"],["uuid","00000000-0000-0000-0000-000000010000"],["uuid","00000000-0000-0000-0000-000000100000"],["uuid","00000000-0000-0000-0000-000001000000"],["uuid","00000000-0000-0000-0000-000010000000"],["uuid","00000000-0000-0000-0000-000100000000"],["uuid","00000000-0000-0000-0000-001000000000"],["uuid","00000000-0000-0000-0000-010000000000"],["uuid","00000000-0000-0000-0000-100000000000"],["uuid","00000000-0000-0000-0001-000000000000"],["uuid","00000000-0000-0000-0010-000000000000"],["uuid","00000000-0000-0000-0100-000000000000"],["uuid","00000000-0000-0000-1000-000000000000"],["uuid","00000000-0000-0001-0000-000000000000"],["uuid","00000000-0000-0010-0000-000000000000"],["uuid","00000000-0000-0100-0000-000000000000"],["uuid","00000000-0000-1000-0000-000000000000"],["uuid","00000000-0001-0000-0000-000000000000"],["uuid","00000000-0010-0000-0000-000000000000"],["uuid","00000000-0100-0000-0000-000000000000"],["uuid","00000000-1000-0000-0000-000000000000"],["uuid","00000001-0000-0000-0000-000000000000"],["uuid","00000010-0000-0000-0000-000000000000"],["uuid","00000100-0000-0000-0000-000000000000"],["uuid","00001000-0000-0000-0000-000000000000"],["uuid","00010000-0000-0000-0000-000000000000"],["uuid","00100000-0000-0000-0000-000000000000"],["uuid","01000000-0000-0000-0000-000000000000"],["uuid","10000000-0000-0000-0000-000000000000"]]]])
+
+OVSDB_CHECK_NEGATIVE([real not acceptable integer atom],
+  [[parse-atoms '["integer"]' '[0.5]' ]],
+  [expected integer])
+
+OVSDB_CHECK_NEGATIVE([string "true" not acceptable boolean atom],
+  [[parse-atoms '["boolean"]' '["true"]' ]],
+  [expected boolean])
+
+OVSDB_CHECK_NEGATIVE([integer not acceptable string atom],
+  [[parse-atoms '["string"]' '[1]']],
+  [expected string])
+
+OVSDB_CHECK_NEGATIVE([uuid atom must be expressed as array],
+  [[parse-atoms '["uuid"]' '["550e8400-e29b-41d4-a716-446655440000"]']],
+  [[expected ["uuid", <string>]]])
+
+AT_BANNER([OSVDB -- simple data])
+
+OVSDB_CHECK_POSITIVE([integer datum],
+  [[parse-data '["integer"]' '[0]' '[1]' '[-1]']],
+  [0
+1
+-1])
+
+OVSDB_CHECK_POSITIVE([real datum], 
+  [[parse-data '["real"]' '[0]' '[1.0]' '[-1.25]']],
+  [0
+1
+-1.25])
+
+OVSDB_CHECK_POSITIVE([boolean datum],
+  [[parse-data '["boolean"]' '[true]' '[false]' ]],
+  [true
+false])
+
+OVSDB_CHECK_POSITIVE([string datum],
+  [[parse-data '["string"]' '[""]' '["true"]' '["\"\\\/\b\f\n\r\t"]']],
+  [""
+"true"
+"\"\\/\b\f\n\r\t"])
+
+AT_BANNER([OVSDB -- set data])
+
+OVSDB_CHECK_POSITIVE([optional boolean],
+  [[parse-data '{"key": "boolean", "min": 0}' \
+    '["set", [true]]' \
+    '["set", [false]]' \
+    '["set", []]']], 
+  [[["set",[true]]
+["set",[false]]
+["set",[]]]],
+  [set])
+
+OVSDB_CHECK_POSITIVE([set of 0 or more integers],
+  [[parse-data '{"key": "integer", "min": 0, "max": "unlimited"}' \
+    '["set", [0]]' \
+    '["set", [0, 1]]' \
+    '["set", [0, 1, 2]]' \
+    '["set", [0, 1, 2, 3, 4, 5]]' \
+    '["set", [0, 1, 2, 3, 4, 5, 6, 7, 8]]' \
+    '["set", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]']],
+  [[["set",[0]]
+["set",[0,1]]
+["set",[0,1,2]]
+["set",[0,1,2,3,4,5]]
+["set",[0,1,2,3,4,5,6,7,8]]
+["set",[0,1,2,3,4,5,6,7,8,9,10]]]])
+
+OVSDB_CHECK_POSITIVE([set of 1 to 3 uuids],
+  [[parse-data '{"key": "uuid", "min": 1, "max": 3}' \
+    '["set", [["uuid", "550e8400-e29b-41d4-a716-446655440000"]]]' \
+    '["set", [["uuid", "c5051240-30ff-43ed-b4b9-93cf3f050813"],
+              ["uuid", "90558331-09af-4d2f-a572-509cad2e9088"],
+              ["uuid", "550e8400-e29b-41d4-a716-446655440000"]]]']],
+  [[["set",[["uuid","550e8400-e29b-41d4-a716-446655440000"]]]
+["set",[["uuid","550e8400-e29b-41d4-a716-446655440000"],["uuid","90558331-09af-4d2f-a572-509cad2e9088"],["uuid","c5051240-30ff-43ed-b4b9-93cf3f050813"]]]]])
+
+OVSDB_CHECK_POSITIVE([set of 0 to 3 strings],
+  [[parse-data '{"key": "string", "min": 0, "max": 3}' \
+    '["set", []]' \
+    '["set", ["a relatively long string"]]' \
+    '["set", ["short string", "a relatively long string"]]' \
+    '["set", ["zzz", "short string", "a relatively long string"]]']],
+  [[["set",[]]
+["set",["a relatively long string"]]
+["set",["a relatively long string","short string"]]
+["set",["a relatively long string","short string","zzz"]]]])
+
+OVSDB_CHECK_NEGATIVE([duplicate boolean not allowed in set],
+  [[parse-data '{"key": "boolean", "max": 5}' '["set", [true, true]]']],
+  [ovsdb error: set contains duplicate])
+
+OVSDB_CHECK_NEGATIVE([duplicate integer not allowed in set],
+  [[parse-data '{"key": "integer", "max": 5}' '["set", [1, 2, 3, 1]]']],
+  [ovsdb error: set contains duplicate])
+
+OVSDB_CHECK_NEGATIVE([duplicate real not allowed in set],
+  [[parse-data '{"key": "real", "max": 5}' '["set", [0.0, -0.0]]']],
+  [ovsdb error: set contains duplicate])
+
+OVSDB_CHECK_NEGATIVE([duplicate string not allowed in set],
+  [[parse-data '{"key": "string", "max": 5}' '["set", ["asdf", "ASDF", "asdf"]]']],
+  [ovsdb error: set contains duplicate])
+
+OVSDB_CHECK_NEGATIVE([duplicate uuid not allowed in set],
+  [[parse-data '{"key": "uuid", "max": 5}' \
+    '["set", [["uuid", "7ef21525-0088-4a28-a418-5518413e43ea"],
+              ["uuid", "355ad037-f1da-40aa-b47c-ff9c7e8c6a38"],
+              ["uuid", "7ef21525-0088-4a28-a418-5518413e43ea"]]]']],
+  [ovsdb error: set contains duplicate])
+
+AT_BANNER([OVSDB -- map data])
+
+OVSDB_CHECK_POSITIVE([map of 1 integer to boolean],
+  [[parse-data '{"key": "integer", "value": "boolean"}' \
+    '["map", [[1, true]]]']],
+  [[["map",[[1,true]]]]])
+
+OVSDB_CHECK_POSITIVE([map of at least 1 integer to boolean],
+  [[parse-data '{"key": "integer", "value": "boolean", "max": "unlimited"}' \
+    '["map", [[1, true]]]' \
+    '["map", [[0, true], [1, false], [2, true], [3, true], [4, true]]]' \
+    '["map", [[3, false], [0, true], [4, false]]]']],
+  [[["map",[[1,true]]]
+["map",[[0,true],[1,false],[2,true],[3,true],[4,true]]]
+["map",[[0,true],[3,false],[4,false]]]]])
+
+OVSDB_CHECK_POSITIVE([map of 1 boolean to integer],
+ [[parse-data '{"key": "boolean", "value": "integer"}' \
+   '["map", [[true, 1]]]']],
+ [[["map",[[true,1]]]]])
+
+OVSDB_CHECK_POSITIVE([map of 5 uuid to real],
+  [[parse-data '{"key": "uuid", "value": "real", "min": 5, "max": 5}' \
+    '["map", [[["uuid", "cad8542b-6ee1-486b-971b-7dcbf6e14979"], 1.0],
+             [["uuid", "6b94b968-2702-4f64-9457-314a34d69b8c"], 2.0],
+             [["uuid", "d2c4a168-24de-47eb-a8a3-c1abfc814979"], 3.0],
+             [["uuid", "25bfa475-d072-4f60-8be1-00f48643e9cb"], 4.0],
+             [["uuid", "1c92b8ca-d5e4-4628-a85d-1dc2d099a99a"], 5.0]]]']],
+  [[["map",[[["uuid","1c92b8ca-d5e4-4628-a85d-1dc2d099a99a"],5],[["uuid","25bfa475-d072-4f60-8be1-00f48643e9cb"],4],[["uuid","6b94b968-2702-4f64-9457-314a34d69b8c"],2],[["uuid","cad8542b-6ee1-486b-971b-7dcbf6e14979"],1],[["uuid","d2c4a168-24de-47eb-a8a3-c1abfc814979"],3]]]]])
+
+OVSDB_CHECK_POSITIVE([map of 10 string to string],
+  [[parse-data '{"key": "string", "value": "string", "min": 10, "max": 10}' \
+    '["map", [["2 gills", "1 chopin"],
+             ["2 chopins", "1 pint"],
+             ["2 pints", "1 quart"],
+             ["2 quarts", "1 pottle"],
+             ["2 pottles", "1 gallon"],
+             ["2 gallons", "1 peck"],
+             ["2 pecks", "1 demibushel"],
+             ["2 demibushel", "1 firkin"],
+             ["2 firkins", "1 kilderkin"],
+             ["2 kilderkins", "1 barrel"]]]']],
+   [[["map",[["2 chopins","1 pint"],["2 demibushel","1 firkin"],["2 firkins","1 kilderkin"],["2 gallons","1 peck"],["2 gills","1 chopin"],["2 kilderkins","1 barrel"],["2 pecks","1 demibushel"],["2 pints","1 quart"],["2 pottles","1 gallon"],["2 quarts","1 pottle"]]]]])
+
+OVSDB_CHECK_NEGATIVE([duplicate integer key not allowed in map],
+  [[parse-data '{"key": "integer", "value": "boolean", "max": 5}' \
+    '["map", [[1, true], [2, false], [1, false]]]']],
+  [ovsdb error: map contains duplicate key])
diff --git a/tests/ovsdb-execution.at b/tests/ovsdb-execution.at
new file mode 100644 (file)
index 0000000..8b2bc0b
--- /dev/null
@@ -0,0 +1,333 @@
+AT_BANNER([OVSDB -- execution])
+
+m4_define([ORDINAL_SCHEMA],
+  [[{"name": "mydb",
+     "tables": {
+       "ordinals": {
+         "columns": {
+           "number": {"type": "integer"},
+           "name": {"type": "string"}}}}}]])
+
+# OVSDB_CHECK_EXECUTION(TITLE, SCHEMA, TRANSACTIONS, OUTPUT, [KEYWORDS])
+#
+# Runs "test-ovsdb execute" with the given SCHEMA and each of the
+# TRANSACTIONS (which should be a quoted list of quoted strings).
+#
+# Checks that the overall output is OUTPUT, but UUIDs in the output
+# are replaced by markers of the form <N> where N is a number.  The
+# first unique UUID is replaced by <0>, the next by <1>, and so on.
+# If a given UUID appears more than once it is always replaced by the
+# same marker.
+#
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.
+m4_define([OVSDB_CHECK_EXECUTION], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb execute execution positive $5])
+   OVS_CHECK_LCOV([test-ovsdb execute '$2' m4_foreach([txn], [$3], [ 'txn'])],
+     [0], [stdout], [])
+   AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0], [$4])
+   AT_CLEANUP])
+
+m4_define([EXECUTION_EXAMPLES], [
+OVSDB_CHECK_EXECUTION([insert row, query table],
+  [ORDINAL_SCHEMA], 
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}}]]],
+   [[[{"op": "select",
+       "table": "ordinals",
+       "where": []}]]]],
+  [[[{"uuid":["uuid","<0>"]}]
+[{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<1>"],"name":"zero","number":0}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, query by value],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}}]]],
+   [[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}}]]],
+   [[[{"op": "select",
+       "table": "ordinals",
+       "where": [["name", "==", "zero"]]}]]],
+   [[[{"op": "select",
+       "table": "ordinals",
+       "where": [["name", "==", "one"]]}]]]],
+  [[[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]}]
+[{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"zero","number":0}]}]
+[{"rows":[{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, query by named-uuid],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "second"},
+      {"op": "select",
+       "table": "ordinals",
+       "where": [["_uuid", "==", ["named-uuid", "first"]]]},
+      {"op": "select",
+       "table": "ordinals",
+       "where": [["_uuid", "==", ["named-uuid", "second"]]]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"zero","number":0}]},{"rows":[{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, update rows by value],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"}]]],
+   [[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "first"}]]],
+   [[[{"op": "update",
+       "table": "ordinals",
+       "where": [["name", "==", "zero"]],
+       "row": {"name": "nought"}}]]],
+   [[[{"op": "select",
+       "table": "ordinals",
+       "where": [],
+       "sort": ["number"]}]]]],
+  [[[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]}]
+[{"count":1}]
+[{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"nought","number":0},{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, delete by named-uuid],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "second"},
+      {"op": "delete",
+       "table": "ordinals",
+       "where": [["_uuid", "==", ["named-uuid", "first"]]]},
+      {"op": "select",
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name","number"]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"count":1},{"rows":[{"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, delete rows by value],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"}]]],
+   [[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "first"}]]],
+   [[[{"op": "delete",
+       "table": "ordinals",
+       "where": [["name", "==", "zero"]]}]]],
+   [[[{"op": "select",
+       "table": "ordinals",
+       "where": []}]]]],
+  [[[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]}]
+[{"count":1}]
+[{"rows":[{"_uuid":["uuid","<1>"],"_version":["uuid","<2>"],"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, delete by (non-matching) value],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"}]]],
+   [[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "first"}]]],
+   [[[{"op": "delete",
+       "table": "ordinals",
+       "where": [["name", "==", "nought"]]}]]],
+   [[[{"op": "select",
+       "table": "ordinals",
+       "where": [],
+       "sort": ["number"]}]]]],
+  [[[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]}]
+[{"count":0}]
+[{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"zero","number":0},{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, delete all],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "second"},
+      {"op": "delete",
+       "table": "ordinals",
+       "where": []},
+      {"op": "select",
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name","number"]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"count":2},{"rows":[]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert row, query table, commit],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "select",
+       "table": "ordinals",
+       "where": []},
+      {"op": "commit",
+       "durable": false}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<1>"],"name":"zero","number":0}]},{}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert row, query table, commit durably],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "select",
+       "table": "ordinals",
+       "where": []},
+      {"op": "commit",
+       "durable": true}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<1>"],"name":"zero","number":0}]},{}]
+]])
+
+OVSDB_CHECK_EXECUTION([equality wait with correct rows],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 0,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1}]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{}]
+]])
+
+OVSDB_CHECK_EXECUTION([equality wait with extra row],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 0,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1},
+                {"name": "two", "number": 2}]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"details":"\"wait\" timed out","error":"timed out"}]
+]])
+
+OVSDB_CHECK_EXECUTION([equality wait with missing row],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 0,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "one", "number": 1}]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"details":"\"wait\" timed out","error":"timed out"}]
+]])
+
+OVSDB_CHECK_EXECUTION([inequality wait with correct rows],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 0,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "!=",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1}]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"details":"\"wait\" timed out","error":"timed out"}]
+]])
+
+OVSDB_CHECK_EXECUTION([inequality wait with extra row],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 0,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "!=",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1},
+                {"name": "two", "number": 2}]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{}]
+]])
+
+OVSDB_CHECK_EXECUTION([inequality wait with missing row],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 0,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "!=",
+       "rows": [{"name": "one", "number": 1}]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{}]
+]])
+])
+
+EXECUTION_EXAMPLES
diff --git a/tests/ovsdb-file.at b/tests/ovsdb-file.at
new file mode 100644 (file)
index 0000000..c1f0bfb
--- /dev/null
@@ -0,0 +1,30 @@
+AT_BANNER([OVSDB -- file storage])
+
+# OVSDB_CHECK_EXECUTION(TITLE, SCHEMA, TRANSACTIONS, OUTPUT, [KEYWORDS])
+#
+# Creates a database with the given SCHEMA and runs each of the
+# TRANSACTIONS (which should be a quoted list of quoted strings)
+# against it with ovsdb-tool one at a time.  
+#
+# Checks that the overall output is OUTPUT, but UUIDs in the output
+# are replaced by markers of the form <N> where N is a number.  The
+# first unique UUID is replaced by <0>, the next by <1>, and so on.
+# If a given UUID appears more than once it is always replaced by the
+# same marker.
+#
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.
+m4_define([OVSDB_CHECK_EXECUTION], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb file positive $5])
+   AT_DATA([schema], [$2
+])
+   touch .db.~lock~
+   OVS_CHECK_LCOV([ovsdb-tool create db schema], [0], [stdout], [ignore])
+   m4_foreach([txn], [$3], 
+     [OVS_CHECK_LCOV([ovsdb-tool transact db 'txn'], [0], [stdout], [ignore])
+cat stdout >> output
+])
+   AT_CHECK([perl $srcdir/uuidfilt.pl output], [0], [$4])
+   AT_CLEANUP])
+
+EXECUTION_EXAMPLES
diff --git a/tests/ovsdb-idl.at b/tests/ovsdb-idl.at
new file mode 100644 (file)
index 0000000..f0e6ff8
--- /dev/null
@@ -0,0 +1,266 @@
+AT_BANNER([OVSDB -- interface description language (IDL)])
+
+# OVSDB_CHECK_IDL(TITLE, [PRE-IDL-TXN], TRANSACTIONS, OUTPUT, [KEYWORDS])
+#
+# Creates a database with a schema derived from idltest.ovsidl, runs
+# each PRE-IDL-TXN (if any), starts an ovsdb-server on that database,
+# and runs "test-ovsdb idl" passing each of the TRANSACTIONS along.
+#
+# Checks that the overall output is OUTPUT.  Before comparison, the
+# output is sorted (using "sort") and UUIDs in the output are replaced
+# by markers of the form <N> where N is a number.  The first unique
+# UUID is replaced by <0>, the next by <1>, and so on.  If a given
+# UUID appears more than once it is always replaced by the same
+# marker.
+#
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.
+m4_define([OVSDB_CHECK_IDL], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb server idl positive $5])
+   OVS_CHECK_LCOV([ovsdb-tool create db $abs_builddir/idltest.ovsschema], 
+                  [0], [stdout], [ignore])
+   AT_CHECK([ovsdb-server --detach --pidfile=$PWD/server-pid --listen=punix:socket --unixctl=$PWD/unixctl db])
+   m4_if([$2], [], [],
+     [OVS_CHECK_LCOV([ovsdb-client transact unix:socket $2], [0], [ignore], [ignore], [kill `cat server-pid`])])
+   AT_CHECK([test-ovsdb -vjsonrpc -t10 idl unix:socket $3], 
+            [0], [stdout], [ignore], [kill `cat server-pid`])
+   AT_CHECK([sort stdout | perl $srcdir/uuidfilt.pl], [0], [$4], [],
+            [kill `cat server-pid`])
+   kill `cat server-pid`
+   AT_CLEANUP])
+
+OVSDB_CHECK_IDL([simple idl, initially empty, no ops],
+  [],
+  [],
+  [000: empty
+001: done
+])
+
+OVSDB_CHECK_IDL([simple idl, initially empty, various ops],
+  [],
+  [['[{"op": "insert",
+       "table": "simple",
+       "row": {"i": 1,
+               "r": 2.0,
+               "b": true,
+               "s": "mystring",
+               "u": ["uuid", "84f5c8f5-ac76-4dbc-a24f-8860eb407fc1"],
+               "ia": ["set", [1, 2, 3]],
+               "ra": ["set", [-0.5]],
+               "ba": ["set", [true, false]],
+               "sa": ["set", ["abc", "def"]], 
+               "ua": ["set", [["uuid", "69443985-7806-45e2-b35f-574a04e720f9"],
+                              ["uuid", "aad11ef0-816a-4b01-93e6-03b8b4256b98"]]]}},
+      {"op": "insert",
+       "table": "simple",
+       "row": {}}]' \
+    '[{"op": "update",
+       "table": "simple",
+       "where": [],
+       "row": {"b": true}}]' \
+    '[{"op": "update",
+       "table": "simple",
+       "where": [],
+       "row": {"r": 123.5}}]' \
+    '[{"op": "insert",
+       "table": "simple",
+       "row": {"i": -1,
+               "r": 125,
+               "b": false,
+               "s": "",
+               "ia": ["set", [1]],
+               "ra": ["set", [1.5]],
+               "ba": ["set", [false]],
+               "sa": ["set", []], 
+               "ua": ["set", []]}}]' \
+    '[{"op": "update",
+       "table": "simple",
+       "where": [["i", "<", 1]],
+       "row": {"s": "newstring"}}]' \
+    '[{"op": "delete",
+       "table": "simple",
+       "where": [["i", "==", 0]]}]' \
+    'reconnect']],
+  [[000: empty
+001: {"error":null,"result":[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]}
+002: i=0 r=0 b=false s= u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+002: i=1 r=2 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+003: {"error":null,"result":[{"count":2}]}
+004: i=0 r=0 b=true s= u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+004: i=1 r=2 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+005: {"error":null,"result":[{"count":2}]}
+006: i=0 r=123.5 b=true s= u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+006: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+007: {"error":null,"result":[{"uuid":["uuid","<6>"]}]}
+008: i=-1 r=125 b=false s= u=<2> ia=[1] ra=[1.5] ba=[false] sa=[] ua=[] uuid=<6>
+008: i=0 r=123.5 b=true s= u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+008: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+009: {"error":null,"result":[{"count":2}]}
+010: i=-1 r=125 b=false s=newstring u=<2> ia=[1] ra=[1.5] ba=[false] sa=[] ua=[] uuid=<6>
+010: i=0 r=123.5 b=true s=newstring u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+010: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+011: {"error":null,"result":[{"count":1}]}
+012: i=-1 r=125 b=false s=newstring u=<2> ia=[1] ra=[1.5] ba=[false] sa=[] ua=[] uuid=<6>
+012: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+013: reconnect
+014: i=-1 r=125 b=false s=newstring u=<2> ia=[1] ra=[1.5] ba=[false] sa=[] ua=[] uuid=<6>
+014: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+015: done
+]])
+
+OVSDB_CHECK_IDL([simple idl, initially populated],
+  [['[{"op": "insert",
+       "table": "simple",
+       "row": {"i": 1,
+               "r": 2.0,
+               "b": true,
+               "s": "mystring",
+               "u": ["uuid", "84f5c8f5-ac76-4dbc-a24f-8860eb407fc1"],
+               "ia": ["set", [1, 2, 3]],
+               "ra": ["set", [-0.5]],
+               "ba": ["set", [true, false]],
+               "sa": ["set", ["abc", "def"]], 
+               "ua": ["set", [["uuid", "69443985-7806-45e2-b35f-574a04e720f9"],
+                              ["uuid", "aad11ef0-816a-4b01-93e6-03b8b4256b98"]]]}},
+      {"op": "insert",
+       "table": "simple",
+       "row": {}}]']],
+  [['[{"op": "update",
+       "table": "simple",
+       "where": [],
+       "row": {"b": true}}]']],
+  [[000: i=0 r=0 b=false s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+000: i=1 r=2 b=true s=mystring u=<2> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<3> <4>] uuid=<5>
+001: {"error":null,"result":[{"count":2}]}
+002: i=0 r=0 b=true s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+002: i=1 r=2 b=true s=mystring u=<2> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<3> <4>] uuid=<5>
+003: done
+]])
+
+OVSDB_CHECK_IDL([self-linking idl, consistent ops],
+  [],
+  [['[{"op": "insert",
+       "table": "selfLink",
+       "row": {"i": 0, "k": ["named-uuid", "self"]},
+       "uuid-name": "self"}]' \
+    '[{"op": "insert",
+       "table": "selfLink",
+       "row": {"i": 1},
+       "uuid-name": "row1"},
+      {"op": "insert",
+       "table": "selfLink",
+       "row": {"i": 2, "k": ["named-uuid", "row1"]},
+       "uuid-name": "row2"},
+      {"op": "update",
+       "table": "selfLink",
+       "where": [["i", "==", 1]],
+       "row": {"k": ["named-uuid", "row2"]}}]' \
+    '[{"op": "update",
+       "table": "selfLink",
+       "where": [["i", "==", 1]],
+       "row": {"k": ["uuid", "#1#"]}}]' \
+    '[{"op": "update",
+       "table": "selfLink",
+       "where": [],
+       "row": {"k": ["uuid", "#0#"]}}]']],
+  [[000: empty
+001: {"error":null,"result":[{"uuid":["uuid","<0>"]}]}
+002: i=0 k=0 ka=[] uuid=<0>
+003: {"error":null,"result":[{"uuid":["uuid","<1>"]},{"uuid":["uuid","<2>"]},{"count":1}]}
+004: i=0 k=0 ka=[] uuid=<0>
+004: i=1 k=2 ka=[] uuid=<1>
+004: i=2 k=1 ka=[] uuid=<2>
+005: {"error":null,"result":[{"count":1}]}
+006: i=0 k=0 ka=[] uuid=<0>
+006: i=1 k=1 ka=[] uuid=<1>
+006: i=2 k=1 ka=[] uuid=<2>
+007: {"error":null,"result":[{"count":3}]}
+008: i=0 k=0 ka=[] uuid=<0>
+008: i=1 k=0 ka=[] uuid=<1>
+008: i=2 k=0 ka=[] uuid=<2>
+009: done
+]])
+
+OVSDB_CHECK_IDL([self-linking idl, inconsistent ops],
+  [],
+  [['[{"op": "insert",
+       "table": "selfLink",
+       "row": {"i": 0, "k": ["uuid", "cf197cc5-c8c9-42f5-82d5-c71a9f2cb96b"]}}]' \
+     '[{"op": "update",
+       "table": "selfLink",
+       "where": [],
+       "row": {"k": ["uuid", "#0#"]}}]' \
+     '[{"op": "update",
+       "table": "selfLink",
+       "where": [],
+       "row": {"k": ["uuid", "c2fca39a-e69a-42a4-9c56-5eca85839ce9"]}}]' \
+     '[{"op": "insert",
+       "table": "selfLink",
+       "row": {"i": 1, "k": ["uuid", "52d752a3-b062-4668-9446-d2e0d4a14703"]}}]' \
+     '[{"op": "update",
+       "table": "selfLink",
+       "where": [],
+       "row": {"k": ["uuid", "#1#"]}}]' \
+]],
+  [[000: empty
+001: {"error":null,"result":[{"uuid":["uuid","<0>"]}]}
+002: i=0 k= ka=[] uuid=<0>
+003: {"error":null,"result":[{"count":1}]}
+004: i=0 k=0 ka=[] uuid=<0>
+005: {"error":null,"result":[{"count":1}]}
+006: i=0 k= ka=[] uuid=<0>
+007: {"error":null,"result":[{"uuid":["uuid","<1>"]}]}
+008: i=0 k= ka=[] uuid=<0>
+008: i=1 k= ka=[] uuid=<1>
+009: {"error":null,"result":[{"count":2}]}
+010: i=0 k=1 ka=[] uuid=<0>
+010: i=1 k=1 ka=[] uuid=<1>
+011: done
+]])
+
+OVSDB_CHECK_IDL([self-linking idl, sets],
+  [],
+  [['[{"op": "insert",
+       "table": "selfLink",
+       "row": {"i": 0, "ka": ["set", [["named-uuid", "i0"]]]},
+       "uuid-name": "i0"},
+      {"op": "insert",
+       "table": "selfLink",
+       "row": {"i": 1, "ka": ["set", [["named-uuid", "i1"]]]},
+       "uuid-name": "i1"},
+      {"op": "insert",
+       "table": "selfLink",
+       "row": {"i": 2, "ka": ["set", [["named-uuid", "i2"]]]},
+       "uuid-name": "i2"},
+      {"op": "insert",
+       "table": "selfLink",
+       "row": {"i": 3, "ka": ["set", [["named-uuid", "i3"]]]},
+       "uuid-name": "i3"}]' \
+    '[{"op": "update",
+       "table": "selfLink",
+       "where": [],
+       "row": {"ka": ["set", [["uuid", "#0#"], ["uuid", "#1#"], ["uuid", "#2#"], ["uuid", "#3#"]]]}}]' \
+    '[{"op": "update",
+       "table": "selfLink",
+       "where": [],
+       "row": {"ka": ["set", [["uuid", "#0#"], ["uuid", "88702e78-845b-4a6e-ad08-cf68922ae84a"], ["uuid", "#2#"], ["uuid", "1ac2b12e-b767-4805-a55d-43976e40c465"]]]}}]']],
+  [[000: empty
+001: {"error":null,"result":[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"uuid":["uuid","<2>"]},{"uuid":["uuid","<3>"]}]}
+002: i=0 k= ka=[0] uuid=<0>
+002: i=1 k= ka=[1] uuid=<1>
+002: i=2 k= ka=[2] uuid=<2>
+002: i=3 k= ka=[3] uuid=<3>
+003: {"error":null,"result":[{"count":4}]}
+004: i=0 k= ka=[0 1 2 3] uuid=<0>
+004: i=1 k= ka=[0 1 2 3] uuid=<1>
+004: i=2 k= ka=[0 1 2 3] uuid=<2>
+004: i=3 k= ka=[0 1 2 3] uuid=<3>
+005: {"error":null,"result":[{"count":4}]}
+006: i=0 k= ka=[0 2] uuid=<0>
+006: i=1 k= ka=[0 2] uuid=<1>
+006: i=2 k= ka=[0 2] uuid=<2>
+006: i=3 k= ka=[0 2] uuid=<3>
+007: done
+]])
+
+# XXX self-linking idl, maps
diff --git a/tests/ovsdb-log.at b/tests/ovsdb-log.at
new file mode 100644 (file)
index 0000000..b0c1c97
--- /dev/null
@@ -0,0 +1,282 @@
+AT_BANNER([OVSDB -- logging])
+
+AT_SETUP([create empty, reread])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([log])
+OVS_CHECK_LCOV(
+  [test-ovsdb log-io file 'O_CREAT|O_RDWR'], [0], 
+  [file: open successful
+], [ignore])
+OVS_CHECK_LCOV(
+  [test-ovsdb log-io file 'O_RDONLY' read], [0], 
+  [file: open successful
+file: read: end of log
+], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write one, reread])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+  [[test-ovsdb log-io file 'O_CREAT|O_RDWR' 'write:[0]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+  [test-ovsdb log-io file 'O_RDONLY' read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: end of log
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([check that O_EXCL works])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+  [[test-ovsdb log-io file 'O_CREAT|O_RDWR' 'write:[1]']], [0], 
+  [[file: open successful
+file: write:[1] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+  [test-ovsdb log-io file 'O_RDONLY' read], [0], 
+  [[file: open successful
+file: read: [1]
+]], [ignore])
+OVS_CHECK_LCOV(
+  [test-ovsdb log-io file 'O_CREAT|O_RDWR|O_EXCL' read], [1], 
+  [], [test-ovsdb: I/O error: create: file failed (File exists)
+])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write one, reread])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+  [[test-ovsdb log-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+  [test-ovsdb log-io file 'O_RDONLY' read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read: end of log
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write one, reread, append])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+  [[test-ovsdb log-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+  [[test-ovsdb log-io file 'O_RDWR' read read read 'write:["append"]']], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: write:["append"] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+  [test-ovsdb log-io file 'O_RDONLY' read read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read: ["append"]
+file: read: end of log
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, reread one, overwrite])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+  [[test-ovsdb log-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+  [[test-ovsdb log-io file 'O_RDWR' read 'write:["more data"]']], [0], 
+  [[file: open successful
+file: read: [0]
+file: write:["more data"] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+  [test-ovsdb log-io file 'O_RDONLY' read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: ["more data"]
+file: read: end of log
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, add corrupted data, read])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+  [[test-ovsdb log-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([echo 'xxx' >> file])
+OVS_CHECK_LCOV(
+  [test-ovsdb log-io file 'O_RDONLY' read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read failed: syntax error: file: parse error at offset 174 in header line "xxx"
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, add corrupted data, read, overwrite])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+  [[test-ovsdb log-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([echo 'xxx' >> file])
+OVS_CHECK_LCOV(
+  [[test-ovsdb log-io file 'O_RDWR' read read read read 'write:[3]']], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read failed: syntax error: file: parse error at offset 174 in header line "xxx"
+file: write:[3] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+  [test-ovsdb log-io file 'O_RDONLY' read read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read: [3]
+file: read: end of log
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, corrupt some data, read, overwrite])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+  [[test-ovsdb log-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([[sed 's/\[2]/[3]/' < file > file.tmp]])
+AT_CHECK([mv file.tmp file])
+AT_CHECK([[grep -c '\[3]' file]], [0], [1
+])
+OVS_CHECK_LCOV(
+  [[test-ovsdb log-io file 'O_RDWR' read read read 'write:["longer data"]']], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read failed: syntax error: file: 4 bytes starting at offset 170 have SHA-1 hash 5c031e5c0d3a9338cc127ebe40bb2748b6a67e78 but should have hash 98f55556e7ffd432381b56a19bd485b3e6446442
+file: write:["longer data"] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+  [test-ovsdb log-io file 'O_RDONLY' read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: ["longer data"]
+file: read: end of log
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, truncate file, read, overwrite])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+  [[test-ovsdb log-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([[sed 's/\[2]/2/' < file > file.tmp]])
+AT_CHECK([mv file.tmp file])
+AT_CHECK([[grep -c '^2$' file]], [0], [1
+])
+OVS_CHECK_LCOV(
+  [[test-ovsdb log-io file 'O_RDWR' read read read 'write:["longer data"]']], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read failed: I/O error: file: error reading 4 bytes starting at offset 170 (unexpected end of file)
+file: write:["longer data"] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+  [test-ovsdb log-io file 'O_RDONLY' read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: ["longer data"]
+file: read: end of log
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write bad JSON, read, overwrite])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+  [[test-ovsdb log-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([[printf '%s\n%s\n' 'OVSDB JSON 5 d910b02871075d3156ec8675dfc95b7d5d640aa6' 'null' >> file]])
+OVS_CHECK_LCOV(
+  [[test-ovsdb log-io file 'O_RDWR' read read read read 'write:["replacement data"]']], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read failed: syntax error: file: 5 bytes starting at offset 228 are not valid JSON (syntax error at beginning of input)
+file: write:["replacement data"] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+  [test-ovsdb log-io file 'O_RDONLY' read read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read: ["replacement data"]
+file: read: end of log
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
diff --git a/tests/ovsdb-monitor.at b/tests/ovsdb-monitor.at
new file mode 100644 (file)
index 0000000..e57f41a
--- /dev/null
@@ -0,0 +1,160 @@
+AT_BANNER([OVSDB -- ovsdb-server monitors])
+
+# OVSDB_CHECK_MONITOR(TITLE, SCHEMA, [PRE-MONITOR-TXN], MONITOR-ARGS,
+#                     TRANSACTIONS, OUTPUT, [KEYWORDS])
+#
+# Creates a database with the given SCHEMA, starts an ovsdb-server on
+# that database, and runs each of the TRANSACTIONS (which should be a
+# quoted list of quoted strings) against it with ovsdb-client one at a
+# time.
+#
+# Checks that the overall output is OUTPUT, but UUIDs in the output
+# are replaced by markers of the form <N> where N is a number.  The
+# first unique UUID is replaced by <0>, the next by <1>, and so on.
+# If a given UUID appears more than once it is always replaced by the
+# same marker.
+#
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.
+m4_define([OVSDB_CHECK_MONITOR], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb server monitor positive $7])
+   AT_DATA([schema], [$2
+])
+   OVS_CHECK_LCOV([ovsdb-tool create db schema], [0], [stdout], [ignore])
+   m4_foreach([txn], [$3],
+     [OVS_CHECK_LCOV([ovsdb-tool transact db 'txn'], [0], [ignore], [ignore])])
+   AT_CHECK([ovsdb-server --detach --pidfile=$PWD/server-pid --listen=punix:socket --unixctl=$PWD/unixctl db])
+   AT_CHECK([ovsdb-client monitor --format=csv unix:socket $4 > output & echo $! > monitor-pid], 
+            [0], [ignore], [ignore], [kill `cat server-pid`])
+   m4_foreach([txn], [$5],
+     [OVS_CHECK_LCOV([ovsdb-client transact unix:socket 'txn'], [0],
+                     [ignore], [ignore], [kill `cat server-pid monitor-pid`])])
+   AT_CHECK([ovs-appctl -t $PWD/unixctl -e exit], [0], [ignore], [ignore])
+   wait
+   AT_CHECK([perl $srcdir/uuidfilt.pl output], [0], [$6])
+   AT_CLEANUP])
+
+OVSDB_CHECK_MONITOR([monitor insert into empty table],
+  [ORDINAL_SCHEMA],
+  [],
+  [ordinals],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}}]]]],
+  [[row,action,name,number,_version
+<0>,insert,"""zero""",0,"[""uuid"",""<1>""]"
+]])
+
+OVSDB_CHECK_MONITOR([monitor insert into populated table],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 10, "name": "ten"}}]]]],
+  [ordinals],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}}]]]],
+  [[row,action,name,number,_version
+<0>,initial,"""ten""",10,"[""uuid"",""<1>""]"
+row,action,name,number,_version
+<2>,insert,"""zero""",0,"[""uuid"",""<3>""]"
+]])
+
+OVSDB_CHECK_MONITOR([monitor delete],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 10, "name": "ten"}}]]]],
+  [ordinals],
+  [[[[{"op": "delete",
+       "table": "ordinals",
+       "where": [["number", "==", 10]]}]]]],
+  [[row,action,name,number,_version
+<0>,initial,"""ten""",10,"[""uuid"",""<1>""]"
+row,action,name,number,_version
+<0>,delete,"""ten""",10,"[""uuid"",""<1>""]"
+]])
+
+OVSDB_CHECK_MONITOR([monitor row update],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 10, "name": "ten"}}]]]],
+  [ordinals],
+  [[[[{"op": "update",
+       "table": "ordinals",
+       "where": [["number", "==", 10]],
+       "row": {"name": "five plus five"}}]]]],
+  [[row,action,name,number,_version
+<0>,initial,"""ten""",10,"[""uuid"",""<1>""]"
+row,action,name,number,_version
+<0>,old,"""ten""",,"[""uuid"",""<1>""]"
+,new,"""five plus five""",10,"[""uuid"",""<2>""]"
+]])
+
+OVSDB_CHECK_MONITOR([monitor no-op row updates],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 10, "name": "ten"}}]]]],
+  [ordinals],
+  [[[[{"op": "update",
+       "table": "ordinals",
+       "where": [["number", "==", 10]],
+       "row": {"number": 10, "name": "ten"}}]]],
+   [[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 9, "name": "nine"}}]]]],
+  [[row,action,name,number,_version
+<0>,initial,"""ten""",10,"[""uuid"",""<1>""]"
+row,action,name,number,_version
+<2>,insert,"""nine""",9,"[""uuid"",""<3>""]"
+]])
+
+OVSDB_CHECK_MONITOR([monitor insert-and-update transaction],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 10, "name": "ten"}}]]]],
+  [ordinals],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 9, "name": "nine"},
+       "uuid-name": "nine"},
+      {"op": "update",
+       "table": "ordinals",
+       "where": [["_uuid", "==", ["named-uuid", "nine"]]],
+       "row": {"name": "three squared"}}]]]],
+  [[row,action,name,number,_version
+<0>,initial,"""ten""",10,"[""uuid"",""<1>""]"
+row,action,name,number,_version
+<2>,insert,"""three squared""",9,"[""uuid"",""<3>""]"
+]])
+
+
+OVSDB_CHECK_MONITOR([monitor insert-update-and-delete transaction],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 10, "name": "ten"}}]]]],
+  [ordinals],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 9, "name": "nine"},
+       "uuid-name": "nine"},
+      {"op": "update",
+       "table": "ordinals",
+       "where": [["_uuid", "==", ["named-uuid", "nine"]]],
+       "row": {"name": "three squared"}},
+      {"op": "delete",
+       "table": "ordinals",
+       "where": [["_uuid", "==", ["named-uuid", "nine"]]]},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 7, "name": "seven"}}]]]],
+  [[row,action,name,number,_version
+<0>,initial,"""ten""",10,"[""uuid"",""<1>""]"
+row,action,name,number,_version
+<2>,insert,"""seven""",7,"[""uuid"",""<3>""]"
+]])
+
diff --git a/tests/ovsdb-query.at b/tests/ovsdb-query.at
new file mode 100644 (file)
index 0000000..c39aa12
--- /dev/null
@@ -0,0 +1,535 @@
+AT_BANNER([OVSDB -- queries])
+
+OVSDB_CHECK_POSITIVE([queries on scalars],
+  [[query \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[{"i": 0,
+       "r": 0.5,
+       "b": true,
+       "s": "a",
+       "u": ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]},
+      {"i": 1,
+       "r": 1.5,
+       "b": false,
+       "s": "b",
+       "u": ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]},
+      {"i": 2,
+       "r": 2.5,
+       "b": true,
+       "s": "c",
+       "u": ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]},
+      {"i": 3,
+       "r": 3.5,
+       "b": false,
+       "s": "d",
+       "u": ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]},
+      {"i": 4,
+       "r": 4.5,
+       "b": true,
+       "s": "e",
+       "u": ["uuid", "4a5127e2-0256-4a72-a7dc-6246213967c7"]}]' \
+    '[[],
+      [["i", "==", 0]],
+      [["i", "!=", 1]],
+      [["i", "<", 2]],
+      [["i", "<=", 3]],
+      [["i", ">", 2]],
+      [["i", ">=", 4]],
+      [["i", "includes", 3]],
+      [["i", "excludes", 2]],
+      [["r", "==", 0.5]],
+      [["r", "!=", 1.5]],
+      [["r", "<", 2.5]],
+      [["r", "<=", 3.5]],
+      [["r", ">", 4.5]],
+      [["r", ">=", 5.5]],
+      [["r", "includes", 1]],
+      [["r", "excludes", 3]],
+      [["b", "==", true]],
+      [["b", "!=", true]],
+      [["b", "includes", false]],
+      [["b", "excludes", true]],
+      [["s", "==", "a"]],
+      [["s", "!=", "b"]],
+      [["s", "includes", "c"]],
+      [["s", "excludes", "d"]],
+      [["u", "==", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]],
+      [["u", "!=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]],
+      [["u", "includes",["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]]']],
+  [dnl
+query  0: 11111
+query  1: 1----
+query  2: 1-111
+query  3: 11---
+query  4: 1111-
+query  5: ---11
+query  6: ----1
+query  7: ---1-
+query  8: 11-11
+query  9: 1----
+query 10: 1-111
+query 11: 11---
+query 12: 1111-
+query 13: -----
+query 14: -----
+query 15: -----
+query 16: 11111
+query 17: 1-1-1
+query 18: -1-1-
+query 19: -1-1-
+query 20: -1-1-
+query 21: 1----
+query 22: 1-111
+query 23: --1--
+query 24: 111-1
+query 25: 1----
+query 26: 1-111
+query 27: --1--],
+  [query])
+
+OVSDB_CHECK_POSITIVE([queries on sets],
+  [[query \
+    '{"columns": {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}}}}' \
+    '[{"i": ["set", []]},
+      {"i": ["set", [0]]},
+      {"i": ["set", [1]]},
+      {"i": ["set", [0, 1]]},
+      {"i": ["set", [2]]},
+      {"i": ["set", [2, 0]]},
+      {"i": ["set", [2, 1]]},
+      {"i": ["set", [2, 1, 0]]}]' \
+    '[[],
+      [["i", "==", ["set", []]]],
+      [["i", "==", ["set", [0]]]],
+      [["i", "==", ["set", [1]]]],
+      [["i", "==", ["set", [0, 1]]]],
+      [["i", "==", ["set", [2]]]],
+      [["i", "==", ["set", [2, 0]]]],
+      [["i", "==", ["set", [2, 1]]]],
+      [["i", "==", ["set", [2, 1, 0]]]],
+      [["i", "!=", ["set", []]]],
+      [["i", "!=", ["set", [0]]]],
+      [["i", "!=", ["set", [1]]]],
+      [["i", "!=", ["set", [0, 1]]]],
+      [["i", "!=", ["set", [2]]]],
+      [["i", "!=", ["set", [2, 0]]]],
+      [["i", "!=", ["set", [2, 1]]]],
+      [["i", "!=", ["set", [2, 1, 0]]]],
+      [["i", "includes", ["set", []]]],
+      [["i", "includes", ["set", [0]]]],
+      [["i", "includes", ["set", [1]]]],
+      [["i", "includes", ["set", [0, 1]]]],
+      [["i", "includes", ["set", [2]]]],
+      [["i", "includes", ["set", [2, 0]]]],
+      [["i", "includes", ["set", [2, 1]]]],
+      [["i", "includes", ["set", [2, 1, 0]]]],
+      [["i", "excludes", ["set", []]]],
+      [["i", "excludes", ["set", [0]]]],
+      [["i", "excludes", ["set", [1]]]],
+      [["i", "excludes", ["set", [0, 1]]]],
+      [["i", "excludes", ["set", [2]]]],
+      [["i", "excludes", ["set", [2, 0]]]],
+      [["i", "excludes", ["set", [2, 1]]]],
+      [["i", "excludes", ["set", [2, 1, 0]]]]]']],
+  [dnl
+query  0: 11111 111
+query  1: 1---- ---
+query  2: -1--- ---
+query  3: --1-- ---
+query  4: ---1- ---
+query  5: ----1 ---
+query  6: ----- 1--
+query  7: ----- -1-
+query  8: ----- --1
+query  9: -1111 111
+query 10: 1-111 111
+query 11: 11-11 111
+query 12: 111-1 111
+query 13: 1111- 111
+query 14: 11111 -11
+query 15: 11111 1-1
+query 16: 11111 11-
+query 17: 11111 111
+query 18: -1-1- 1-1
+query 19: --11- -11
+query 20: ---1- --1
+query 21: ----1 111
+query 22: ----- 1-1
+query 23: ----- -11
+query 24: ----- --1
+query 25: 11111 111
+query 26: 1-1-1 -1-
+query 27: 11--1 1--
+query 28: 1---1 ---
+query 29: 1111- ---
+query 30: 1-1-- ---
+query 31: 11--- ---
+query 32: 1---- ---], [query])
+
+# This is the same as the "set" test except that it adds values,
+# all of which always match.
+OVSDB_CHECK_POSITIVE([queries on maps (1)],
+  [[query \
+    '{"columns": {"i": {"type": {"key": "integer",
+                                 "value": "boolean",
+                                 "min": 0,
+                                 "max": "unlimited"}}}}' \
+    '[{"i": ["map", []]},
+      {"i": ["map", [[0, true]]]},
+      {"i": ["map", [[1, false]]]},
+      {"i": ["map", [[0, true], [1, false]]]},
+      {"i": ["map", [[2, true]]]},
+      {"i": ["map", [[2, true], [0, true]]]},
+      {"i": ["map", [[2, true], [1, false]]]},
+      {"i": ["map", [[2, true], [1, false], [0, true]]]}]' \
+    '[[],
+      [["i", "==", ["map", []]]],
+      [["i", "==", ["map", [[0, true]]]]],
+      [["i", "==", ["map", [[1, false]]]]],
+      [["i", "==", ["map", [[0, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true]]]]],
+      [["i", "==", ["map", [[2, true], [0, true]]]]],
+      [["i", "==", ["map", [[2, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "!=", ["map", []]]],
+      [["i", "!=", ["map", [[0, true]]]]],
+      [["i", "!=", ["map", [[1, false]]]]],
+      [["i", "!=", ["map", [[0, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true]]]]],
+      [["i", "!=", ["map", [[2, true], [0, true]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "includes", ["map", []]]],
+      [["i", "includes", ["map", [[0, true]]]]],
+      [["i", "includes", ["map", [[1, false]]]]],
+      [["i", "includes", ["map", [[0, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true]]]]],
+      [["i", "includes", ["map", [[2, true], [0, true]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "excludes", ["map", []]]],
+      [["i", "excludes", ["map", [[0, true]]]]],
+      [["i", "excludes", ["map", [[1, false]]]]],
+      [["i", "excludes", ["map", [[0, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [0, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false], [0, true]]]]]]']],
+  [dnl
+query  0: 11111 111
+query  1: 1---- ---
+query  2: -1--- ---
+query  3: --1-- ---
+query  4: ---1- ---
+query  5: ----1 ---
+query  6: ----- 1--
+query  7: ----- -1-
+query  8: ----- --1
+query  9: -1111 111
+query 10: 1-111 111
+query 11: 11-11 111
+query 12: 111-1 111
+query 13: 1111- 111
+query 14: 11111 -11
+query 15: 11111 1-1
+query 16: 11111 11-
+query 17: 11111 111
+query 18: -1-1- 1-1
+query 19: --11- -11
+query 20: ---1- --1
+query 21: ----1 111
+query 22: ----- 1-1
+query 23: ----- -11
+query 24: ----- --1
+query 25: 11111 111
+query 26: 1-1-1 -1-
+query 27: 11--1 1--
+query 28: 1---1 ---
+query 29: 1111- ---
+query 30: 1-1-- ---
+query 31: 11--- ---
+query 32: 1---- ---], [query])
+
+# This is the same as the "set" test except that it adds values,
+# and those values don't always match.
+OVSDB_CHECK_POSITIVE([queries on maps (2)],
+  [[query \
+    '{"columns": {"i": {"type": {"key": "integer",
+                                 "value": "boolean",
+                                 "min": 0,
+                                 "max": "unlimited"}}}}' \
+    '[{"i": ["map", []]},
+      {"i": ["map", [[0, true]]]},
+      {"i": ["map", [[0, false]]]},
+      {"i": ["map", [[1, false]]]},
+      {"i": ["map", [[1, true]]]},
+
+      {"i": ["map", [[0, true], [1, false]]]},
+      {"i": ["map", [[0, true], [1, true]]]},
+      {"i": ["map", [[2, true]]]},
+      {"i": ["map", [[2, false]]]},
+      {"i": ["map", [[2, true], [0, true]]]},
+
+      {"i": ["map", [[2, false], [0, true]]]},
+      {"i": ["map", [[2, true], [1, false]]]},
+      {"i": ["map", [[2, true], [1, true]]]},
+      {"i": ["map", [[2, true], [1, false], [0, true]]]},
+      {"i": ["map", [[2, true], [1, false], [0, false]]]}]' \
+    '[[],
+      [["i", "==", ["map", []]]],
+      [["i", "==", ["map", [[0, true]]]]],
+      [["i", "==", ["map", [[1, false]]]]],
+      [["i", "==", ["map", [[0, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true]]]]],
+      [["i", "==", ["map", [[2, true], [0, true]]]]],
+      [["i", "==", ["map", [[2, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "!=", ["map", []]]],
+      [["i", "!=", ["map", [[0, true]]]]],
+      [["i", "!=", ["map", [[1, false]]]]],
+      [["i", "!=", ["map", [[0, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true]]]]],
+      [["i", "!=", ["map", [[2, true], [0, true]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "includes", ["map", []]]],
+      [["i", "includes", ["map", [[0, true]]]]],
+      [["i", "includes", ["map", [[1, false]]]]],
+      [["i", "includes", ["map", [[0, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true]]]]],
+      [["i", "includes", ["map", [[2, true], [0, true]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "excludes", ["map", []]]],
+      [["i", "excludes", ["map", [[0, true]]]]],
+      [["i", "excludes", ["map", [[1, false]]]]],
+      [["i", "excludes", ["map", [[0, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [0, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false], [0, true]]]]]]']],
+  [dnl
+query  0: 11111 11111 11111
+query  1: 1---- ----- -----
+query  2: -1--- ----- -----
+query  3: ---1- ----- -----
+query  4: ----- 1---- -----
+query  5: ----- --1-- -----
+query  6: ----- ----1 -----
+query  7: ----- ----- -1---
+query  8: ----- ----- ---1-
+query  9: -1111 11111 11111
+query 10: 1-111 11111 11111
+query 11: 111-1 11111 11111
+query 12: 11111 -1111 11111
+query 13: 11111 11-11 11111
+query 14: 11111 1111- 11111
+query 15: 11111 11111 1-111
+query 16: 11111 11111 111-1
+query 17: 11111 11111 11111
+query 18: -1--- 11--1 1--1-
+query 19: ---1- 1---- -1-11
+query 20: ----- 1---- ---1-
+query 21: ----- --1-1 -1111
+query 22: ----- ----1 ---1-
+query 23: ----- ----- -1-11
+query 24: ----- ----- ---1-
+query 25: 11111 11111 11111
+query 26: 1-111 --11- -11-1
+query 27: 111-1 -1111 1-1--
+query 28: 1-1-1 --11- --1--
+query 29: 11111 11-1- 1----
+query 30: 1-111 ---1- -----
+query 31: 111-1 -1-1- 1----
+query 32: 1-1-1 ---1- -----], [query])
+
+OVSDB_CHECK_POSITIVE([UUID-distinct queries on scalars],
+  [[query-distinct \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[{"i": 0,
+       "r": 0.5,
+       "b": true,
+       "s": "a",
+       "u": ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]},
+      {"i": 1,
+       "r": 1.5,
+       "b": false,
+       "s": "b",
+       "u": ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]},
+      {"i": 2,
+       "r": 2.5,
+       "b": true,
+       "s": "c",
+       "u": ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]},
+      {"i": 3,
+       "r": 3.5,
+       "b": false,
+       "s": "d",
+       "u": ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]},
+      {"i": 4,
+       "r": 4.5,
+       "b": true,
+       "s": "e",
+       "u": ["uuid", "4a5127e2-0256-4a72-a7dc-6246213967c7"]}]' \
+    '[[],
+      [["i", "==", 0]],
+      [["i", "!=", 1]],
+      [["i", "<", 2]],
+      [["i", "<=", 3]],
+      [["i", ">", 2]],
+      [["i", ">=", 4]],
+      [["i", "includes", 3]],
+      [["i", "excludes", 2]],
+      [["r", "==", 0.5]],
+      [["r", "!=", 1.5]],
+      [["r", "<", 2.5]],
+      [["r", "<=", 3.5]],
+      [["r", ">", 4.5]],
+      [["r", ">=", 5.5]],
+      [["r", "includes", 1]],
+      [["r", "excludes", 3]],
+      [["b", "==", true]],
+      [["b", "!=", true]],
+      [["b", "includes", false]],
+      [["b", "excludes", true]],
+      [["s", "==", "a"]],
+      [["s", "!=", "b"]],
+      [["s", "includes", "c"]],
+      [["s", "excludes", "d"]],
+      [["u", "==", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]],
+      [["u", "!=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]],
+      [["u", "includes",["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]]' \
+    '["_uuid"]']],
+  [dnl
+query  0: abcde
+query  1: a----
+query  2: a-cde
+query  3: ab---
+query  4: abcd-
+query  5: ---de
+query  6: ----e
+query  7: ---d-
+query  8: ab-de
+query  9: a----
+query 10: a-cde
+query 11: ab---
+query 12: abcd-
+query 13: -----
+query 14: -----
+query 15: -----
+query 16: abcde
+query 17: a-c-e
+query 18: -b-d-
+query 19: -b-d-
+query 20: -b-d-
+query 21: a----
+query 22: a-cde
+query 23: --c--
+query 24: abc-e
+query 25: a----
+query 26: a-cde
+query 27: --c--],
+  [query])
+
+OVSDB_CHECK_POSITIVE([Boolean-distinct queries on scalars],
+  [[query-distinct \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[{"i": 0,
+       "r": 0.5,
+       "b": true,
+       "s": "a",
+       "u": ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]},
+      {"i": 1,
+       "r": 1.5,
+       "b": false,
+       "s": "b",
+       "u": ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]},
+      {"i": 2,
+       "r": 2.5,
+       "b": true,
+       "s": "c",
+       "u": ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]},
+      {"i": 3,
+       "r": 3.5,
+       "b": false,
+       "s": "d",
+       "u": ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]},
+      {"i": 4,
+       "r": 4.5,
+       "b": true,
+       "s": "e",
+       "u": ["uuid", "4a5127e2-0256-4a72-a7dc-6246213967c7"]}]' \
+    '[[],
+      [["i", "==", 0]],
+      [["i", "!=", 1]],
+      [["i", "<", 2]],
+      [["i", "<=", 3]],
+      [["i", ">", 2]],
+      [["i", ">=", 4]],
+      [["i", "includes", 3]],
+      [["i", "excludes", 2]],
+      [["r", "==", 0.5]],
+      [["r", "!=", 1.5]],
+      [["r", "<", 2.5]],
+      [["r", "<=", 3.5]],
+      [["r", ">", 4.5]],
+      [["r", ">=", 5.5]],
+      [["r", "includes", 1]],
+      [["r", "excludes", 3]],
+      [["b", "==", true]],
+      [["b", "!=", true]],
+      [["b", "includes", false]],
+      [["b", "excludes", true]],
+      [["s", "==", "a"]],
+      [["s", "!=", "b"]],
+      [["s", "includes", "c"]],
+      [["s", "excludes", "d"]],
+      [["u", "==", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]],
+      [["u", "!=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]],
+      [["u", "includes",["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]]' \
+    '["b"]']],
+  [dnl
+query  0: ababa
+query  1: a-a-a
+query  2: ababa
+query  3: ababa
+query  4: ababa
+query  5: ababa
+query  6: a-a-a
+query  7: -b-b-
+query  8: ababa
+query  9: a-a-a
+query 10: ababa
+query 11: ababa
+query 12: ababa
+query 13: -----
+query 14: -----
+query 15: -----
+query 16: ababa
+query 17: a-a-a
+query 18: -b-b-
+query 19: -b-b-
+query 20: -b-b-
+query 21: a-a-a
+query 22: ababa
+query 23: a-a-a
+query 24: ababa
+query 25: a-a-a
+query 26: ababa
+query 27: a-a-a],
+  [query])
diff --git a/tests/ovsdb-row.at b/tests/ovsdb-row.at
new file mode 100644 (file)
index 0000000..e631f6f
--- /dev/null
@@ -0,0 +1,277 @@
+AT_BANNER([OVSDB -- rows])
+
+# Autoconf 2.63 has a bug that causes the double-quotes below to be
+# lost, so that the following tests fail, so we mark them as XFAIL for
+# Autoconf < 2.64.
+
+m4_define([RESERVED_COLUMNS], [["_uuid":["uuid","00000000-0000-0000-0000-000000000000"],"_version":["uuid","00000000-0000-0000-0000-000000000000"]]])
+
+OVSDB_CHECK_POSITIVE([row with one string column],
+  [[parse-rows \
+    '{"columns": {"name": {"type": "string"}}}' \
+    '{"name": "value"}' \
+    '{"name": ""}' \
+    '{"name": "longer string with spaces"}' \
+    '{}']],
+  [{RESERVED_COLUMNS,"name":"value"}
+name
+{RESERVED_COLUMNS,"name":""}
+name
+{RESERVED_COLUMNS,"name":"longer string with spaces"}
+name
+{RESERVED_COLUMNS,"name":""}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row with one integer column],
+  [[parse-rows \
+    '{"columns": {"count": {"type": "integer"}}}' \
+    '{"count": 1}' \
+    '{"count": -1}' \
+    '{"count": 2e10}' \
+    '{}']],
+  [{RESERVED_COLUMNS,"count":1}
+count
+{RESERVED_COLUMNS,"count":-1}
+count
+{RESERVED_COLUMNS,"count":20000000000}
+count
+{RESERVED_COLUMNS,"count":0}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row with one real column],
+  [[parse-rows \
+    '{"columns": {"cost": {"type": "real"}}}' \
+    '{"cost": 1.0}' \
+    '{"cost": -2.0}' \
+    '{"cost": 123000}' \
+    '{}']],
+  [{RESERVED_COLUMNS,"cost":1}
+cost
+{RESERVED_COLUMNS,"cost":-2}
+cost
+{RESERVED_COLUMNS,"cost":123000}
+cost
+{RESERVED_COLUMNS,"cost":0}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row with one boolean column],
+  [[parse-rows \
+    '{"columns": {"feasible": {"type": "boolean"}}}' \
+    '{"feasible": true}' \
+    '{"feasible": false}' \
+    '{}']],
+  [{RESERVED_COLUMNS,"feasible":true}
+feasible
+{RESERVED_COLUMNS,"feasible":false}
+feasible
+{RESERVED_COLUMNS,"feasible":false}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row with one uuid column],
+  [[parse-rows \
+    '{"columns": {"ref": {"type": "uuid"}}}' \
+    '{"ref": ["uuid", "f707423d-bf5b-48b5-b6c0-797c900ba4b6"]}' \
+    '{"ref": ["uuid", "33583cc5-d2f4-43de-b1ca-8aac14071b51"]}' \
+    '{}']],
+  [{RESERVED_COLUMNS,"ref":[["uuid","f707423d-bf5b-48b5-b6c0-797c900ba4b6"]]}
+ref
+{RESERVED_COLUMNS,"ref":[["uuid","33583cc5-d2f4-43de-b1ca-8aac14071b51"]]}
+ref
+{RESERVED_COLUMNS,"ref":[["uuid","00000000-0000-0000-0000-000000000000"]]}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row with set of 1 to 2 elements],
+  [[parse-rows \
+    '{"columns": {"myset": {"type": {"key": "integer", "min": 1, "max": 2}}}}' \
+    '{}']],
+  [{RESERVED_COLUMNS,["myset":["set",[0]]]}
+<none>])
+
+OVSDB_CHECK_POSITIVE([row with map of 1 to 2 elements],
+  [[parse-rows \
+    '{"columns": {"mymap": {"type": {"key": "integer", "value": "uuid", "min": 1, "max": 2}}}}' \
+    '{}']],
+  [{RESERVED_COLUMNS,["mymap":["map",[[0,["uuid","00000000-0000-0000-0000-000000000000"]]]]]}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row with several columns],
+  [[parse-rows \
+    '{"columns":
+        {"vswitch": {"type": "uuid"},
+         "name": {"type": "string"},
+         "datapath_id": {"type": {"key": "string", "min": 0}},
+         "hwaddr": {"type": "string"},
+         "mirrors": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+         "netflows": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+         "controller": {"type": {"key": "uuid", "min": 0}},
+         "listeners": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+         "snoops": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}}' \
+    '{"vswitch": ["uuid", "1a5c7280-0d4c-4e34-9ec7-c772339f7774"],
+      "name": "br0",
+      "datapath_id": ["set", ["000ae4256bb0"]],
+      "hwaddr": "00:0a:e4:25:6b:b0"}' \
+    '{}']],
+ [{RESERVED_COLUMNS,["controller":["set",[]],"datapath_id":["set",["000ae4256bb0"]],"hwaddr":"00:0a:e4:25:6b:b0","listeners":["set",[]],"mirrors":["set",[]],"name":"br0","netflows":["set",[]],"snoops":["set",[]],"vswitch":["uuid","1a5c7280-0d4c-4e34-9ec7-c772339f7774"]]}
+datapath_id, hwaddr, name, vswitch
+{RESERVED_COLUMNS,["controller":["set",[]],"datapath_id":["set",[]],"hwaddr":"","listeners":["set",[]],"mirrors":["set",[]],"name":"","netflows":["set",[]],"snoops":["set",[]],"vswitch":["uuid","00000000-0000-0000-0000-000000000000"]]}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row hashing (scalars)],
+  [[compare-rows \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+     '["null", {}]' \
+     '["i1", {"i": 1}]' \
+     '["i2", {"i": 2}]' \
+     '["i4", {"i": 4}]' \
+     '["i8", {"i": 8}]' \
+     '["i16", {"i": 16}]' \
+     '["i32", {"i": 32}]' \
+     '["i64", {"i": 64}]' \
+     '["i128", {"i": 128}]' \
+     '["i256", {"i": 256}]' \
+     '["null2", {"r": -0}]' \
+     '["r123", {"r": 123}]' \
+     '["r0.0625", {"r": 0.0625}]' \
+     '["r0.125", {"r": 0.125}]' \
+     '["r0.25", {"r": 0.25}]' \
+     '["r0.5", {"r": 0.5}]' \
+     '["r1", {"r": 1}]' \
+     '["r2", {"r": 2}]' \
+     '["r4", {"r": 4}]' \
+     '["r8", {"r": 8}]' \
+     '["r16", {"r": 16}]' \
+     '["r32", {"r": 32}]' \
+     '["null3", {"b": false}]' \
+     '["b1", {"b": true}]' \
+     '["null4", {"s": ""}]' \
+     '["s0", {"s": "a"}]' \
+     '["s1", {"s": "b"}]' \
+     '["s2", {"s": "c"}]' \
+     '["s3", {"s": "d"}]' \
+     '["s4", {"s": "e"}]' \
+     '["s5", {"s": "f"}]' \
+     '["s6", {"s": "g"}]' \
+     '["s7", {"s": "h"}]' \
+     '["s8", {"s": "i"}]' \
+     '["s9", {"s": "j"}]' \
+     '["null5", {"u": ["uuid","00000000-0000-0000-0000-000000000000"]}]' \
+     '["u1", {"u": ["uuid","10000000-0000-0000-0000-000000000000"]}]' \
+     '["u2", {"u": ["uuid","01000000-0000-0000-0000-000000000000"]}]' \
+     '["u3", {"u": ["uuid","00100000-0000-0000-0000-000000000000"]}]' \
+     '["u4", {"u": ["uuid","00010000-0000-0000-0000-000000000000"]}]' \
+     '["u5", {"u": ["uuid","00001000-0000-0000-0000-000000000000"]}]' \
+     '["u6", {"u": ["uuid","00000100-0000-0000-0000-000000000000"]}]' \
+     '["u7", {"u": ["uuid","00000010-0000-0000-0000-000000000000"]}]' \
+     '["u8", {"u": ["uuid","00000001-0000-0000-0000-000000000000"]}]' \
+     '["null6", {"u": ["uuid","00000000-c6db-4d22-970f-b41fabd20c4b"]}]']],
+  [[null == null2
+null == null3
+null == null4
+null == null5
+hash(null) == hash(null6)
+null2 == null3
+null2 == null4
+null2 == null5
+hash(null2) == hash(null6)
+null3 == null4
+null3 == null5
+hash(null3) == hash(null6)
+null4 == null5
+hash(null4) == hash(null6)
+hash(null5) == hash(null6)]])
+
+OVSDB_CHECK_POSITIVE([row hashing (sets)],
+  [[compare-rows \
+    '{"columns":
+        {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}},
+         "r": {"type": {"key": "real", "min": 0, "max": "unlimited"}},
+         "b": {"type": {"key": "boolean", "min": 0, "max": "unlimited"}},
+        "s": {"type": {"key": "string", "min": 0, "max": "unlimited"}},
+         "u": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}}' \
+    '["null0", {"i": ["set", []]}]' \
+    '["i0", {"i": ["set", [0]]}]' \
+    '["i01", {"i": ["set", [0, 1]]}]' \
+    '["i012", {"i": ["set", [0, 1, 2]]}]' \
+    '["i021", {"i": ["set", [0, 2, 1]]}]' \
+    '["i201", {"i": ["set", [2, 0, 1]]}]' \
+    '["i102", {"i": ["set", [1, 0, 2]]}]' \
+    '["i120", {"i": ["set", [1, 2, 0]]}]' \
+    '["i210", {"i": ["set", [2, 1, 0]]}]' \
+    '["r0", {"r": ["set", [0]]}]' \
+    '["r01", {"r": ["set", [0, 1]]}]' \
+    '["r012", {"r": ["set", [0, 1, 2]]}]' \
+    '["r201", {"r": ["set", [2, 0, 1]]}]' \
+    '["null1", {"b": ["set", []]}]' \
+    '["b0", {"b": ["set", [false]]}]' \
+    '["b1", {"b": ["set", [true]]}]' \
+    '["b01", {"b": ["set", [false, true]]}]' \
+    '["b10", {"b": ["set", [true, false]]}]' \
+    '["null2", {"s": ["set", []]}]' \
+    '["sa", {"s": ["set", ["a"]]}]' \
+    '["sb", {"s": ["set", ["b"]]}]' \
+    '["sab", {"s": ["set", ["a", "b"]]}]' \
+    '["sba", {"s": ["set", ["b", "a"]]}]']],
+  [[null0 == null1
+null0 == null2
+i012 == i021
+i012 == i201
+i012 == i102
+i012 == i120
+i012 == i210
+i021 == i201
+i021 == i102
+i021 == i120
+i021 == i210
+i201 == i102
+i201 == i120
+i201 == i210
+i102 == i120
+i102 == i210
+i120 == i210
+r012 == r201
+null1 == null2
+b01 == b10
+sab == sba]])
+
+OVSDB_CHECK_POSITIVE([row hashing (maps)],
+  [[compare-rows \
+    '{"columns":
+        {"ii": {"type": {"key": "integer", "value": "integer", 
+                         "min": 0, "max": "unlimited"}},
+         "rr": {"type": {"key": "real", "value": "real",
+                         "min": 0, "max": "unlimited"}},
+         "bb": {"type": {"key": "boolean", "value": "boolean",
+                         "min": 0, "max": "unlimited"}},
+        "ss": {"type": {"key": "string", "value": "string",
+                         "min": 0, "max": "unlimited"}}}}' \
+    '["null", {}]' \
+    '["ii0", {"ii": ["map", [[0, 0]]]}]' \
+    '["ii1", {"ii": ["map", [[0, 1]]]}]' \
+    '["ii00", {"ii": ["map", [[0, 0], [1, 0]]]}]' \
+    '["ii01", {"ii": ["map", [[0, 0], [1, 1]]]}]' \
+    '["ii10", {"ii": ["map", [[0, 1], [1, 0]]]}]' \
+    '["ii11", {"ii": ["map", [[0, 1], [1, 1]]]}]' \
+    '["rr0", {"rr": ["map", [[0, 0]]]}]' \
+    '["rr0", {"rr": ["map", [[0, 1]]]}]' \
+    '["rr00", {"rr": ["map", [[0, 0], [1, 0]]]}]' \
+    '["rr01", {"rr": ["map", [[0, 0], [1, 1]]]}]' \
+    '["rr10", {"rr": ["map", [[0, 1], [1, 0]]]}]' \
+    '["rr11", {"rr": ["map", [[0, 1], [1, 1]]]}]' \
+    '["bb0", {"bb": ["map", [[false, false]]]}]' \
+    '["bb1", {"bb": ["map", [[false, true]]]}]' \
+    '["bb00", {"bb": ["map", [[false, false], [true, false]]]}]' \
+    '["bb01", {"bb": ["map", [[false, false], [true, true]]]}]' \
+    '["bb10", {"bb": ["map", [[false, true], [true, false]]]}]' \
+    '["bb11", {"bb": ["map", [[false, true], [true, true]]]}]' \
+    '["ss0", {"ss": ["map", [["a", "a"]]]}]' \
+    '["ss1", {"ss": ["map", [["a", "b"]]]}]' \
+    '["ss00", {"ss": ["map", [["a", "a"], ["b", "a"]]]}]' \
+    '["ss01", {"ss": ["map", [["a", "a"], ["b", "b"]]]}]' \
+    '["ss10", {"ss": ["map", [["a", "b"], ["b", "a"]]]}]' \
+    '["ss11", {"ss": ["map", [["a", "b"], ["b", "b"]]]}]'; echo
+]], [[]])
diff --git a/tests/ovsdb-server.at b/tests/ovsdb-server.at
new file mode 100644 (file)
index 0000000..359f7e2
--- /dev/null
@@ -0,0 +1,34 @@
+AT_BANNER([OVSDB -- ovsdb-server transactions])
+
+# OVSDB_CHECK_EXECUTION(TITLE, SCHEMA, TRANSACTIONS, OUTPUT, [KEYWORDS])
+#
+# Creates a database with the given SCHEMA, starts an ovsdb-server on
+# that database, and runs each of the TRANSACTIONS (which should be a
+# quoted list of quoted strings) against it with ovsdb-client one at a
+# time.
+#
+# Checks that the overall output is OUTPUT, but UUIDs in the output
+# are replaced by markers of the form <N> where N is a number.  The
+# first unique UUID is replaced by <0>, the next by <1>, and so on.
+# If a given UUID appears more than once it is always replaced by the
+# same marker.
+#
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.
+m4_define([OVSDB_CHECK_EXECUTION], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb server positive $5])
+   AT_DATA([schema], [$2
+])
+   OVS_CHECK_LCOV([ovsdb-tool create db schema], [0], [stdout], [ignore])
+   AT_CHECK([ovsdb-server --detach --pidfile=$PWD/pid --listen=punix:socket --unixctl=$PWD/unixctl db])
+   m4_foreach([txn], [$3], 
+     [OVS_CHECK_LCOV([ovsdb-client transact unix:socket 'txn'], [0], [stdout], [ignore],
+     [test ! -e pid || kill `cat pid`])
+cat stdout >> output
+])
+   AT_CHECK([perl $srcdir/uuidfilt.pl output], [0], [$4], [],
+            [test ! -e pid || kill `cat pid`])
+   test ! -e pid || kill `cat pid`
+   AT_CLEANUP])
+
+EXECUTION_EXAMPLES
diff --git a/tests/ovsdb-table.at b/tests/ovsdb-table.at
new file mode 100644 (file)
index 0000000..ebc5992
--- /dev/null
@@ -0,0 +1,31 @@
+AT_BANNER([OVSDB -- tables])
+
+OVSDB_CHECK_POSITIVE([table with one column],
+  [[parse-table mytable '{"columns": {"name": {"type": "string"}}}']],
+  [[{"columns":{"name":{"type":"string"}}}]])
+
+OVSDB_CHECK_POSITIVE([immutable table with one column],
+  [[parse-table mytable \
+    '{"columns": {"name": {"type": "string"}},
+      "mutable": false}']],
+  [[{"columns":{"name":{"type":"string"}},"mutable":false}]])
+
+OVSDB_CHECK_POSITIVE([table with comment],
+  [[parse-table mytable \
+    '{"columns": {"name": {"type": "string"}},
+      "comment": "description of table"}']],
+  [[{"columns":{"name":{"type":"string"}},"comment":"description of table"}]])
+
+OVSDB_CHECK_NEGATIVE([column names may not begin with _],
+  [[parse-table mytable \
+    '{"columns": {"_column": {"type": "integer"}}}']],
+  [[names beginning with "_" are reserved]],
+  [table])
+
+OVSDB_CHECK_NEGATIVE([table must have at least one column (1)],
+  [[parse-table mytable '{}']],
+  [[Parsing table schema for table mytable failed: Required 'columns' member is missing.]])
+
+OVSDB_CHECK_NEGATIVE([table must have at least one column (2)],
+  [[parse-table mytable '{"columns": {}}']],
+  [[table must have at least one column]])
diff --git a/tests/ovsdb-transaction.at b/tests/ovsdb-transaction.at
new file mode 100644 (file)
index 0000000..f0c29d4
--- /dev/null
@@ -0,0 +1,384 @@
+AT_BANNER([OVSDB -- transactions])
+
+OVSDB_CHECK_POSITIVE([empty table, empty transaction],
+  [[transact \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+print:
+commit:
+print:
+abort:
+print:])
+
+OVSDB_CHECK_POSITIVE([nonempty table, empty transaction],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+print:
+1: i=2, j=3
+2: i=2, j=3
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3])
+
+OVSDB_CHECK_POSITIVE([insert, commit],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["insert", "3", "1", "2"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 1 2:
+print:
+1: i=2, j=3
+2: i=2, j=3
+3: i=1, j=2
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+3: i=1, j=2],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([insert, abort],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["insert", "3", "1", "2"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 1 2:
+print:
+1: i=2, j=3
+2: i=2, j=3
+3: i=1, j=2
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([modify, commit],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["modify", "2", "5", "-1"]' \
+    '["modify", "1", "-1", "4"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+modify 2 5 -1:
+modify 1 -1 4:
+print:
+1: i=2, j=4
+2: i=5, j=3
+commit:
+print:
+1: i=2, j=4
+2: i=5, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([modify, abort],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["modify", "2", "5", "-1"]' \
+    '["modify", "1", "-1", "4"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+modify 2 5 -1:
+modify 1 -1 4:
+print:
+1: i=2, j=4
+2: i=5, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([delete, commit],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["delete", "1"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+delete 1:
+print:
+2: i=2, j=3
+commit:
+print:
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([delete, abort],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["delete", "1"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+delete 1:
+print:
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([modify, delete, commit],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["modify", "1", "5", "6"]' \
+    '["delete", "1"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+modify 1 5 6:
+delete 1:
+print:
+2: i=2, j=3
+commit:
+print:
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([modify, delete, abort],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["modify", "1", "5", "6"]' \
+    '["delete", "1"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+modify 1 5 6:
+delete 1:
+print:
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([insert, delete, commit],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["insert", "3", "5", "6"]' \
+    '["delete", "1"]' \
+    '["delete", "3"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 5 6:
+delete 1:
+delete 3:
+print:
+2: i=2, j=3
+commit:
+print:
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([insert, delete, abort],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["insert", "3", "5", "6"]' \
+    '["delete", "1"]' \
+    '["delete", "3"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 5 6:
+delete 1:
+delete 3:
+print:
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+  [transaction])
+
+
+OVSDB_CHECK_POSITIVE([insert, modify, delete, commit],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["insert", "3", "5", "6"]' \
+    '["delete", "1"]' \
+    '["modify", "3", "7", "8"]' \
+    '["delete", "3"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 5 6:
+delete 1:
+modify 3 7 8:
+delete 3:
+print:
+2: i=2, j=3
+commit:
+print:
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([insert, modify, delete, abort],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["insert", "3", "5", "6"]' \
+    '["delete", "1"]' \
+    '["modify", "3", "7", "8"]' \
+    '["delete", "3"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 5 6:
+delete 1:
+modify 3 7 8:
+delete 3:
+print:
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+  [transaction])
+
diff --git a/tests/ovsdb-trigger.at b/tests/ovsdb-trigger.at
new file mode 100644 (file)
index 0000000..49ac69f
--- /dev/null
@@ -0,0 +1,173 @@
+AT_BANNER([OVSDB -- triggers])
+
+# This is like OVSDB_CHECK_POSITIVE, except that UUIDs in the output
+# are replaced by markers of the form <N> where N is a number.  The
+# first unique UUID is replaced by <0>, the next by <1>, and so on.
+# If a given UUID appears more than once it is always replaced by the
+# same marker.
+m4_define([OVSDB_CHECK_TRIGGER], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb execute execution trigger positive $4])
+   OVS_CHECK_LCOV([test-ovsdb trigger $2], [0], [stdout], [])
+   AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0], [$3])
+   AT_CLEANUP])
+
+OVSDB_CHECK_TRIGGER([trigger fires immediately],
+  ['ORDINAL_SCHEMA' [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 10,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1}]},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 2, "name": "two"}}]']],
+  [[t=0: trigger 0 (immediate): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{},{"uuid":["uuid","<2>"]}]
+]])
+
+OVSDB_CHECK_TRIGGER([trigger times out],
+  ['ORDINAL_SCHEMA' [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 10,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1},
+                {"name": "two", "number": 2}]}]' \
+    '["advance", 10]']],
+  [[t=0: new trigger 0
+t=10: trigger 0 (delayed): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"details":"\"wait\" timed out after 10 ms","error":"timed out"}]
+]])
+
+OVSDB_CHECK_TRIGGER([trigger fires after delay],
+  ['ORDINAL_SCHEMA' [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}}]' \
+    '["advance", 5]' \
+    '[{"op": "wait",
+       "timeout": 10,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1},
+                {"name": "two", "number": 2}]}]' \
+    '["advance", 5]' \
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 2, "name": "two"}}]']],
+  [[t=0: trigger 0 (immediate): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]
+t=5: new trigger 1
+t=10: trigger 2 (immediate): [{"uuid":["uuid","<2>"]}]
+t=10: trigger 1 (delayed): [{}]
+]])
+
+OVSDB_CHECK_TRIGGER([delayed trigger modifies database],
+  ['ORDINAL_SCHEMA' [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}}]' \
+    '["advance", 5]' \
+    '[{"op": "wait",
+       "timeout": 10,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1},
+                {"name": "two", "number": 2}]},
+      {"op": "delete",
+       "table": "ordinals",
+       "where": [["number", "<", 2]]}]' \
+    '["advance", 5]' \
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 2, "name": "two"}}]' \
+    '["advance", 5]' \
+    '[{"op": "select",
+       "table": "ordinals",
+       "where": []}]']],
+  [[t=0: trigger 0 (immediate): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]
+t=5: new trigger 1
+t=10: trigger 2 (immediate): [{"uuid":["uuid","<2>"]}]
+t=10: trigger 1 (delayed): [{},{"count":2}]
+t=15: trigger 3 (immediate): [{"rows":[{"_uuid":["uuid","<2>"],"_version":["uuid","<3>"],"name":"two","number":2}]}]
+]])
+
+OVSDB_CHECK_TRIGGER([one delayed trigger wakes up another],
+  ['ORDINAL_SCHEMA' [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}}]' \
+    '["advance", 5]' \
+    '[{"op": "wait",
+       "timeout": 10,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "two", "number": 2}]},
+      {"op": "delete",
+       "table": "ordinals",
+       "where": [["number", "==", 2]]},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 3, "name": "three"}}]' \
+    '[{"op": "wait",
+       "timeout": 10,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1},
+                {"name": "two", "number": 2}]},
+      {"op": "delete",
+       "table": "ordinals",
+       "where": [["number", "<", 2]]}]' \
+    '["advance", 5]' \
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 2, "name": "two"}}]' \
+    '["advance", 5]' \
+    '[{"op": "select",
+       "table": "ordinals",
+       "where": []}]']],
+  [[t=0: trigger 0 (immediate): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]
+t=5: new trigger 1
+t=5: new trigger 2
+t=10: trigger 3 (immediate): [{"uuid":["uuid","<2>"]}]
+t=10: trigger 2 (delayed): [{},{"count":2}]
+t=15: trigger 1 (delayed): [{},{"count":1},{"uuid":["uuid","<3>"]}]
+t=15: trigger 4 (immediate): [{"rows":[{"_uuid":["uuid","<3>"],"_version":["uuid","<4>"],"name":"three","number":3}]}]
+]])
+
diff --git a/tests/ovsdb-types.at b/tests/ovsdb-types.at
new file mode 100644 (file)
index 0000000..9a92a5c
--- /dev/null
@@ -0,0 +1,90 @@
+AT_BANNER([OVSDB -- atomic types])
+
+OVSDB_CHECK_POSITIVE([integer], 
+  [[parse-atomic-type '["integer"]' ]], ["integer"])
+OVSDB_CHECK_POSITIVE([real], 
+  [[parse-atomic-type '["real"]' ]], ["real"])
+OVSDB_CHECK_POSITIVE([boolean], 
+  [[parse-atomic-type '["boolean"]' ]], ["boolean"])
+OVSDB_CHECK_POSITIVE([string], 
+  [[parse-atomic-type '["string"]' ]], ["string"])
+OVSDB_CHECK_POSITIVE([uuid], 
+  [[parse-atomic-type '["uuid"]' ]], ["uuid"])
+OVSDB_CHECK_NEGATIVE([void is not a valid atomic-type],
+  [[parse-atomic-type '["void"]' ]], ["void" is not an atomic-type])
+
+AT_BANNER([OVSDB -- simple types])
+
+OVSDB_CHECK_POSITIVE([simple integer], 
+  [[parse-type '["integer"]' ]], ["integer"])
+OVSDB_CHECK_POSITIVE([simple real], 
+  [[parse-type '["real"]' ]], ["real"])
+OVSDB_CHECK_POSITIVE([simple boolean], 
+  [[parse-type '["boolean"]' ]], ["boolean"])
+OVSDB_CHECK_POSITIVE([simple string], 
+  [[parse-type '["string"]' ]], ["string"])
+OVSDB_CHECK_POSITIVE([simple uuid], 
+  [[parse-type '["uuid"]' ]], ["uuid"])
+OVSDB_CHECK_POSITIVE([integer in object],
+  [[parse-type '{"key": "integer"}' ]], ["integer"])
+OVSDB_CHECK_POSITIVE([real in object with explicit min and max],
+  [[parse-type '{"key": "real", "min": 1, "max": 1}' ]], ["real"])
+
+OVSDB_CHECK_NEGATIVE([key type is required],
+  [[parse-type '{}' ]], [Required 'key' member is missing.])
+OVSDB_CHECK_NEGATIVE([void is not a valid type],
+  [[parse-type '["void"]' ]], ["void" is not an atomic-type])
+
+AT_BANNER([OVSDB -- set types])
+
+OVSDB_CHECK_POSITIVE([optional boolean],
+  [[parse-type '{"key": "boolean", "min": 0}' ]], 
+  [[{"key":"boolean","min":0}]],
+  [set])
+OVSDB_CHECK_POSITIVE([set of 1 to 3 uuids],
+  [[parse-type '{"key": "uuid", "min": 1, "max": 3}' ]], 
+  [[{"key":"uuid","max":3}]])
+OVSDB_CHECK_POSITIVE([set of 0 to 3 strings],
+  [[parse-type '{"key": "string", "min": 0, "max": 3}' ]], 
+  [[{"key":"string","max":3,"min":0}]])
+OVSDB_CHECK_POSITIVE([set of 0 or more integers],
+  [[parse-type '{"key": "integer", "min": 0, "max": "unlimited"}']],
+  [[{"key":"integer","max":"unlimited","min":0}]])
+OVSDB_CHECK_POSITIVE([set of 10 or more reals],
+  [[parse-type '{"key": "real", "min": 10, "max": "unlimited"}']],
+  [[{"key":"real","max":"unlimited","min":10}]])
+
+OVSDB_CHECK_NEGATIVE([set max cannot be less than min],
+  [[parse-type '{"key": "real", "min": 5, "max": 3}' ]],
+  [ovsdb type fails constraint checks])
+OVSDB_CHECK_NEGATIVE([set max cannot be negative],
+  [[parse-type '{"key": "real", "max": -1}' ]],
+  [bad min or max value])
+OVSDB_CHECK_NEGATIVE([set min cannot be negative],
+  [[parse-type '{"key": "real", "min": -1}' ]],
+  [bad min or max value])
+
+AT_BANNER([OVSDB -- map types])
+
+OVSDB_CHECK_POSITIVE([map of 1 integer to boolean],
+ [[parse-type '{"key": "integer", "value": "boolean"}' ]],
+ [[{"key":"integer","value":"boolean"}]])
+OVSDB_CHECK_POSITIVE([map of 1 boolean to integer, explicit min and max],
+ [[parse-type '{"key": "boolean", "value": "integer", "min": 1, "max": 1}' ]],
+ [[{"key":"boolean","value":"integer"}]])
+OVSDB_CHECK_POSITIVE([map of 2 to 5 uuid to real],
+ [[parse-type '{"key": "uuid", "value": "real", "min": 2, "max": 5}' ]],
+ [[{"key":"uuid","max":5,"min":2,"value":"real"}]])
+OVSDB_CHECK_POSITIVE([map of 0 to 10 string to uuid],
+ [[parse-type '{"key": "string", "value": "uuid", "min": 0, "max": 10}' ]],
+ [[{"key":"string","max":10,"min":0,"value":"uuid"}]])
+OVSDB_CHECK_POSITIVE([map of 10 to 20 real to string],
+ [[parse-type '{"key": "real", "value": "string", "min": 10, "max": 20}' ]],
+ [[{"key":"real","max":20,"min":10,"value":"string"}]])
+OVSDB_CHECK_POSITIVE([map of 20 or more string to real],
+ [[parse-type '{"key": "string", "value": "real", "min": 20, "max": "unlimited"}' ]],
+ [[{"key":"string","max":"unlimited","min":20,"value":"real"}]])
+
+OVSDB_CHECK_NEGATIVE([map key type is required],
+ [[parse-type '{"value": "integer"}' ]],
+ [Required 'key' member is missing.])
diff --git a/tests/ovsdb.at b/tests/ovsdb.at
new file mode 100644 (file)
index 0000000..6e3f63d
--- /dev/null
@@ -0,0 +1,52 @@
+# OVSDB_CHECK_POSITIVE(TITLE, TEST-OVSDB-ARGS, OUTPUT, [KEYWORDS], [PREREQ])
+#
+# Runs "test-ovsdb TEST-OVSDB-ARGS" and checks that it exits with
+# status 0 and prints OUTPUT on stdout.
+#
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.  If
+# PREREQ is specified then the test is skipped if the Autoconf version
+# is less than PREREQ.
+m4_define([OVSDB_CHECK_POSITIVE], 
+  [AT_SETUP([$1])
+   m4_if([$5], [], [], 
+         [AT_XFAIL_IF([m4_version_prereq([$5], [false], [true])])])
+   AT_KEYWORDS([ovsdb positive $4])
+   OVS_CHECK_LCOV([test-ovsdb $2], [0], [$3
+], [])
+   AT_CLEANUP])
+
+# OVSDB_CHECK_NEGATIVE(TITLE, TEST-OVSDB-ARGS, OUTPUT, [KEYWORDS], [PREREQ])
+#
+# Runs "test-ovsdb TEST-OVSDB-ARGS" and checks that it exits with
+# status 1 and that its output on stdout contains substring OUTPUT.
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.  
+m4_define([OVSDB_CHECK_NEGATIVE], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb negative $4])
+   OVS_CHECK_LCOV([test-ovsdb $2], [1], [], [stderr])
+   m4_assert(m4_len([$3]))
+   AT_CHECK(
+     [if grep -F -e "AS_ESCAPE([$3])" stderr
+      then
+        :
+      else
+        exit 99
+      fi], 
+            [0], [ignore], [ignore])
+   AT_CLEANUP])
+
+m4_include([tests/ovsdb-log.at])
+m4_include([tests/ovsdb-types.at])
+m4_include([tests/ovsdb-data.at])
+m4_include([tests/ovsdb-column.at])
+m4_include([tests/ovsdb-table.at])
+m4_include([tests/ovsdb-row.at])
+m4_include([tests/ovsdb-condition.at])
+m4_include([tests/ovsdb-query.at])
+m4_include([tests/ovsdb-transaction.at])
+m4_include([tests/ovsdb-execution.at])
+m4_include([tests/ovsdb-trigger.at])
+m4_include([tests/ovsdb-file.at])
+m4_include([tests/ovsdb-server.at])
+m4_include([tests/ovsdb-monitor.at])
+m4_include([tests/ovsdb-idl.at])
diff --git a/tests/reconnect.at b/tests/reconnect.at
new file mode 100644 (file)
index 0000000..33e4b95
--- /dev/null
@@ -0,0 +1,1039 @@
+AT_BANNER([reconnect library])
+
+######################################################################
+AT_SETUP([nothing happens if not enabled])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [run
+timeout
+])
+OVS_CHECK_LCOV([test-reconnect < input], [0], 
+  [### t=1000 ###
+run
+timeout
+  no timeout
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([quick connect, idle disconnect])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [enable
+
+# Connection succeeds.
+run
+connected
+
+# Send inactivity probe.
+timeout
+run
+
+# Idle timeout kills connection.
+timeout
+run
+disconnected
+])
+OVS_CHECK_LCOV([test-reconnect < input], [0], 
+  [### t=1000 ###
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+
+# Connection succeeds.
+run
+  should connect
+connected
+  in ACTIVE for 0 ms (0 ms backoff)
+  1 successful connections out of 1 attempts, seqno 1
+  connected (0 ms), total 0 ms connected
+
+# Send inactivity probe.
+timeout
+  advance 5000 ms
+
+### t=6000 ###
+  in ACTIVE for 5000 ms (0 ms backoff)
+  connected (5000 ms), total 5000 ms connected
+run
+  should send probe
+  in IDLE for 0 ms (0 ms backoff)
+
+# Idle timeout kills connection.
+timeout
+  advance 5000 ms
+
+### t=11000 ###
+  in IDLE for 5000 ms (0 ms backoff)
+  connected (10000 ms), total 10000 ms connected
+run
+  should disconnect
+disconnected
+  in BACKOFF for 0 ms (1000 ms backoff)
+  1 successful connections out of 1 attempts, seqno 2
+  not connected (0 ms), total 10000 ms connected
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([slow connect, idle disconnect])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [enable
+
+# Start connecting.
+run
+connecting
+
+# Connect after 500 ms.
+advance 500
+run
+connected
+
+# Send inactivity probe.
+timeout
+run
+
+# Idle timeout kills connection.
+timeout
+run
+disconnected
+])
+OVS_CHECK_LCOV([test-reconnect < input], [0], 
+  [### t=1000 ###
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+
+# Start connecting.
+run
+  should connect
+connecting
+  in CONNECTING for 0 ms (0 ms backoff)
+
+# Connect after 500 ms.
+advance 500
+
+### t=1500 ###
+  in CONNECTING for 500 ms (0 ms backoff)
+run
+  should connect
+connected
+  in ACTIVE for 0 ms (0 ms backoff)
+  created 1000, last received 1000, last connected 1500
+  1 successful connections out of 1 attempts, seqno 1
+  connected (0 ms), total 0 ms connected
+
+# Send inactivity probe.
+timeout
+  advance 5000 ms
+
+### t=6500 ###
+  in ACTIVE for 5000 ms (0 ms backoff)
+  connected (5000 ms), total 5000 ms connected
+run
+  should send probe
+  in IDLE for 0 ms (0 ms backoff)
+
+# Idle timeout kills connection.
+timeout
+  advance 5000 ms
+
+### t=11500 ###
+  in IDLE for 5000 ms (0 ms backoff)
+  connected (10000 ms), total 10000 ms connected
+run
+  should disconnect
+disconnected
+  in BACKOFF for 0 ms (1000 ms backoff)
+  1 successful connections out of 1 attempts, seqno 2
+  not connected (0 ms), total 10000 ms connected
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([connect backs off])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [enable
+
+# First connection attempt fails after 1000 ms.
+run
+connecting
+run
+timeout
+run
+connect-failed
+
+# Back off for 1000 ms.
+timeout
+run
+
+# Second connection attempt fails after 1000 ms.
+connecting
+timeout
+run
+connect-failed
+
+# Back off for 2000 ms.
+timeout
+run
+
+# Third connection attempt fails after 2000 ms.
+connecting
+timeout
+run
+connect-failed
+
+# Back off for 4000 ms.
+timeout
+run
+
+# Third connection attempt fails after 4000 ms.
+connecting
+timeout
+run
+connect-failed
+
+# Back off for 8000 ms.
+timeout
+run
+
+# Third connection attempt fails after 8000 ms.
+connecting
+timeout
+run
+connect-failed
+
+# Back off for 8000 ms.
+timeout
+run
+
+# Fourth connection attempt fails after 8000 ms.
+connecting
+timeout
+run
+connect-failed
+])
+OVS_CHECK_LCOV([test-reconnect < input], [0], 
+  [### t=1000 ###
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+
+# First connection attempt fails after 1000 ms.
+run
+  should connect
+connecting
+  in CONNECTING for 0 ms (0 ms backoff)
+run
+  should connect
+timeout
+  advance 1000 ms
+
+### t=2000 ###
+  in CONNECTING for 1000 ms (0 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (1000 ms backoff)
+  0 successful connections out of 1 attempts, seqno 0
+
+# Back off for 1000 ms.
+timeout
+  advance 1000 ms
+
+### t=3000 ###
+  in BACKOFF for 1000 ms (1000 ms backoff)
+run
+  should connect
+
+# Second connection attempt fails after 1000 ms.
+connecting
+  in CONNECTING for 0 ms (1000 ms backoff)
+timeout
+  advance 1000 ms
+
+### t=4000 ###
+  in CONNECTING for 1000 ms (1000 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (2000 ms backoff)
+  0 successful connections out of 2 attempts, seqno 0
+
+# Back off for 2000 ms.
+timeout
+  advance 2000 ms
+
+### t=6000 ###
+  in BACKOFF for 2000 ms (2000 ms backoff)
+run
+  should connect
+
+# Third connection attempt fails after 2000 ms.
+connecting
+  in CONNECTING for 0 ms (2000 ms backoff)
+timeout
+  advance 2000 ms
+
+### t=8000 ###
+  in CONNECTING for 2000 ms (2000 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (4000 ms backoff)
+  0 successful connections out of 3 attempts, seqno 0
+
+# Back off for 4000 ms.
+timeout
+  advance 4000 ms
+
+### t=12000 ###
+  in BACKOFF for 4000 ms (4000 ms backoff)
+run
+  should connect
+
+# Third connection attempt fails after 4000 ms.
+connecting
+  in CONNECTING for 0 ms (4000 ms backoff)
+timeout
+  advance 4000 ms
+
+### t=16000 ###
+  in CONNECTING for 4000 ms (4000 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (8000 ms backoff)
+  0 successful connections out of 4 attempts, seqno 0
+
+# Back off for 8000 ms.
+timeout
+  advance 8000 ms
+
+### t=24000 ###
+  in BACKOFF for 8000 ms (8000 ms backoff)
+run
+  should connect
+
+# Third connection attempt fails after 8000 ms.
+connecting
+  in CONNECTING for 0 ms (8000 ms backoff)
+timeout
+  advance 8000 ms
+
+### t=32000 ###
+  in CONNECTING for 8000 ms (8000 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (8000 ms backoff)
+  0 successful connections out of 5 attempts, seqno 0
+
+# Back off for 8000 ms.
+timeout
+  advance 8000 ms
+
+### t=40000 ###
+  in BACKOFF for 8000 ms (8000 ms backoff)
+run
+  should connect
+
+# Fourth connection attempt fails after 8000 ms.
+connecting
+  in CONNECTING for 0 ms (8000 ms backoff)
+timeout
+  advance 8000 ms
+
+### t=48000 ###
+  in CONNECTING for 8000 ms (8000 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (8000 ms backoff)
+  0 successful connections out of 6 attempts, seqno 0
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([connections with no data preserve backoff])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [enable
+
+# First connect, then idle timeout kills connection.
+run
+connected
+timeout
+run
+timeout
+run
+disconnected
+
+# Back off for 1000 ms.
+timeout
+run
+
+# Second connect, then idle timeout kills connection.
+run
+connected
+timeout
+run
+timeout
+run
+disconnected
+
+# Back off for 2000 ms.
+timeout
+run
+
+# Third connect, then idle timeout kills connection.
+run
+connected
+timeout
+run
+timeout
+run
+disconnected
+
+# Back off for 4000 ms.
+timeout
+], [### t=1000 ###
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+
+# First connect, then idle timeout kills connection.
+run
+  should connect
+connected
+  in ACTIVE for 0 ms (0 ms backoff)
+  1 successful connections out of 1 attempts, seqno 1
+  connected (0 ms), total 0 ms connected
+timeout
+  advance 5000 ms
+
+### t=6000 ###
+  in ACTIVE for 5000 ms (0 ms backoff)
+  connected (5000 ms), total 5000 ms connected
+run
+  should send probe
+  in IDLE for 0 ms (0 ms backoff)
+timeout
+  advance 5000 ms
+
+### t=11000 ###
+  in IDLE for 5000 ms (0 ms backoff)
+  connected (10000 ms), total 10000 ms connected
+run
+  should disconnect
+disconnected
+  in BACKOFF for 0 ms (1000 ms backoff)
+  1 successful connections out of 1 attempts, seqno 2
+  not connected (0 ms), total 10000 ms connected
+
+# Back off for 1000 ms.
+timeout
+  advance 1000 ms
+
+### t=12000 ###
+  in BACKOFF for 1000 ms (1000 ms backoff)
+run
+  should connect
+
+# Second connect, then idle timeout kills connection.
+run
+  should connect
+connected
+  in ACTIVE for 0 ms (1000 ms backoff)
+  created 1000, last received 1000, last connected 12000
+  2 successful connections out of 2 attempts, seqno 3
+  connected (0 ms), total 10000 ms connected
+timeout
+  advance 5000 ms
+
+### t=17000 ###
+  in ACTIVE for 5000 ms (1000 ms backoff)
+  connected (5000 ms), total 15000 ms connected
+run
+  should send probe
+  in IDLE for 0 ms (1000 ms backoff)
+timeout
+  advance 5000 ms
+
+### t=22000 ###
+  in IDLE for 5000 ms (1000 ms backoff)
+  connected (10000 ms), total 20000 ms connected
+run
+  should disconnect
+disconnected
+  in BACKOFF for 0 ms (2000 ms backoff)
+  2 successful connections out of 2 attempts, seqno 4
+  not connected (0 ms), total 20000 ms connected
+
+# Back off for 2000 ms.
+timeout
+  advance 2000 ms
+
+### t=24000 ###
+  in BACKOFF for 2000 ms (2000 ms backoff)
+run
+  should connect
+
+# Third connect, then idle timeout kills connection.
+run
+  should connect
+connected
+  in ACTIVE for 0 ms (2000 ms backoff)
+  created 1000, last received 1000, last connected 24000
+  3 successful connections out of 3 attempts, seqno 5
+  connected (0 ms), total 20000 ms connected
+timeout
+  advance 5000 ms
+
+### t=29000 ###
+  in ACTIVE for 5000 ms (2000 ms backoff)
+  connected (5000 ms), total 25000 ms connected
+run
+  should send probe
+  in IDLE for 0 ms (2000 ms backoff)
+timeout
+  advance 5000 ms
+
+### t=34000 ###
+  in IDLE for 5000 ms (2000 ms backoff)
+  connected (10000 ms), total 30000 ms connected
+run
+  should disconnect
+disconnected
+  in BACKOFF for 0 ms (4000 ms backoff)
+  3 successful connections out of 3 attempts, seqno 6
+  not connected (0 ms), total 30000 ms connected
+
+# Back off for 4000 ms.
+timeout
+  advance 4000 ms
+
+### t=38000 ###
+  in BACKOFF for 4000 ms (4000 ms backoff)
+
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([brief connection preserves backoff])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [enable
+
+# First connection attempt fails after 1000 ms.
+run
+connecting
+run
+timeout
+run
+connect-failed
+
+# Back off for 1000 ms.
+timeout
+run
+
+# Second connection attempt fails after 1000 ms.
+connecting
+timeout
+run
+connect-failed
+
+# Back off for 2000 ms.
+timeout
+run
+
+# Third connection attempt succeeds after 500 ms.
+connecting
+advance 500
+run
+connected
+
+# Connection drops after another 250 ms.
+advance 250
+disconnected
+run
+
+# Back off for 4000 ms.
+timeout
+run
+])
+OVS_CHECK_LCOV([test-reconnect < input], [0], 
+  [### t=1000 ###
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+
+# First connection attempt fails after 1000 ms.
+run
+  should connect
+connecting
+  in CONNECTING for 0 ms (0 ms backoff)
+run
+  should connect
+timeout
+  advance 1000 ms
+
+### t=2000 ###
+  in CONNECTING for 1000 ms (0 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (1000 ms backoff)
+  0 successful connections out of 1 attempts, seqno 0
+
+# Back off for 1000 ms.
+timeout
+  advance 1000 ms
+
+### t=3000 ###
+  in BACKOFF for 1000 ms (1000 ms backoff)
+run
+  should connect
+
+# Second connection attempt fails after 1000 ms.
+connecting
+  in CONNECTING for 0 ms (1000 ms backoff)
+timeout
+  advance 1000 ms
+
+### t=4000 ###
+  in CONNECTING for 1000 ms (1000 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (2000 ms backoff)
+  0 successful connections out of 2 attempts, seqno 0
+
+# Back off for 2000 ms.
+timeout
+  advance 2000 ms
+
+### t=6000 ###
+  in BACKOFF for 2000 ms (2000 ms backoff)
+run
+  should connect
+
+# Third connection attempt succeeds after 500 ms.
+connecting
+  in CONNECTING for 0 ms (2000 ms backoff)
+advance 500
+
+### t=6500 ###
+  in CONNECTING for 500 ms (2000 ms backoff)
+run
+  should connect
+connected
+  in ACTIVE for 0 ms (2000 ms backoff)
+  created 1000, last received 1000, last connected 6500
+  1 successful connections out of 3 attempts, seqno 1
+  connected (0 ms), total 0 ms connected
+
+# Connection drops after another 250 ms.
+advance 250
+
+### t=6750 ###
+  in ACTIVE for 250 ms (2000 ms backoff)
+  connected (250 ms), total 250 ms connected
+disconnected
+  in BACKOFF for 0 ms (4000 ms backoff)
+  1 successful connections out of 3 attempts, seqno 2
+  not connected (0 ms), total 250 ms connected
+run
+
+# Back off for 4000 ms.
+timeout
+  advance 4000 ms
+
+### t=10750 ###
+  in BACKOFF for 4000 ms (4000 ms backoff)
+run
+  should connect
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([brief connection with data preserves backoff])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [enable
+
+# First connection attempt fails after 1000 ms.
+run
+connecting
+run
+timeout
+run
+connect-failed
+
+# Back off for 1000 ms.
+timeout
+run
+
+# Second connection attempt fails after 1000 ms.
+connecting
+timeout
+run
+connect-failed
+
+# Back off for 2000 ms.
+timeout
+run
+
+# Third connection attempt succeeds after 500 ms.
+connecting
+advance 500
+run
+connected
+
+# Connection receives 3 chunks of data spaced 250 ms apart.
+advance 250
+run
+received
+advance 250
+run
+received
+advance 250
+run
+received
+
+# Connection drops.
+disconnected
+run
+
+# Back off for 4000 ms.
+timeout
+run
+])
+OVS_CHECK_LCOV([test-reconnect < input], [0], 
+  [### t=1000 ###
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+
+# First connection attempt fails after 1000 ms.
+run
+  should connect
+connecting
+  in CONNECTING for 0 ms (0 ms backoff)
+run
+  should connect
+timeout
+  advance 1000 ms
+
+### t=2000 ###
+  in CONNECTING for 1000 ms (0 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (1000 ms backoff)
+  0 successful connections out of 1 attempts, seqno 0
+
+# Back off for 1000 ms.
+timeout
+  advance 1000 ms
+
+### t=3000 ###
+  in BACKOFF for 1000 ms (1000 ms backoff)
+run
+  should connect
+
+# Second connection attempt fails after 1000 ms.
+connecting
+  in CONNECTING for 0 ms (1000 ms backoff)
+timeout
+  advance 1000 ms
+
+### t=4000 ###
+  in CONNECTING for 1000 ms (1000 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (2000 ms backoff)
+  0 successful connections out of 2 attempts, seqno 0
+
+# Back off for 2000 ms.
+timeout
+  advance 2000 ms
+
+### t=6000 ###
+  in BACKOFF for 2000 ms (2000 ms backoff)
+run
+  should connect
+
+# Third connection attempt succeeds after 500 ms.
+connecting
+  in CONNECTING for 0 ms (2000 ms backoff)
+advance 500
+
+### t=6500 ###
+  in CONNECTING for 500 ms (2000 ms backoff)
+run
+  should connect
+connected
+  in ACTIVE for 0 ms (2000 ms backoff)
+  created 1000, last received 1000, last connected 6500
+  1 successful connections out of 3 attempts, seqno 1
+  connected (0 ms), total 0 ms connected
+
+# Connection receives 3 chunks of data spaced 250 ms apart.
+advance 250
+
+### t=6750 ###
+  in ACTIVE for 250 ms (2000 ms backoff)
+  connected (250 ms), total 250 ms connected
+run
+received
+  created 1000, last received 6750, last connected 6500
+advance 250
+
+### t=7000 ###
+  in ACTIVE for 500 ms (2000 ms backoff)
+  connected (500 ms), total 500 ms connected
+run
+received
+  created 1000, last received 7000, last connected 6500
+advance 250
+
+### t=7250 ###
+  in ACTIVE for 750 ms (2000 ms backoff)
+  connected (750 ms), total 750 ms connected
+run
+received
+  created 1000, last received 7250, last connected 6500
+
+# Connection drops.
+disconnected
+  in BACKOFF for 0 ms (4000 ms backoff)
+  1 successful connections out of 3 attempts, seqno 2
+  not connected (0 ms), total 750 ms connected
+run
+
+# Back off for 4000 ms.
+timeout
+  advance 4000 ms
+
+### t=11250 ###
+  in BACKOFF for 4000 ms (4000 ms backoff)
+run
+  should connect
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([long connection resets backoff])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [enable
+
+# First connection attempt fails after 1000 ms.
+run
+connecting
+run
+timeout
+run
+connect-failed
+
+# Back off for 1000 ms.
+timeout
+run
+
+# Second connection attempt fails after 1000 ms.
+connecting
+timeout
+run
+connect-failed
+
+# Back off for 2000 ms.
+timeout
+run
+
+# Third connection attempt succeeds after 500 ms.
+connecting
+advance 500
+run
+connected
+
+# Connection receives 3 chunks of data spaced 2000 ms apart.
+advance 2000
+run
+received
+advance 2000
+run
+received
+advance 2000
+run
+received
+
+# Connection drops.
+disconnected
+run
+
+# Back off for 1000 ms.
+timeout
+run
+])
+OVS_CHECK_LCOV([test-reconnect < input], [0], 
+  [### t=1000 ###
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+
+# First connection attempt fails after 1000 ms.
+run
+  should connect
+connecting
+  in CONNECTING for 0 ms (0 ms backoff)
+run
+  should connect
+timeout
+  advance 1000 ms
+
+### t=2000 ###
+  in CONNECTING for 1000 ms (0 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (1000 ms backoff)
+  0 successful connections out of 1 attempts, seqno 0
+
+# Back off for 1000 ms.
+timeout
+  advance 1000 ms
+
+### t=3000 ###
+  in BACKOFF for 1000 ms (1000 ms backoff)
+run
+  should connect
+
+# Second connection attempt fails after 1000 ms.
+connecting
+  in CONNECTING for 0 ms (1000 ms backoff)
+timeout
+  advance 1000 ms
+
+### t=4000 ###
+  in CONNECTING for 1000 ms (1000 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (2000 ms backoff)
+  0 successful connections out of 2 attempts, seqno 0
+
+# Back off for 2000 ms.
+timeout
+  advance 2000 ms
+
+### t=6000 ###
+  in BACKOFF for 2000 ms (2000 ms backoff)
+run
+  should connect
+
+# Third connection attempt succeeds after 500 ms.
+connecting
+  in CONNECTING for 0 ms (2000 ms backoff)
+advance 500
+
+### t=6500 ###
+  in CONNECTING for 500 ms (2000 ms backoff)
+run
+  should connect
+connected
+  in ACTIVE for 0 ms (2000 ms backoff)
+  created 1000, last received 1000, last connected 6500
+  1 successful connections out of 3 attempts, seqno 1
+  connected (0 ms), total 0 ms connected
+
+# Connection receives 3 chunks of data spaced 2000 ms apart.
+advance 2000
+
+### t=8500 ###
+  in ACTIVE for 2000 ms (2000 ms backoff)
+  connected (2000 ms), total 2000 ms connected
+run
+received
+  created 1000, last received 8500, last connected 6500
+advance 2000
+
+### t=10500 ###
+  in ACTIVE for 4000 ms (2000 ms backoff)
+  connected (4000 ms), total 4000 ms connected
+run
+received
+  created 1000, last received 10500, last connected 6500
+advance 2000
+
+### t=12500 ###
+  in ACTIVE for 6000 ms (2000 ms backoff)
+  connected (6000 ms), total 6000 ms connected
+run
+received
+  created 1000, last received 12500, last connected 6500
+
+# Connection drops.
+disconnected
+  in BACKOFF for 0 ms (1000 ms backoff)
+  1 successful connections out of 3 attempts, seqno 2
+  not connected (0 ms), total 6000 ms connected
+run
+
+# Back off for 1000 ms.
+timeout
+  advance 1000 ms
+
+### t=13500 ###
+  in BACKOFF for 1000 ms (1000 ms backoff)
+run
+  should connect
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([connection attempt fails quickly])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [enable
+
+# Connection fails quickly.
+run
+connect-failed ECONNREFUSED
+
+# Back off for 1000 ms.
+run
+timeout
+
+# Connection fails quickly again.
+run
+connect-failed ECONNREFUSED
+
+# Back off for 2000 ms.
+run
+timeout
+])
+OVS_CHECK_LCOV([test-reconnect < input], [0], 
+  [### t=1000 ###
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+
+# Connection fails quickly.
+run
+  should connect
+connect-failed ECONNREFUSED
+  in BACKOFF for 0 ms (1000 ms backoff)
+  0 successful connections out of 1 attempts, seqno 0
+
+# Back off for 1000 ms.
+run
+timeout
+  advance 1000 ms
+
+### t=2000 ###
+  in BACKOFF for 1000 ms (1000 ms backoff)
+
+# Connection fails quickly again.
+run
+  should connect
+connect-failed ECONNREFUSED
+  in BACKOFF for 0 ms (2000 ms backoff)
+  0 successful connections out of 2 attempts, seqno 0
+
+# Back off for 2000 ms.
+run
+timeout
+  advance 2000 ms
+
+### t=4000 ###
+  in BACKOFF for 2000 ms (2000 ms backoff)
+])
+AT_CLEANUP
+
diff --git a/tests/test-aes128.c b/tests/test-aes128.c
new file mode 100644 (file)
index 0000000..8a6c28c
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <ctype.h>
+#include "aes128.h"
+#include "util.h"
+
+static void
+hex_to_uint8(const char *input, uint8_t *output, size_t n)
+{
+    size_t i;
+
+    if (strlen(input) != n * 2) {
+        goto error;
+    }
+    for (i = 0; i < n; i++) {
+        unsigned char hi = input[i * 2];
+        unsigned char lo = input[i * 2 + 1];
+
+        if (!isxdigit(hi) || !isxdigit(lo)) {
+            goto error;
+        }
+        output[i] = (hexit_value(hi) << 4) + hexit_value(lo);
+    }
+    return;
+
+error:
+    ovs_fatal(0, "\"%s\" is not exactly %zu hex digits", input, n * 2);
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct aes128 aes;
+    uint8_t plaintext[16];
+    uint8_t ciphertext[16];
+    uint8_t key[16];
+    size_t i;
+
+    if (argc != 3) {
+        ovs_fatal(0, "usage: %s KEY PLAINTEXT, where KEY and PLAINTEXT each "
+                  "consist of 32 hex digits", argv[0]);
+    }
+
+    hex_to_uint8(argv[1], key, 16);
+    hex_to_uint8(argv[2], plaintext, 16);
+
+    aes128_schedule(&aes, key);
+    aes128_encrypt(&aes, plaintext, ciphertext);
+    for (i = 0; i < 16; i++) {
+        printf("%02x", ciphertext[i]);
+    }
+    putchar('\n');
+
+    return 0;
+}
index 0307e48..d36c8eb 100644 (file)
@@ -446,7 +446,7 @@ make_rule(int wc_fields, unsigned int priority, int value_pat)
         }
     }
 
-    rule = xcalloc(1, sizeof *rule);
+    rule = xzalloc(sizeof *rule);
     cls_rule_from_flow(&rule->cls_rule, &flow, wildcards,
                        !wildcards ? UINT_MAX : priority);
     return rule;
diff --git a/tests/test-dir_name.c b/tests/test-dir_name.c
new file mode 100644 (file)
index 0000000..627ecf1
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "util.h"
+#include <stdlib.h>
+
+int
+main(int argc, char *argv[])
+{
+    int i;
+
+    for (i = 1; i < argc; i++) {
+        char *dir = dir_name(argv[i]);
+        puts(dir);
+        free(dir);
+    }
+
+    return 0;
+}
diff --git a/tests/test-json.c b/tests/test-json.c
new file mode 100644 (file)
index 0000000..6261786
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "json.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+
+#include "util.h"
+
+/* --pretty: If set, the JSON output is pretty-printed, instead of printed as
+ * compactly as possible.  */
+static int pretty = 0;
+
+/* --multiple: If set, the input is a sequence of JSON objects or arrays,
+ * instead of exactly one object or array. */
+static int multiple = 0;
+
+static bool
+print_and_free_json(struct json *json)
+{
+    bool ok;
+    if (json->type == JSON_STRING) {
+        printf("error: %s\n", json->u.string);
+        ok = false;
+    } else {
+        char *s = json_to_string(json, JSSF_SORT | (pretty ? JSSF_PRETTY : 0));
+        puts(s);
+        free(s);
+        ok = true;
+    }
+    json_destroy(json);
+    return ok;
+}
+
+static bool
+refill(FILE *file, void *buffer, size_t buffer_size, size_t *n, size_t *used)
+{
+    *used = 0;
+    if (feof(file)) {
+        *n = 0;
+        return false;
+    } else {
+        *n = fread(buffer, 1, buffer_size, file);
+        if (ferror(file)) {
+            ovs_fatal(errno, "Error reading input file");
+        }
+        return *n > 0;
+    }
+}
+
+static bool
+parse_multiple(const char *input_file)
+{
+    struct json_parser *parser;
+    char buffer[BUFSIZ];
+    size_t n, used;
+    FILE *file;
+    bool ok;
+
+    file = fopen(input_file, "r");
+    if (!file) {
+        ovs_fatal(errno, "Cannot open \"%s\"", input_file);
+    }
+
+    parser = NULL;
+    n = used = 0;
+    ok = true;
+    while (used < n || refill(file, buffer, sizeof buffer, &n, &used)) {
+        if (!parser && isspace((unsigned char) buffer[used])) {
+            /* Skip white space. */
+            used++;
+        } else {
+            if (!parser) {
+                parser = json_parser_create(0);
+            }
+
+            used += json_parser_feed(parser, &buffer[used], n - used);
+            if (used < n) {
+                if (!print_and_free_json(json_parser_finish(parser))) {
+                    ok = false;
+                }
+                parser = NULL;
+            }
+        }
+    }
+    if (parser) {
+        if (!print_and_free_json(json_parser_finish(parser))) {
+            ok = false;
+        }
+    }
+    return ok;
+}
+
+int
+main(int argc, char *argv[])
+{
+    const char *input_file;
+    bool ok;
+
+    set_program_name(argv[0]);
+
+    for (;;) {
+        static const struct option options[] = {
+            {"pretty", no_argument, &pretty, 1},
+            {"multiple", no_argument, &multiple, 1},
+        };
+        int option_index = 0;
+        int c = getopt_long (argc, argv, "", options, &option_index);
+
+        if (c == -1) {
+            break;
+        }
+        switch (c) {
+        case 0:
+            break;
+
+        case '?':
+            exit(1);
+
+        default:
+            abort();
+        }
+    }
+
+    if (argc - optind != 1) {
+        ovs_fatal(0, "usage: %s [--pretty] [--multiple] INPUT.json",
+                  program_name);
+    }
+
+    input_file = argv[optind];
+    if (!strcmp(input_file, "-")) {
+        input_file = "/dev/stdin";
+    }
+
+    if (multiple) {
+        ok = parse_multiple(input_file);
+    } else {
+        ok = print_and_free_json(json_from_file(input_file));
+    }
+
+    return !ok;
+}
diff --git a/tests/test-jsonrpc.c b/tests/test-jsonrpc.c
new file mode 100644 (file)
index 0000000..42d2c39
--- /dev/null
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "jsonrpc.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "command-line.h"
+#include "daemon.h"
+#include "json.h"
+#include "poll-loop.h"
+#include "stream.h"
+#include "timeval.h"
+#include "util.h"
+#include "vlog.h"
+
+static struct command all_commands[];
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+    set_program_name(argv[0]);
+    time_init();
+    vlog_init();
+    parse_options(argc, argv);
+    run_command(argc - optind, argv + optind, all_commands);
+    return 0;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+    static struct option long_options[] = {
+        {"verbose", optional_argument, 0, 'v'},
+        {"help", no_argument, 0, 'h'},
+        DAEMON_LONG_OPTIONS,
+        {0, 0, 0, 0},
+    };
+    char *short_options = long_options_to_short_options(long_options);
+
+    for (;;) {
+        int c = getopt_long(argc, argv, short_options, long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case 'h':
+            usage();
+
+        case 'v':
+            vlog_set_verbosity(optarg);
+            break;
+
+        DAEMON_OPTION_HANDLERS
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+}
+
+static void
+usage(void)
+{
+    printf("%s: JSON-RPC test utility\n"
+           "usage: %s [OPTIONS] COMMAND [ARG...]\n"
+           "  listen LOCAL             listen for connections on LOCAL\n"
+           "  request REMOTE METHOD PARAMS   send request, print reply\n"
+           "  notify REMOTE METHOD PARAMS  send notification and exit\n",
+           program_name, program_name);
+    stream_usage("JSON-RPC", true, true);
+    daemon_usage();
+    vlog_usage();
+    printf("\nOther options:\n"
+           "  -h, --help                  display this help message\n");
+    exit(EXIT_SUCCESS);
+}
+\f
+/* Command helper functions. */
+
+static struct json *
+parse_json(const char *s)
+{
+    struct json *json = json_from_string(s);
+    if (json->type == JSON_STRING) {
+        ovs_fatal(0, "\"%s\": %s", s, json->u.string);
+    }
+    return json;
+}
+
+static void
+print_and_free_json(struct json *json)
+{
+    char *string = json_to_string(json, JSSF_SORT);
+    json_destroy(json);
+    puts(string);
+    free(string);
+}
+\f
+/* Command implementations. */
+
+static void
+handle_rpc(struct jsonrpc *rpc, struct jsonrpc_msg *msg, bool *done)
+{
+    struct jsonrpc_msg *reply = NULL;
+    if (msg->type == JSONRPC_REQUEST) {
+        if (!strcmp(msg->method, "echo")) {
+            reply = jsonrpc_create_reply(json_clone(msg->params), msg->id);
+        } else {
+            struct json *error = json_object_create();
+            json_object_put_string(error, "error", "unknown method");
+            reply = jsonrpc_create_error(error, msg->id);
+            ovs_error(0, "unknown request %s", msg->method);
+        }
+
+    } else if (msg->type == JSONRPC_NOTIFY) {
+        if (!strcmp(msg->method, "shutdown")) {
+            *done = true;
+        } else {
+            jsonrpc_error(rpc, ENOTTY);
+            ovs_error(0, "unknown notification %s", msg->method);
+        }
+    } else {
+        jsonrpc_error(rpc, EPROTO);
+        ovs_error(0, "unsolicited JSON-RPC reply or error");
+    }
+
+    if (reply) {
+        jsonrpc_send(rpc, reply);
+    }
+}
+
+static void
+do_listen(int argc UNUSED, char *argv[])
+{
+    struct pstream *pstream;
+    struct jsonrpc **rpcs;
+    size_t n_rpcs, allocated_rpcs;
+    bool done;
+    int error;
+
+    die_if_already_running();
+
+    error = pstream_open(argv[1], &pstream);
+    if (error) {
+        ovs_fatal(error, "could not listen on \"%s\"", argv[1]);
+    }
+
+    daemonize();
+
+    rpcs = NULL;
+    n_rpcs = allocated_rpcs = 0;
+    done = false;
+    for (;;) {
+        struct stream *stream;
+        size_t i;
+
+        /* Accept new connections. */
+        error = pstream_accept(pstream, &stream);
+        if (!error) {
+            if (n_rpcs >= allocated_rpcs) {
+                rpcs = x2nrealloc(rpcs, &allocated_rpcs, sizeof *rpcs);
+            }
+            rpcs[n_rpcs++] = jsonrpc_open(stream);
+        } else if (error != EAGAIN) {
+            ovs_fatal(error, "pstream_accept failed");
+        }
+
+        /* Service existing connections. */
+        for (i = 0; i < n_rpcs; ) {
+            struct jsonrpc *rpc = rpcs[i];
+            struct jsonrpc_msg *msg;
+
+            jsonrpc_run(rpc);
+            if (!jsonrpc_get_backlog(rpc)) {
+                error = jsonrpc_recv(rpc, &msg);
+                if (!error) {
+                    handle_rpc(rpc, msg, &done);
+                    jsonrpc_msg_destroy(msg);
+                }
+            }
+
+            error = jsonrpc_get_status(rpc);
+            if (error) {
+                jsonrpc_close(rpc);
+                ovs_error(error, "connection closed");
+                memmove(&rpcs[i], &rpcs[i + 1],
+                        (n_rpcs - i - 1) * sizeof *rpcs);
+                n_rpcs--;
+            } else {
+                i++;
+            }
+        }
+
+        /* Wait for something to do. */
+        if (done && !n_rpcs) {
+            break;
+        }
+        pstream_wait(pstream);
+        for (i = 0; i < n_rpcs; i++) {
+            struct jsonrpc *rpc = rpcs[i];
+
+            jsonrpc_wait(rpc);
+            if (!jsonrpc_get_backlog(rpc)) {
+                jsonrpc_recv_wait(rpc);
+            }
+        }
+        poll_block();
+    }
+}
+
+
+static void
+do_request(int argc UNUSED, char *argv[])
+{
+    struct jsonrpc_msg *msg;
+    struct jsonrpc *rpc;
+    struct json *params;
+    struct stream *stream;
+    const char *method;
+    char *string;
+    int error;
+
+    method = argv[2];
+    params = parse_json(argv[3]);
+    msg = jsonrpc_create_request(method, params, NULL);
+    string = jsonrpc_msg_is_valid(msg);
+    if (string) {
+        ovs_fatal(0, "not a valid JSON-RPC request: %s", string);
+    }
+
+    error = stream_open_block(argv[1], &stream);
+    if (error) {
+        ovs_fatal(error, "could not open \"%s\"", argv[1]);
+    }
+    rpc = jsonrpc_open(stream);
+
+    error = jsonrpc_send(rpc, msg);
+    if (error) {
+        ovs_fatal(error, "could not send request");
+    }
+
+    error = jsonrpc_recv_block(rpc, &msg);
+    if (error) {
+        ovs_fatal(error, "error waiting for reply");
+    }
+    print_and_free_json(jsonrpc_msg_to_json(msg));
+
+    jsonrpc_close(rpc);
+}
+
+static void
+do_notify(int argc UNUSED, char *argv[])
+{
+    struct jsonrpc_msg *msg;
+    struct jsonrpc *rpc;
+    struct json *params;
+    struct stream *stream;
+    const char *method;
+    char *string;
+    int error;
+
+    method = argv[2];
+    params = parse_json(argv[3]);
+    msg = jsonrpc_create_notify(method, params);
+    string = jsonrpc_msg_is_valid(msg);
+    if (string) {
+        ovs_fatal(0, "not a JSON RPC-valid notification: %s", string);
+    }
+
+    error = stream_open_block(argv[1], &stream);
+    if (error) {
+        ovs_fatal(error, "could not open \"%s\"", argv[1]);
+    }
+    rpc = jsonrpc_open(stream);
+
+    error = jsonrpc_send_block(rpc, msg);
+    if (error) {
+        ovs_fatal(error, "could not send request");
+    }
+    jsonrpc_close(rpc);
+}
+
+static void
+do_help(int argc UNUSED, char *argv[] UNUSED)
+{
+    usage();
+}
+
+static struct command all_commands[] = {
+    { "listen", 1, 1, do_listen },
+    { "request", 3, 3, do_request },
+    { "notify", 3, 3, do_notify },
+    { "help", 0, INT_MAX, do_help },
+    { NULL, 0, 0, NULL },
+};
diff --git a/tests/test-lockfile.c b/tests/test-lockfile.c
new file mode 100644 (file)
index 0000000..31e13a7
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "lockfile.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "process.h"
+#include "timeval.h"
+#include "util.h"
+
+#undef NDEBUG
+#include <assert.h>
+
+struct test {
+    const char *name;
+    void (*function)(void);
+};
+
+static const struct test tests[];
+
+static void
+run_lock_and_unlock(void)
+{
+    struct lockfile *lockfile;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+    lockfile_unlock(lockfile);
+}
+
+static void
+run_lock_and_unlock_twice(void)
+{
+    struct lockfile *lockfile;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+    lockfile_unlock(lockfile);
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+    lockfile_unlock(lockfile);
+}
+
+static void
+run_lock_blocks_same_process(void)
+{
+    struct lockfile *lockfile;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+    assert(lockfile_lock("file", 0, &lockfile) == EDEADLK);
+    lockfile_unlock(lockfile);
+}
+
+static void
+run_lock_blocks_same_process_twice(void)
+{
+    struct lockfile *lockfile;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+    assert(lockfile_lock("file", 0, &lockfile) == EDEADLK);
+    assert(lockfile_lock("file", 0, &lockfile) == EDEADLK);
+    lockfile_unlock(lockfile);
+}
+
+static enum { PARENT, CHILD }
+do_fork(void)
+{
+    switch (fork()) {
+    case 0:
+        time_postfork();
+        lockfile_postfork();
+        return CHILD;
+
+    default:
+        return PARENT;
+
+    case -1:
+        /* Error. */
+        ovs_fatal(errno, "fork failed");
+    }
+}
+
+static void
+run_lock_blocks_other_process(void)
+{
+    struct lockfile *lockfile;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+    if (do_fork() == CHILD) {
+        assert(lockfile_lock("file", 0, &lockfile) == EAGAIN);
+        exit(11);
+    }
+}
+
+static void
+run_lock_twice_blocks_other_process(void)
+{
+    struct lockfile *lockfile, *dummy;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+    assert(lockfile_lock("file", 0, &dummy) == EDEADLK);
+    if (do_fork() == CHILD) {
+        assert(lockfile_lock("file", 0, &dummy) == EAGAIN);
+        exit(11);
+    }
+}
+
+static void
+run_lock_and_unlock_allows_other_process(void)
+{
+    struct lockfile *lockfile;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+    lockfile_unlock(lockfile);
+
+    if (do_fork() == CHILD) {
+        assert(lockfile_lock("file", 0, &lockfile) == 0);
+        exit(11);
+    }
+}
+
+static void
+run_lock_timeout_gets_the_lock(void)
+{
+    struct lockfile *lockfile;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+
+    if (do_fork() == CHILD) {
+        assert(lockfile_lock("file", TIME_UPDATE_INTERVAL * 3,
+                             &lockfile) == 0);
+        exit(11);
+    } else {
+        long long int now = time_msec();
+        while (time_msec() < now + TIME_UPDATE_INTERVAL) {
+            pause();
+        }
+        lockfile_unlock(lockfile);
+    }
+}
+
+static void
+run_lock_timeout_runs_out(void)
+{
+    struct lockfile *lockfile;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+
+    if (do_fork() == CHILD) {
+        assert(lockfile_lock("file", TIME_UPDATE_INTERVAL,
+                             &lockfile) == ETIMEDOUT);
+        exit(11);
+    } else {
+        long long int now = time_msec();
+        while (time_msec() < now + TIME_UPDATE_INTERVAL * 3) {
+            pause();
+        }
+        lockfile_unlock(lockfile);
+    }
+}
+
+static void
+run_lock_multiple(void)
+{
+    struct lockfile *a, *b, *c, *dummy;
+
+    assert(lockfile_lock("a", 0, &a) == 0);
+    assert(lockfile_lock("b", 0, &b) == 0);
+    assert(lockfile_lock("c", 0, &c) == 0);
+
+    lockfile_unlock(a);
+    assert(lockfile_lock("a", 0, &a) == 0);
+    assert(lockfile_lock("a", 0, &dummy) == EDEADLK);
+    lockfile_unlock(a);
+
+    lockfile_unlock(b);
+    assert(lockfile_lock("a", 0, &a) == 0);
+
+    lockfile_unlock(c);
+    lockfile_unlock(a);
+}
+
+static void
+run_help(void)
+{
+    size_t i;
+
+    printf("usage: %s TESTNAME\n"
+           "where TESTNAME is one of the following:\n",
+           program_name);
+    for (i = 0; tests[i].name; i++) {
+        fprintf(stderr, "\t%s\n", tests[i].name);
+    }
+}
+
+static const struct test tests[] = {
+#define TEST(NAME) { #NAME, run_##NAME }
+    TEST(lock_and_unlock),
+    TEST(lock_and_unlock_twice),
+    TEST(lock_blocks_same_process),
+    TEST(lock_blocks_same_process_twice),
+    TEST(lock_blocks_other_process),
+    TEST(lock_twice_blocks_other_process),
+    TEST(lock_and_unlock_allows_other_process),
+    TEST(lock_timeout_gets_the_lock),
+    TEST(lock_timeout_runs_out),
+    TEST(lock_multiple),
+    TEST(help),
+    { 0, 0 }
+#undef TEST
+};
+
+int
+main(int argc, char *argv[])
+{
+    size_t i;
+
+    set_program_name(argv[0]);
+    time_init();
+
+    if (argc != 2) {
+        ovs_fatal(0, "exactly one argument required; use \"%s help\" for help",
+                  program_name);
+        return 1;
+    }
+
+    for (i = 0; tests[i].name; i++) {
+        if (!strcmp(argv[1], tests[i].name)) {
+            int n_children;
+            int status;
+
+            (tests[i].function)();
+
+            n_children = 0;
+            while (wait(&status) > 0) {
+                if (WIFEXITED(status) && WEXITSTATUS(status) == 11) {
+                    n_children++;
+                } else {
+                    ovs_fatal(0, "child exited in unexpected way: %s",
+                              process_status_msg(status));
+                }
+            }
+            if (errno != ECHILD) {
+                ovs_fatal(errno, "wait");
+            }
+
+            printf("%s: success (%d child%s)\n",
+                   tests[i].name, n_children, n_children != 1 ? "ren" : "");
+            exit(0);
+        }
+    }
+    ovs_fatal(0, "unknown test \"%s\"; use \"%s help\" for help",
+              argv[1], program_name);
+}
+
diff --git a/tests/test-ovsdb.c b/tests/test-ovsdb.c
new file mode 100644 (file)
index 0000000..f3305ed
--- /dev/null
@@ -0,0 +1,1464 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <assert.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "command-line.h"
+#include "json.h"
+#include "jsonrpc.h"
+#include "ovsdb-data.h"
+#include "ovsdb-error.h"
+#include "ovsdb-idl.h"
+#include "ovsdb-types.h"
+#include "ovsdb/column.h"
+#include "ovsdb/condition.h"
+#include "ovsdb/file.h"
+#include "ovsdb/log.h"
+#include "ovsdb/ovsdb.h"
+#include "ovsdb/query.h"
+#include "ovsdb/row.h"
+#include "ovsdb/table.h"
+#include "ovsdb/transaction.h"
+#include "ovsdb/trigger.h"
+#include "poll-loop.h"
+#include "stream.h"
+#include "svec.h"
+#include "tests/idltest.h"
+#include "timeval.h"
+#include "util.h"
+#include "vlog.h"
+
+static struct command all_commands[];
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+    set_program_name(argv[0]);
+    time_init();
+    vlog_init();
+    parse_options(argc, argv);
+    run_command(argc - optind, argv + optind, all_commands);
+    return 0;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+    static struct option long_options[] = {
+        {"timeout", required_argument, 0, 't'},
+        {"verbose", optional_argument, 0, 'v'},
+        {"help", no_argument, 0, 'h'},
+        {0, 0, 0, 0},
+    };
+    char *short_options = long_options_to_short_options(long_options);
+
+    for (;;) {
+        unsigned long int timeout;
+        int c;
+
+        c = getopt_long(argc, argv, short_options, long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case 't':
+            timeout = strtoul(optarg, NULL, 10);
+            if (timeout <= 0) {
+                ovs_fatal(0, "value %s on -t or --timeout is not at least 1",
+                          optarg);
+            } else {
+                time_alarm(timeout);
+            }
+            break;
+
+        case 'h':
+            usage();
+
+        case 'v':
+            vlog_set_verbosity(optarg);
+            break;
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+}
+
+static void
+usage(void)
+{
+    printf("%s: Open vSwitch database test utility\n"
+           "usage: %s [OPTIONS] COMMAND [ARG...]\n\n"
+           "  log-io FILE FLAGS COMMAND...\n"
+           "    open FILE with FLAGS, run COMMANDs\n"
+           "  parse-atomic-type TYPE\n"
+           "    parse TYPE as OVSDB atomic type, and re-serialize\n"
+           "  parse-type JSON\n"
+           "    parse JSON as OVSDB type, and re-serialize\n"
+           "  parse-atoms TYPE ATOM...\n"
+           "    parse ATOMs as atoms of given TYPE, and re-serialize\n"
+           "  sort-atoms TYPE ATOM...\n"
+           "    print ATOMs in sorted order, and re-serialize\n"
+           "  parse-data TYPE DATUM...\n"
+           "    parse DATUMs as data of given TYPE, and re-serialize\n"
+           "  parse-column NAME OBJECT\n"
+           "    parse column NAME with info OBJECT, and re-serialize\n"
+           "  parse-table NAME OBJECT\n"
+           "    parse table NAME with info OBJECT\n"
+           "  parse-row TABLE ROW..., and re-serialize\n"
+           "    parse each ROW of defined TABLE\n"
+           "  compare-row TABLE ROW...\n"
+           "    mutually compare all of the ROWs, print those that are equal\n"
+           "  parse-conditions TABLE CONDITION...\n"
+           "    parse each CONDITION on TABLE, and re-serialize\n"
+           "  evaluate-conditions TABLE [CONDITION,...] [ROW,...]\n"
+           "    test CONDITIONS on TABLE against each ROW, print results\n"
+           "  query TABLE [ROW,...] [CONDITION,...]\n"
+           "    add each ROW to TABLE, then query and print the rows that\n"
+           "    satisfy each CONDITION.\n"
+           "  query-distinct TABLE [ROW,...] [CONDITION,...] COLUMNS\n"
+           "    add each ROW to TABLE, then query and print the rows that\n"
+           "    satisfy each CONDITION and have distinct COLUMNS.\n"
+           "  transact COMMAND\n"
+           "    execute each specified transactional COMMAND:\n"
+           "      commit\n"
+           "      abort\n"
+           "      insert UUID I J\n"
+           "      delete UUID\n"
+           "      modify UUID I J\n"
+           "      print\n"
+           "  execute SCHEMA TRANSACTION...\n"
+           "    executes each TRANSACTION on an initially empty database\n"
+           "    the specified SCHEMA\n"
+           "  trigger SCHEMA TRANSACTION...\n"
+           "    executes each TRANSACTION on an initially empty database\n"
+           "    the specified SCHEMA.   A TRANSACTION of the form\n"
+           "    [\"advance\", NUMBER] advances NUMBER milliseconds in\n"
+           "    simulated time, for causing triggers to time out.\n"
+           "  idl SERVER [TRANSACTION...]\n"
+           "    connect to SERVER and dump the contents of the database\n"
+           "    as seen initially by the IDL implementation and after\n"
+           "    executing each TRANSACTION.  (Each TRANSACTION must modify\n"
+           "    the database or this command will hang.)\n",
+           program_name, program_name);
+    vlog_usage();
+    printf("\nOther options:\n"
+           "  -t, --timeout=SECS          give up after SECS seconds\n"
+           "  -h, --help                  display this help message\n");
+    exit(EXIT_SUCCESS);
+}
+\f
+/* Command helper functions. */
+
+static struct json *
+parse_json(const char *s)
+{
+    struct json *json = json_from_string(s);
+    if (json->type == JSON_STRING) {
+        ovs_fatal(0, "\"%s\": %s", s, json->u.string);
+    }
+    return json;
+}
+
+static struct json *
+unbox_json(struct json *json)
+{
+    if (json->type == JSON_ARRAY && json->u.array.n == 1) {
+        struct json *inner = json->u.array.elems[0];
+        json->u.array.elems[0] = NULL;
+        json_destroy(json);
+        return inner;
+    } else {
+        return json;
+    }
+}
+
+static void
+print_and_free_json(struct json *json)
+{
+    char *string = json_to_string(json, JSSF_SORT);
+    json_destroy(json);
+    puts(string);
+    free(string);
+}
+
+static void
+check_ovsdb_error(struct ovsdb_error *error)
+{
+    if (error) {
+        ovs_fatal(0, "%s", ovsdb_error_to_string(error));
+    }
+}
+\f
+/* Command implementations. */
+
+static void
+do_log_io(int argc, char *argv[])
+{
+    const char *name = argv[1];
+    char *mode = argv[2];
+
+    struct ovsdb_error *error;
+    struct ovsdb_log *log;
+    char *save_ptr = NULL;
+    const char *token;
+    int flags;
+    int i;
+
+    for (flags = 0, token = strtok_r(mode, " |", &save_ptr); token != NULL;
+         token = strtok_r(NULL, " |", &save_ptr))
+    {
+        if (!strcmp(token, "O_RDONLY")) {
+            flags |= O_RDONLY;
+        } else if (!strcmp(token, "O_RDWR")) {
+            flags |= O_RDWR;
+        } else if (!strcmp(token, "O_TRUNC")) {
+            flags |= O_TRUNC;
+        } else if (!strcmp(token, "O_CREAT")) {
+            flags |= O_CREAT;
+        } else if (!strcmp(token, "O_EXCL")) {
+            flags |= O_EXCL;
+        } else if (!strcmp(token, "O_TRUNC")) {
+            flags |= O_TRUNC;
+        }
+    }
+
+    check_ovsdb_error(ovsdb_log_open(name, flags, &log));
+    printf("%s: open successful\n", name);
+
+    for (i = 3; i < argc; i++) {
+        const char *command = argv[i];
+        if (!strcmp(command, "read")) {
+            struct json *json;
+
+            error = ovsdb_log_read(log, &json);
+            if (!error) {
+                printf("%s: read: ", name);
+                if (json) {
+                    print_and_free_json(json);
+                } else {
+                    printf("end of log\n");
+                }
+                continue;
+            }
+        } else if (!strncmp(command, "write:", 6)) {
+            struct json *json = parse_json(command + 6);
+            error = ovsdb_log_write(log, json);
+            json_destroy(json);
+        } else if (!strcmp(command, "commit")) {
+            error = ovsdb_log_commit(log);
+        } else {
+            ovs_fatal(0, "unknown log-io command \"%s\"", command);
+        }
+        if (error) {
+            char *s = ovsdb_error_to_string(error);
+            printf("%s: %s failed: %s\n", name, command, s);
+            free(s);
+        } else {
+            printf("%s: %s successful\n", name, command);
+        }
+    }
+
+    ovsdb_log_close(log);
+}
+
+static void
+do_parse_atomic_type(int argc UNUSED, char *argv[])
+{
+    enum ovsdb_atomic_type type;
+    struct json *json;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_atomic_type_from_json(&type, json));
+    json_destroy(json);
+    print_and_free_json(ovsdb_atomic_type_to_json(type));
+}
+
+static void
+do_parse_type(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_type type;
+    struct json *json;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_type_from_json(&type, json));
+    json_destroy(json);
+    print_and_free_json(ovsdb_type_to_json(&type));
+}
+
+static void
+do_parse_atoms(int argc, char *argv[])
+{
+    enum ovsdb_atomic_type type;
+    struct json *json;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_atomic_type_from_json(&type, json));
+    json_destroy(json);
+
+    for (i = 2; i < argc; i++) {
+        union ovsdb_atom atom;
+
+        json = unbox_json(parse_json(argv[i]));
+        check_ovsdb_error(ovsdb_atom_from_json(&atom, type, json, NULL));
+        json_destroy(json);
+
+        print_and_free_json(ovsdb_atom_to_json(&atom, type));
+
+        ovsdb_atom_destroy(&atom, type);
+    }
+}
+
+static void
+do_parse_data(int argc, char *argv[])
+{
+    struct ovsdb_type type;
+    struct json *json;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_type_from_json(&type, json));
+    json_destroy(json);
+
+    for (i = 2; i < argc; i++) {
+        struct ovsdb_datum datum;
+
+        json = unbox_json(parse_json(argv[i]));
+        check_ovsdb_error(ovsdb_datum_from_json(&datum, &type, json, NULL));
+        json_destroy(json);
+
+        print_and_free_json(ovsdb_datum_to_json(&datum, &type));
+
+        ovsdb_datum_destroy(&datum, &type);
+    }
+}
+
+static enum ovsdb_atomic_type compare_atoms_atomic_type;
+
+static int
+compare_atoms(const void *a_, const void *b_)
+{
+    const union ovsdb_atom *a = a_;
+    const union ovsdb_atom *b = b_;
+
+    return ovsdb_atom_compare_3way(a, b, compare_atoms_atomic_type);
+}
+
+static void
+do_sort_atoms(int argc UNUSED, char *argv[])
+{
+    enum ovsdb_atomic_type type;
+    union ovsdb_atom *atoms;
+    struct json *json, **json_atoms;
+    size_t n_atoms;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_atomic_type_from_json(&type, json));
+    json_destroy(json);
+
+    json = unbox_json(parse_json(argv[2]));
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "second argument must be array");
+    }
+
+    /* Convert JSON atoms to internal representation. */
+    n_atoms = json->u.array.n;
+    atoms = xmalloc(n_atoms * sizeof *atoms);
+    for (i = 0; i < n_atoms; i++) {
+        check_ovsdb_error(ovsdb_atom_from_json(&atoms[i], type,
+                                               json->u.array.elems[i], NULL));
+    }
+    json_destroy(json);
+
+    /* Sort atoms. */
+    compare_atoms_atomic_type = type;
+    qsort(atoms, n_atoms, sizeof *atoms, compare_atoms);
+
+    /* Convert internal representation back to JSON. */
+    json_atoms = xmalloc(n_atoms * sizeof *json_atoms);
+    for (i = 0; i < n_atoms; i++) {
+        json_atoms[i] = ovsdb_atom_to_json(&atoms[i], type);
+        ovsdb_atom_destroy(&atoms[i], type);
+    }
+    print_and_free_json(json_array_create(json_atoms, n_atoms));
+}
+
+static void
+do_parse_column(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_column *column;
+    struct json *json;
+
+    json = parse_json(argv[2]);
+    check_ovsdb_error(ovsdb_column_from_json(json, argv[1], &column));
+    json_destroy(json);
+    print_and_free_json(ovsdb_column_to_json(column));
+    ovsdb_column_destroy(column);
+}
+
+static void
+do_parse_table(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_table_schema *ts;
+    struct json *json;
+
+    json = parse_json(argv[2]);
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, argv[1], &ts));
+    json_destroy(json);
+    print_and_free_json(ovsdb_table_schema_to_json(ts));
+    ovsdb_table_schema_destroy(ts);
+}
+
+static void
+do_parse_rows(int argc, char *argv[])
+{
+    struct ovsdb_column_set all_columns;
+    struct ovsdb_table_schema *ts;
+    struct ovsdb_table *table;
+    struct json *json;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    table = ovsdb_table_create(ts);
+    ovsdb_column_set_init(&all_columns);
+    ovsdb_column_set_add_all(&all_columns, table);
+
+    for (i = 2; i < argc; i++) {
+        struct ovsdb_column_set columns;
+        struct ovsdb_row *row;
+
+        ovsdb_column_set_init(&columns);
+        row = ovsdb_row_create(table);
+
+        json = unbox_json(parse_json(argv[i]));
+        check_ovsdb_error(ovsdb_row_from_json(row, json, NULL, &columns));
+        json_destroy(json);
+
+        print_and_free_json(ovsdb_row_to_json(row, &all_columns));
+
+        if (columns.n_columns) {
+            struct svec names;
+            size_t j;
+            char *s;
+
+            svec_init(&names);
+            for (j = 0; j < columns.n_columns; j++) {
+                svec_add(&names, columns.columns[j]->name);
+            }
+            svec_sort(&names);
+            s = svec_join(&names, ", ", "");
+            puts(s);
+            free(s);
+            svec_destroy(&names);
+        } else {
+            printf("<none>\n");
+        }
+
+        ovsdb_column_set_destroy(&columns);
+        ovsdb_row_destroy(row);
+    }
+
+    ovsdb_column_set_destroy(&all_columns);
+    ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+}
+
+static void
+do_compare_rows(int argc, char *argv[])
+{
+    struct ovsdb_column_set all_columns;
+    struct ovsdb_table_schema *ts;
+    struct ovsdb_table *table;
+    struct ovsdb_row **rows;
+    struct json *json;
+    char **names;
+    int n_rows;
+    int i, j;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    table = ovsdb_table_create(ts);
+    ovsdb_column_set_init(&all_columns);
+    ovsdb_column_set_add_all(&all_columns, table);
+
+    n_rows = argc - 2;
+    rows = xmalloc(sizeof *rows * n_rows);
+    names = xmalloc(sizeof *names * n_rows);
+    for (i = 0; i < n_rows; i++) {
+        rows[i] = ovsdb_row_create(table);
+
+        json = parse_json(argv[i + 2]);
+        if (json->type != JSON_ARRAY || json->u.array.n != 2
+            || json->u.array.elems[0]->type != JSON_STRING) {
+            ovs_fatal(0, "\"%s\" does not have expected form "
+                      "[\"name\", {data}]", argv[i]);
+        }
+        names[i] = xstrdup(json->u.array.elems[0]->u.string);
+        check_ovsdb_error(ovsdb_row_from_json(rows[i], json->u.array.elems[1],
+                                              NULL, NULL));
+        json_destroy(json);
+    }
+    for (i = 0; i < n_rows; i++) {
+        uint32_t i_hash = ovsdb_row_hash_columns(rows[i], &all_columns, 0);
+        for (j = i + 1; j < n_rows; j++) {
+            uint32_t j_hash = ovsdb_row_hash_columns(rows[j], &all_columns, 0);
+            if (ovsdb_row_equal_columns(rows[i], rows[j], &all_columns)) {
+                printf("%s == %s\n", names[i], names[j]);
+                if (i_hash != j_hash) {
+                    printf("but hash(%s) != hash(%s)\n", names[i], names[j]);
+                    abort();
+                }
+            } else if (i_hash == j_hash) {
+                printf("hash(%s) == hash(%s)\n", names[i], names[j]);
+            }
+        }
+    }
+    free(rows);
+    free(names);
+
+    ovsdb_column_set_destroy(&all_columns);
+    ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+}
+
+static void
+do_parse_conditions(int argc, char *argv[])
+{
+    struct ovsdb_table_schema *ts;
+    struct json *json;
+    int exit_code = 0;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    for (i = 2; i < argc; i++) {
+        struct ovsdb_condition cnd;
+        struct ovsdb_error *error;
+
+        json = parse_json(argv[i]);
+        error = ovsdb_condition_from_json(ts, json, NULL, &cnd);
+        if (!error) {
+            print_and_free_json(ovsdb_condition_to_json(&cnd));
+        } else {
+            char *s = ovsdb_error_to_string(error);
+            ovs_error(0, "%s", s);
+            free(s);
+            ovsdb_error_destroy(error);
+            exit_code = 1;
+        }
+        json_destroy(json);
+
+        ovsdb_condition_destroy(&cnd);
+    }
+    ovsdb_table_schema_destroy(ts);
+
+    exit(exit_code);
+}
+
+static void
+do_evaluate_conditions(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_table_schema *ts;
+    struct ovsdb_table *table;
+    struct ovsdb_condition *conditions;
+    size_t n_conditions;
+    struct ovsdb_row **rows;
+    size_t n_rows;
+    struct json *json;
+    size_t i, j;
+
+    /* Parse table schema, create table. */
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    table = ovsdb_table_create(ts);
+
+    /* Parse conditions. */
+    json = parse_json(argv[2]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "CONDITION argument is not JSON array");
+    }
+    n_conditions = json->u.array.n;
+    conditions = xmalloc(n_conditions * sizeof *conditions);
+    for (i = 0; i < n_conditions; i++) {
+        check_ovsdb_error(ovsdb_condition_from_json(ts, json->u.array.elems[i],
+                                                    NULL, &conditions[i]));
+    }
+    json_destroy(json);
+
+    /* Parse rows. */
+    json = parse_json(argv[3]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "ROW argument is not JSON array");
+    }
+    n_rows = json->u.array.n;
+    rows = xmalloc(n_rows * sizeof *rows);
+    for (i = 0; i < n_rows; i++) {
+        rows[i] = ovsdb_row_create(table);
+        check_ovsdb_error(ovsdb_row_from_json(rows[i], json->u.array.elems[i],
+                                              NULL, NULL));
+    }
+    json_destroy(json);
+
+    for (i = 0; i < n_conditions; i++) {
+        printf("condition %2d:", i);
+        for (j = 0; j < n_rows; j++) {
+            bool result = ovsdb_condition_evaluate(rows[j], &conditions[i]);
+            if (j % 5 == 0) {
+                putchar(' ');
+            }
+            putchar(result ? 'T' : '-');
+        }
+        printf("\n");
+    }
+
+    for (i = 0; i < n_conditions; i++) {
+        ovsdb_condition_destroy(&conditions[i]);
+    }
+    for (i = 0; i < n_rows; i++) {
+        ovsdb_row_destroy(rows[i]);
+    }
+    ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+}
+
+struct do_query_cbdata {
+    struct uuid *row_uuids;
+    int *counts;
+    size_t n_rows;
+};
+
+static bool
+do_query_cb(const struct ovsdb_row *row, void *cbdata_)
+{
+    struct do_query_cbdata *cbdata = cbdata_;
+    size_t i;
+
+    for (i = 0; i < cbdata->n_rows; i++) {
+        if (uuid_equals(ovsdb_row_get_uuid(row), &cbdata->row_uuids[i])) {
+            cbdata->counts[i]++;
+        }
+    }
+
+    return true;
+}
+
+static void
+do_query(int argc UNUSED, char *argv[])
+{
+    struct do_query_cbdata cbdata;
+    struct ovsdb_table_schema *ts;
+    struct ovsdb_table *table;
+    struct json *json;
+    int exit_code = 0;
+    size_t i;
+
+    /* Parse table schema, create table. */
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    table = ovsdb_table_create(ts);
+
+    /* Parse rows, add to table. */
+    json = parse_json(argv[2]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "ROW argument is not JSON array");
+    }
+    cbdata.n_rows = json->u.array.n;
+    cbdata.row_uuids = xmalloc(cbdata.n_rows * sizeof *cbdata.row_uuids);
+    cbdata.counts = xmalloc(cbdata.n_rows * sizeof *cbdata.counts);
+    for (i = 0; i < cbdata.n_rows; i++) {
+        struct ovsdb_row *row = ovsdb_row_create(table);
+        uuid_generate(ovsdb_row_get_uuid_rw(row));
+        check_ovsdb_error(ovsdb_row_from_json(row, json->u.array.elems[i],
+                                              NULL, NULL));
+        if (ovsdb_table_get_row(table, ovsdb_row_get_uuid(row))) {
+            ovs_fatal(0, "duplicate UUID "UUID_FMT" in table",
+                      UUID_ARGS(ovsdb_row_get_uuid(row)));
+        }
+        cbdata.row_uuids[i] = *ovsdb_row_get_uuid(row);
+        ovsdb_table_put_row(table, row);
+    }
+    json_destroy(json);
+
+    /* Parse conditions and execute queries. */
+    json = parse_json(argv[3]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "CONDITION argument is not JSON array");
+    }
+    for (i = 0; i < json->u.array.n; i++) {
+        struct ovsdb_condition cnd;
+        size_t j;
+
+        check_ovsdb_error(ovsdb_condition_from_json(ts, json->u.array.elems[i],
+                                                    NULL, &cnd));
+
+        memset(cbdata.counts, 0, cbdata.n_rows * sizeof *cbdata.counts);
+        ovsdb_query(table, &cnd, do_query_cb, &cbdata);
+
+        printf("query %2d:", i);
+        for (j = 0; j < cbdata.n_rows; j++) {
+            if (j % 5 == 0) {
+                putchar(' ');
+            }
+            if (cbdata.counts[j]) {
+                printf("%d", cbdata.counts[j]);
+                if (cbdata.counts[j] > 1) {
+                    /* Dup! */
+                    exit_code = 1;
+                }
+            } else {
+                putchar('-');
+            }
+        }
+        putchar('\n');
+
+        ovsdb_condition_destroy(&cnd);
+    }
+    json_destroy(json);
+
+    ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+
+    exit(exit_code);
+}
+
+struct do_query_distinct_class {
+    struct ovsdb_row *example;
+    int count;
+};
+
+struct do_query_distinct_row {
+    struct uuid uuid;
+    struct do_query_distinct_class *class;
+};
+
+static void
+do_query_distinct(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_column_set columns;
+    struct ovsdb_table_schema *ts;
+    struct ovsdb_table *table;
+    struct do_query_distinct_row *rows;
+    size_t n_rows;
+    struct do_query_distinct_class *classes;
+    size_t n_classes;
+    struct json *json;
+    int exit_code = 0;
+    size_t i, j, k;
+
+    /* Parse table schema, create table. */
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    table = ovsdb_table_create(ts);
+
+    /* Parse column set. */
+    json = parse_json(argv[4]);
+    ovsdb_column_set_from_json(json, table, &columns);
+    json_destroy(json);
+
+    /* Parse rows, add to table. */
+    json = parse_json(argv[2]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "ROW argument is not JSON array");
+    }
+    n_rows = json->u.array.n;
+    rows = xmalloc(n_rows * sizeof *rows);
+    classes = xmalloc(n_rows * sizeof *classes);
+    n_classes = 0;
+    for (i = 0; i < n_rows; i++) {
+        struct ovsdb_row *row;
+        size_t j;
+
+        /* Parse row. */
+        row = ovsdb_row_create(table);
+        uuid_generate(ovsdb_row_get_uuid_rw(row));
+        check_ovsdb_error(ovsdb_row_from_json(row, json->u.array.elems[i],
+                                              NULL, NULL));
+
+        /* Initialize row and find equivalence class. */
+        rows[i].uuid = *ovsdb_row_get_uuid(row);
+        rows[i].class = NULL;
+        for (j = 0; j < n_classes; j++) {
+            if (ovsdb_row_equal_columns(row, classes[j].example, &columns)) {
+                rows[i].class = &classes[j];
+                break;
+            }
+        }
+        if (!rows[i].class) {
+            rows[i].class = &classes[n_classes];
+            classes[n_classes].example = ovsdb_row_clone(row);
+            n_classes++;
+        }
+
+        /* Add row to table. */
+        if (ovsdb_table_get_row(table, ovsdb_row_get_uuid(row))) {
+            ovs_fatal(0, "duplicate UUID "UUID_FMT" in table",
+                      UUID_ARGS(ovsdb_row_get_uuid(row)));
+        }
+        ovsdb_table_put_row(table, row);
+
+    }
+    json_destroy(json);
+
+    /* Parse conditions and execute queries. */
+    json = parse_json(argv[3]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "CONDITION argument is not JSON array");
+    }
+    for (i = 0; i < json->u.array.n; i++) {
+        struct ovsdb_row_set results;
+        struct ovsdb_condition cnd;
+
+        check_ovsdb_error(ovsdb_condition_from_json(ts, json->u.array.elems[i],
+                                                    NULL, &cnd));
+
+        for (j = 0; j < n_classes; j++) {
+            classes[j].count = 0;
+        }
+        ovsdb_row_set_init(&results);
+        ovsdb_query_distinct(table, &cnd, &columns, &results);
+        for (j = 0; j < results.n_rows; j++) {
+            for (k = 0; k < n_rows; k++) {
+                if (uuid_equals(ovsdb_row_get_uuid(results.rows[j]),
+                                &rows[k].uuid)) {
+                    rows[k].class->count++;
+                }
+            }
+        }
+        ovsdb_row_set_destroy(&results);
+
+        printf("query %2d:", i);
+        for (j = 0; j < n_rows; j++) {
+            int count = rows[j].class->count;
+
+            if (j % 5 == 0) {
+                putchar(' ');
+            }
+            if (count > 1) {
+                /* Dup! */
+                printf("%d", count);
+                exit_code = 1;
+            } else if (count == 1) {
+                putchar("abcdefghijklmnopqrstuvwxyz"[rows[j].class - classes]);
+            } else {
+                putchar('-');
+            }
+        }
+        putchar('\n');
+
+        ovsdb_condition_destroy(&cnd);
+    }
+    json_destroy(json);
+
+    ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+
+    exit(exit_code);
+}
+
+static void
+do_execute(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_schema *schema;
+    struct json *json;
+    struct ovsdb *db;
+    int i;
+
+    /* Create database. */
+    json = parse_json(argv[1]);
+    check_ovsdb_error(ovsdb_schema_from_json(json, &schema));
+    json_destroy(json);
+    db = ovsdb_create(schema);
+
+    for (i = 2; i < argc; i++) {
+        struct json *params, *result;
+        char *s;
+
+        params = parse_json(argv[i]);
+        result = ovsdb_execute(db, params, 0, NULL);
+        s = json_to_string(result, JSSF_SORT);
+        printf("%s\n", s);
+        json_destroy(params);
+        json_destroy(result);
+    }
+
+    ovsdb_destroy(db);
+}
+
+struct test_trigger {
+    struct ovsdb_trigger trigger;
+    int number;
+};
+
+static void
+do_trigger_dump(struct test_trigger *t, long long int now, const char *title)
+{
+    struct json *result;
+    char *s;
+
+    result = ovsdb_trigger_steal_result(&t->trigger);
+    s = json_to_string(result, JSSF_SORT);
+    printf("t=%lld: trigger %d (%s): %s\n", now, t->number, title, s);
+    json_destroy(result);
+    ovsdb_trigger_destroy(&t->trigger);
+    free(t);
+}
+
+static void
+do_trigger(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_schema *schema;
+    struct list completions;
+    struct json *json;
+    struct ovsdb *db;
+    long long int now;
+    int number;
+    int i;
+
+    /* Create database. */
+    json = parse_json(argv[1]);
+    check_ovsdb_error(ovsdb_schema_from_json(json, &schema));
+    json_destroy(json);
+    db = ovsdb_create(schema);
+
+    list_init(&completions);
+    now = 0;
+    number = 0;
+    for (i = 2; i < argc; i++) {
+        struct json *params = parse_json(argv[i]);
+        if (params->type == JSON_ARRAY
+            && json_array(params)->n == 2
+            && json_array(params)->elems[0]->type == JSON_STRING
+            && !strcmp(json_string(json_array(params)->elems[0]), "advance")
+            && json_array(params)->elems[1]->type == JSON_INTEGER) {
+            now += json_integer(json_array(params)->elems[1]);
+            json_destroy(params);
+        } else {
+            struct test_trigger *t = xmalloc(sizeof *t);
+            ovsdb_trigger_init(db, &t->trigger, params, &completions, now);
+            t->number = number++;
+            if (ovsdb_trigger_is_complete(&t->trigger)) {
+                do_trigger_dump(t, now, "immediate");
+            } else {
+                printf("t=%lld: new trigger %d\n", now, t->number);
+            }
+        }
+
+        ovsdb_trigger_run(db, now);
+        while (!list_is_empty(&completions)) {
+            do_trigger_dump(CONTAINER_OF(list_pop_front(&completions),
+                                         struct test_trigger, trigger.node),
+                            now, "delayed");
+        }
+
+        ovsdb_trigger_wait(db, now);
+        poll_immediate_wake();
+        poll_block();
+    }
+
+    ovsdb_destroy(db);
+}
+
+static void
+do_help(int argc UNUSED, char *argv[] UNUSED)
+{
+    usage();
+}
+\f
+/* "transact" command. */
+
+static struct ovsdb *do_transact_db;
+static struct ovsdb_txn *do_transact_txn;
+static struct ovsdb_table *do_transact_table;
+
+static void
+do_transact_commit(int argc UNUSED, char *argv[] UNUSED)
+{
+    ovsdb_txn_commit(do_transact_txn, false);
+    do_transact_txn = NULL;
+}
+
+static void
+do_transact_abort(int argc UNUSED, char *argv[] UNUSED)
+{
+    ovsdb_txn_abort(do_transact_txn);
+    do_transact_txn = NULL;
+}
+
+static void
+uuid_from_integer(int integer, struct uuid *uuid)
+{
+    uuid_zero(uuid);
+    uuid->parts[3] = integer;
+}
+
+static const struct ovsdb_row *
+do_transact_find_row(const char *uuid_string)
+{
+    const struct ovsdb_row *row;
+    struct uuid uuid;
+
+    uuid_from_integer(atoi(uuid_string), &uuid);
+    row = ovsdb_table_get_row(do_transact_table, &uuid);
+    if (!row) {
+        ovs_fatal(0, "table does not contain row with UUID "UUID_FMT,
+                  UUID_ARGS(&uuid));
+    }
+    return row;
+}
+
+static void
+do_transact_set_integer(struct ovsdb_row *row, const char *column_name,
+                        int integer)
+{
+    if (integer != -1) {
+        const struct ovsdb_column *column;
+
+        column = ovsdb_table_schema_get_column(do_transact_table->schema,
+                                               column_name);
+        row->fields[column->index].keys[0].integer = integer;
+    }
+}
+
+static int
+do_transact_get_integer(const struct ovsdb_row *row, const char *column_name)
+{
+    const struct ovsdb_column *column;
+
+    column = ovsdb_table_schema_get_column(do_transact_table->schema,
+                                           column_name);
+    return row->fields[column->index].keys[0].integer;
+}
+
+static void
+do_transact_set_i_j(struct ovsdb_row *row,
+                    const char *i_string, const char *j_string)
+{
+    do_transact_set_integer(row, "i", atoi(i_string));
+    do_transact_set_integer(row, "j", atoi(j_string));
+}
+
+static void
+do_transact_insert(int argc UNUSED, char *argv[] UNUSED)
+{
+    struct ovsdb_row *row;
+    struct uuid *uuid;
+
+    row = ovsdb_row_create(do_transact_table);
+
+    /* Set UUID. */
+    uuid = ovsdb_row_get_uuid_rw(row);
+    uuid_from_integer(atoi(argv[1]), uuid);
+    if (ovsdb_table_get_row(do_transact_table, uuid)) {
+        ovs_fatal(0, "table already contains row with UUID "UUID_FMT,
+                  UUID_ARGS(uuid));
+    }
+
+    do_transact_set_i_j(row, argv[2], argv[3]);
+
+    /* Insert row. */
+    ovsdb_txn_row_insert(do_transact_txn, row);
+}
+
+static void
+do_transact_delete(int argc UNUSED, char *argv[] UNUSED)
+{
+    const struct ovsdb_row *row = do_transact_find_row(argv[1]);
+    ovsdb_txn_row_delete(do_transact_txn, row);
+}
+
+static void
+do_transact_modify(int argc UNUSED, char *argv[] UNUSED)
+{
+    const struct ovsdb_row *row_ro;
+    struct ovsdb_row *row_rw;
+
+    row_ro = do_transact_find_row(argv[1]);
+    row_rw = ovsdb_txn_row_modify(do_transact_txn, row_ro);
+    do_transact_set_i_j(row_rw, argv[2], argv[3]);
+}
+
+static int
+compare_rows_by_uuid(const void *a_, const void *b_)
+{
+    struct ovsdb_row *const *ap = a_;
+    struct ovsdb_row *const *bp = b_;
+
+    return uuid_compare_3way(ovsdb_row_get_uuid(*ap), ovsdb_row_get_uuid(*bp));
+}
+
+static void
+do_transact_print(int argc UNUSED, char *argv[] UNUSED)
+{
+    const struct ovsdb_row **rows;
+    const struct ovsdb_row *row;
+    size_t n_rows;
+    size_t i;
+
+    n_rows = hmap_count(&do_transact_table->rows);
+    rows = xmalloc(n_rows * sizeof *rows);
+    i = 0;
+    HMAP_FOR_EACH (row, struct ovsdb_row, hmap_node,
+                   &do_transact_table->rows) {
+        rows[i++] = row;
+    }
+    assert(i == n_rows);
+
+    qsort(rows, n_rows, sizeof *rows, compare_rows_by_uuid);
+
+    for (i = 0; i < n_rows; i++) {
+        printf("\n%"PRId32": i=%d, j=%d",
+               ovsdb_row_get_uuid(rows[i])->parts[3],
+               do_transact_get_integer(rows[i], "i"),
+               do_transact_get_integer(rows[i], "j"));
+    }
+
+    free(rows);
+}
+
+static void
+do_transact(int argc, char *argv[])
+{
+    static const struct command do_transact_commands[] = {
+        { "commit", 0, 0, do_transact_commit },
+        { "abort", 0, 0, do_transact_abort },
+        { "insert", 2, 3, do_transact_insert },
+        { "delete", 1, 1, do_transact_delete },
+        { "modify", 2, 3, do_transact_modify },
+        { "print", 0, 0, do_transact_print },
+        { NULL, 0, 0, NULL },
+    };
+
+    struct ovsdb_schema *schema;
+    struct json *json;
+    int i;
+
+    /* Create table. */
+    json = parse_json("{\"name\": \"testdb\", "
+                      " \"tables\": "
+                      "  {\"mytable\": "
+                      "    {\"columns\": "
+                      "      {\"i\": {\"type\": \"integer\"}, "
+                      "       \"j\": {\"type\": \"integer\"}}}}}");
+    check_ovsdb_error(ovsdb_schema_from_json(json, &schema));
+    json_destroy(json);
+    do_transact_db = ovsdb_create(schema);
+    do_transact_table = ovsdb_get_table(do_transact_db, "mytable");
+    assert(do_transact_table != NULL);
+
+    for (i = 1; i < argc; i++) {
+        struct json *command;
+        size_t n_args;
+        char **args;
+        int j;
+
+        command = parse_json(argv[i]);
+        if (command->type != JSON_ARRAY) {
+            ovs_fatal(0, "transaction %d must be JSON array "
+                      "with at least 1 element", i);
+        }
+
+        n_args = command->u.array.n;
+        args = xmalloc((n_args + 1) * sizeof *args);
+        for (j = 0; j < n_args; j++) {
+            struct json *s = command->u.array.elems[j];
+            if (s->type != JSON_STRING) {
+                ovs_fatal(0, "transaction %d argument %d must be JSON string",
+                          i, j);
+            }
+            args[j] = xstrdup(json_string(s));
+        }
+        args[n_args] = NULL;
+
+        if (!do_transact_txn) {
+            do_transact_txn = ovsdb_txn_create(do_transact_db);
+        }
+
+        for (j = 0; j < n_args; j++) {
+            if (j) {
+                putchar(' ');
+            }
+            fputs(args[j], stdout);
+        }
+        fputs(":", stdout);
+        run_command(n_args, args, do_transact_commands);
+        putchar('\n');
+
+        for (j = 0; j < n_args; j++) {
+            free(args[j]);
+        }
+        free(args);
+        json_destroy(command);
+    }
+    ovsdb_txn_abort(do_transact_txn);
+    ovsdb_destroy(do_transact_db); /* Also destroys 'schema'. */
+}
+
+static int
+compare_selflink(const void *a_, const void *b_)
+{
+    const struct idltest_selflink *const *ap = a_;
+    const struct idltest_selflink *const *bp = b_;
+    const struct idltest_selflink *a = *ap;
+    const struct idltest_selflink *b = *bp;
+
+
+    return a->i < b->i ? -1 : a->i > b->i;
+}
+
+static void
+print_idl(struct ovsdb_idl *idl, int step)
+{
+    const struct idltest_simple *s;
+    const struct idltest_selflink *sl;
+    int n = 0;
+
+    IDLTEST_SIMPLE_FOR_EACH (s, idl) {
+        size_t i;
+
+        printf("%03d: i=%"PRId64" r=%g b=%s s=%s u="UUID_FMT" ia=[",
+               step, s->i, s->r, s->b ? "true" : "false",
+               s->s, UUID_ARGS(&s->u));
+        for (i = 0; i < s->n_ia; i++) {
+            printf("%s%"PRId64, i ? " " : "", s->ia[i]);
+        }
+        printf("] ra=[");
+        for (i = 0; i < s->n_ra; i++) {
+            printf("%s%g", i ? " " : "", s->ra[i]);
+        }
+        printf("] ba=[");
+        for (i = 0; i < s->n_ba; i++) {
+            printf("%s%s", i ? " " : "", s->ba[i] ? "true" : "false");
+        }
+        printf("] sa=[");
+        for (i = 0; i < s->n_sa; i++) {
+            printf("%s%s", i ? " " : "", s->sa[i]);
+        }
+        printf("] ua=[");
+        for (i = 0; i < s->n_ua; i++) {
+            printf("%s"UUID_FMT, i ? " " : "", UUID_ARGS(&s->ua[i]));
+        }
+        printf("] uuid="UUID_FMT"\n", UUID_ARGS(&s->header_.uuid));
+        n++;
+    }
+    IDLTEST_SELFLINK_FOR_EACH (sl, idl) {
+        struct idltest_selflink **links;
+        size_t i;
+
+        printf("%03d: i=%"PRId64" k=", step, sl->i);
+        if (sl->k) {
+            printf("%"PRId64, sl->k->i);
+        }
+        printf(" ka=[");
+        links = xmemdup(sl->ka, sl->n_ka * sizeof *sl->ka);
+        qsort(links, sl->n_ka, sizeof *links, compare_selflink);
+        for (i = 0; i < sl->n_ka; i++) {
+            printf("%s%"PRId64, i ? " " : "", links[i]->i);
+        }
+        free(links);
+        printf("] uuid="UUID_FMT"\n", UUID_ARGS(&sl->header_.uuid));
+        n++;
+    }
+    if (!n) {
+        printf("%03d: empty\n", step);
+    }
+}
+
+static unsigned int
+print_updated_idl(struct ovsdb_idl *idl, struct jsonrpc *rpc,
+                  int step, unsigned int seqno)
+{
+    for (;;) {
+        unsigned int new_seqno;
+
+        if (rpc) {
+            jsonrpc_run(rpc);
+        }
+        ovsdb_idl_run(idl);
+        new_seqno = ovsdb_idl_get_seqno(idl);
+        if (new_seqno != seqno) {
+            print_idl(idl, step);
+            return new_seqno;
+        }
+
+        if (rpc) {
+            jsonrpc_wait(rpc);
+        }
+        ovsdb_idl_wait(idl);
+        poll_block();
+    }
+}
+
+static void
+parse_uuids(const struct json *json, struct ovsdb_symbol_table *symtab,
+            size_t *n)
+{
+    struct uuid uuid;
+
+    if (json->type == JSON_STRING && uuid_from_string(&uuid, json->u.string)) {
+        char *name = xasprintf("#%d#", *n);
+        ovsdb_symbol_table_put(symtab, name, &uuid);
+        free(name);
+        *n += 1;
+    } else if (json->type == JSON_ARRAY) {
+        size_t i;
+
+        for (i = 0; i < json->u.array.n; i++) {
+            parse_uuids(json->u.array.elems[i], symtab, n);
+        }
+    } else if (json->type == JSON_OBJECT) {
+        const struct shash_node *node;
+
+        SHASH_FOR_EACH (node, json_object(json)) {
+            parse_uuids(node->data, symtab, n);
+        }
+    }
+}
+
+static void
+substitute_uuids(struct json *json, const struct ovsdb_symbol_table *symtab)
+{
+    if (json->type == JSON_STRING) {
+        const struct uuid *uuid;
+
+        uuid = ovsdb_symbol_table_get(symtab, json->u.string);
+        if (uuid) {
+            free(json->u.string);
+            json->u.string = xasprintf(UUID_FMT, UUID_ARGS(uuid));
+        }
+    } else if (json->type == JSON_ARRAY) {
+        size_t i;
+
+        for (i = 0; i < json->u.array.n; i++) {
+            substitute_uuids(json->u.array.elems[i], symtab);
+        }
+    } else if (json->type == JSON_OBJECT) {
+        const struct shash_node *node;
+
+        SHASH_FOR_EACH (node, json_object(json)) {
+            substitute_uuids(node->data, symtab);
+        }
+    }
+}
+
+static void
+do_idl(int argc, char *argv[])
+{
+    struct jsonrpc *rpc;
+    struct ovsdb_idl *idl;
+    unsigned int seqno = 0;
+    struct ovsdb_symbol_table *symtab;
+    size_t n_uuids = 0;
+    int step = 0;
+    int error;
+    int i;
+
+    idl = ovsdb_idl_create(argv[1], &idltest_idl_class);
+    if (argc > 2) {
+        struct stream *stream;
+
+        error = stream_open_block(argv[1], &stream);
+        if (error) {
+            ovs_fatal(error, "failed to connect to \"%s\"", argv[1]);
+        }
+        rpc = jsonrpc_open(stream);
+    } else {
+        rpc = NULL;
+    }
+
+    symtab = ovsdb_symbol_table_create();
+    for (i = 2; i < argc; i++) {
+        struct jsonrpc_msg *request, *reply;
+        int error;
+
+        seqno = print_updated_idl(idl, rpc, step++, seqno);
+
+        if (!strcmp(argv[i], "reconnect")) {
+            printf("%03d: reconnect\n", step++);
+            ovsdb_idl_force_reconnect(idl);
+        } else {
+            struct json *json = parse_json(argv[i]);
+            substitute_uuids(json, symtab);
+            request = jsonrpc_create_request("transact", json, NULL);
+            error = jsonrpc_transact_block(rpc, request, &reply);
+            if (error) {
+                ovs_fatal(error, "jsonrpc transaction failed");
+            }
+            printf("%03d: ", step++);
+            if (reply->result) {
+                parse_uuids(reply->result, symtab, &n_uuids);
+            }
+            json_destroy(reply->id);
+            reply->id = NULL;
+            print_and_free_json(jsonrpc_msg_to_json(reply));
+        }
+    }
+    ovsdb_symbol_table_destroy(symtab);
+
+    if (rpc) {
+        jsonrpc_close(rpc);
+    }
+    print_updated_idl(idl, NULL, step++, seqno);
+    ovsdb_idl_destroy(idl);
+    printf("%03d: done\n", step);
+}
+
+static struct command all_commands[] = {
+    { "log-io", 2, INT_MAX, do_log_io },
+    { "parse-atomic-type", 1, 1, do_parse_atomic_type },
+    { "parse-type", 1, 1, do_parse_type },
+    { "parse-atoms", 2, INT_MAX, do_parse_atoms },
+    { "parse-data", 2, INT_MAX, do_parse_data },
+    { "sort-atoms", 2, 2, do_sort_atoms },
+    { "parse-column", 2, 2, do_parse_column },
+    { "parse-table", 2, 2, do_parse_table },
+    { "parse-rows", 2, INT_MAX, do_parse_rows },
+    { "compare-rows", 2, INT_MAX, do_compare_rows },
+    { "parse-conditions", 2, INT_MAX, do_parse_conditions },
+    { "evaluate-conditions", 3, 3, do_evaluate_conditions },
+    { "query", 3, 3, do_query },
+    { "query-distinct", 4, 4, do_query_distinct },
+    { "transact", 1, INT_MAX, do_transact },
+    { "execute", 2, INT_MAX, do_execute },
+    { "trigger", 2, INT_MAX, do_trigger },
+    { "idl", 1, INT_MAX, do_idl },
+    { "help", 0, INT_MAX, do_help },
+    { NULL, 0, 0, NULL },
+};
diff --git a/tests/test-reconnect.c b/tests/test-reconnect.c
new file mode 100644 (file)
index 0000000..a8784fc
--- /dev/null
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "reconnect.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "command-line.h"
+#include "compiler.h"
+#include "svec.h"
+#include "util.h"
+
+static struct reconnect *reconnect;
+static int now;
+
+static const struct command commands[];
+
+static void diff_stats(const struct reconnect_stats *old,
+                       const struct reconnect_stats *new);
+
+int
+main(void)
+{
+    struct reconnect_stats prev;
+    int old_time;
+    char line[128];
+
+    now = 1000;
+    reconnect = reconnect_create(now);
+    reconnect_set_name(reconnect, "remote");
+    reconnect_get_stats(reconnect, now, &prev);
+    printf("### t=%d ###\n", now);
+    old_time = now;
+    while (fgets(line, sizeof line, stdin)) {
+        struct reconnect_stats cur;
+        struct svec args;
+
+        fputs(line, stdout);
+        if (line[0] == '#') {
+            continue;
+        }
+
+        svec_init(&args);
+        svec_parse_words(&args, line);
+        svec_terminate(&args);
+        if (!svec_is_empty(&args)) {
+            run_command(args.n, args.names, commands);
+        }
+        svec_destroy(&args);
+
+        if (old_time != now) {
+            printf("\n### t=%d ###\n", now);
+            old_time = now;
+        }
+
+        reconnect_get_stats(reconnect, now, &cur);
+        diff_stats(&prev, &cur);
+        prev = cur;
+    }
+
+    return 0;
+}
+
+static void
+do_enable(int argc UNUSED, char *argv[] UNUSED)
+{
+    reconnect_enable(reconnect, now);
+}
+
+static void
+do_disable(int argc UNUSED, char *argv[] UNUSED)
+{
+    reconnect_disable(reconnect, now);
+}
+
+static void
+do_force_reconnect(int argc UNUSED, char *argv[] UNUSED)
+{
+    reconnect_force_reconnect(reconnect, now);
+}
+
+static int
+error_from_string(const char *s)
+{
+    if (!s) {
+        return 0;
+    } else if (!strcmp(s, "ECONNREFUSED")) {
+        return ECONNREFUSED;
+    } else if (!strcmp(s, "EOF")) {
+        return EOF;
+    } else {
+        ovs_fatal(0, "unknown error '%s'", s);
+    }
+}
+
+static void
+do_disconnected(int argc UNUSED, char *argv[])
+{
+    reconnect_disconnected(reconnect, now, error_from_string(argv[1]));
+}
+
+static void
+do_connecting(int argc UNUSED, char *argv[] UNUSED)
+{
+    reconnect_connecting(reconnect, now);
+}
+
+static void
+do_connect_failed(int argc UNUSED, char *argv[])
+{
+    reconnect_connect_failed(reconnect, now, error_from_string(argv[1]));
+}
+
+static void
+do_connected(int argc UNUSED, char *argv[] UNUSED)
+{
+    reconnect_connected(reconnect, now);
+}
+
+static void
+do_received(int argc UNUSED, char *argv[] UNUSED)
+{
+    reconnect_received(reconnect, now);
+}
+
+static void
+do_run(int argc, char *argv[])
+{
+    enum reconnect_action action;
+
+    if (argc > 1) {
+        now += atoi(argv[1]);
+    }
+
+    action = reconnect_run(reconnect, now);
+    switch (action) {
+    default:
+        if (action != 0) {
+            NOT_REACHED();
+        }
+        break;
+
+    case RECONNECT_CONNECT:
+        printf("  should connect\n");
+        break;
+
+    case RECONNECT_DISCONNECT:
+        printf("  should disconnect\n");
+        break;
+
+    case RECONNECT_PROBE:
+        printf("  should send probe\n");
+        break;
+    }
+}
+
+static void
+do_advance(int argc UNUSED, char *argv[])
+{
+    now += atoi(argv[1]);
+}
+
+static void
+do_timeout(int argc UNUSED, char *argv[] UNUSED)
+{
+    int timeout = reconnect_timeout(reconnect, now);
+    if (timeout >= 0) {
+        printf("  advance %d ms\n", timeout);
+        now += timeout;
+    } else {
+        printf("  no timeout\n");
+    }
+}
+
+static void
+diff_stats(const struct reconnect_stats *old,
+           const struct reconnect_stats *new)
+{
+    if (old->state != new->state
+        || old->state_elapsed != new->state_elapsed
+        || old->backoff != new->backoff) {
+        printf("  in %s for %u ms (%d ms backoff)\n",
+               new->state, new->state_elapsed, new->backoff);
+    }
+    if (old->creation_time != new->creation_time
+        || old->last_received != new->last_received
+        || old->last_connected != new->last_connected) {
+        printf("  created %lld, last received %lld, last connected %lld\n",
+               new->creation_time, new->last_received, new->last_connected);
+    }
+    if (old->n_successful_connections != new->n_successful_connections
+        || old->n_attempted_connections != new->n_attempted_connections
+        || old->seqno != new->seqno) {
+        printf("  %u successful connections out of %u attempts, seqno %u\n",
+               new->n_successful_connections, new->n_attempted_connections,
+               new->seqno);
+    }
+    if (old->is_connected != new->is_connected
+        || old->current_connection_duration != new->current_connection_duration
+        || old->total_connected_duration != new->total_connected_duration) {
+        printf("  %sconnected (%u ms), total %u ms connected\n",
+               new->is_connected ? "" : "not ",
+               new->current_connection_duration,
+               new->total_connected_duration);
+    }
+}
+
+static const struct command commands[] = {
+    { "enable", 0, 0, do_enable },
+    { "disable", 0, 0, do_disable },
+    { "force-reconnect", 0, 0, do_force_reconnect },
+    { "disconnected", 0, 1, do_disconnected },
+    { "connecting", 0, 0, do_connecting },
+    { "connect-failed", 0, 1, do_connect_failed },
+    { "connected", 0, 0, do_connected },
+    { "received", 0, 0, do_received },
+    { "run", 0, 1, do_run },
+    { "advance", 1, 1, do_advance },
+    { "timeout", 0, 0, do_timeout },
+    { NULL, 0, 0, NULL },
+};
+
diff --git a/tests/test-timeval.c b/tests/test-timeval.c
new file mode 100644 (file)
index 0000000..ba8b3d3
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "timeval.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "daemon.h"
+#include "util.h"
+
+#undef NDEBUG
+#include <assert.h>
+
+static long long int
+gettimeofday_in_msec(void)
+{
+    struct timeval tv;
+
+    assert(!gettimeofday(&tv, NULL));
+    return timeval_to_msec(&tv);
+}
+
+static void
+do_test(void)
+{
+    /* Wait until we are awakened by a signal (typically EINTR due to the
+     * setitimer()).  Then ensure that, if time has really advanced by
+     * TIME_UPDATE_INTERVAL, then time_msec() reports that it advanced.
+     */
+    long long int start_time_msec;
+    long long int start_gtod;
+
+    start_time_msec = time_msec();
+    start_gtod = gettimeofday_in_msec();
+    for (;;) {
+        /* Wait up to 1 second.  Using select() to do the timeout avoids
+         * interfering with the interval timer. */
+        struct timeval timeout;
+        timeout.tv_sec = 1;
+        timeout.tv_usec = 0;
+        assert(select(0, NULL, NULL, NULL, &timeout) == -1 && errno == EINTR);
+
+        if (gettimeofday_in_msec() - start_gtod >= TIME_UPDATE_INTERVAL) {
+            assert(time_msec() - start_time_msec >= TIME_UPDATE_INTERVAL);
+            break;
+        }
+    }
+}
+
+static void
+usage(void)
+{
+    ovs_fatal(0, "usage: %s TEST, where TEST is \"plain\" or \"daemon\"",
+              program_name);
+}
+
+int
+main(int argc, char *argv[])
+{
+    set_program_name(argv[0]);
+    time_init();
+
+    if (argc != 2) {
+        usage();
+    } else if (!strcmp(argv[1], "plain")) {
+        do_test();
+    } else if (!strcmp(argv[1], "daemon")) {
+        /* Test that time still advances even in a daemon.  This is an
+         * interesting test because fork() cancels the interval timer. */
+        char cwd[1024];
+        FILE *success;
+
+        assert(getcwd(cwd, sizeof cwd) == cwd);
+
+        unlink("test-timeval.success");
+
+        /* Daemonize, with a pidfile in the current directory. */
+        set_detach();
+        set_pidfile(xasprintf("%s/test-timeval.pid", cwd));
+        set_no_chdir();
+        daemonize();
+
+        /* Run the test. */
+        do_test();
+
+        /* Report success by writing out a file, since the ultimate invoker of
+         * test-timeval can't wait on the daemonized process. */
+        success = fopen("test-timeval.success", "w");
+        if (!success) {
+            ovs_fatal(errno, "test-timeval.success: create failed");
+        }
+        fprintf(success, "success\n");
+        fclose(success);
+    } else {
+        usage();
+    }
+
+    return 0;
+}
diff --git a/tests/test-uuid.c b/tests/test-uuid.c
new file mode 100644 (file)
index 0000000..750b74e
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "uuid.h"
+#include <stdio.h>
+
+int
+main(int argc, char *argv[])
+{
+    struct uuid uuid;
+
+    if (argc == 1) {
+        uuid_generate(&uuid);
+    } else if (argc == 2) {
+        if (!uuid_from_string(&uuid, argv[1])) {
+            ovs_fatal(0, "\"%s\" is not a valid UUID", argv[1]);
+        }
+    } else {
+        ovs_fatal(0, "usage: %s [UUID]", argv[0]);
+    }
+
+    printf(UUID_FMT"\n", UUID_ARGS(&uuid));
+
+    return 0;
+}
index c232a87..d5eadc2 100644 (file)
@@ -16,9 +16,19 @@ limitations under the License.])
 
 AT_TESTED([ovs-vswitchd])
 AT_TESTED([ovs-vsctl])
+AT_TESTED([perl])
 
 m4_include([tests/lcov-pre.at])
 m4_include([tests/library.at])
+m4_include([tests/dir_name.at])
+m4_include([tests/aes128.at])
+m4_include([tests/uuid.at])
+m4_include([tests/json.at])
+m4_include([tests/jsonrpc.at])
+m4_include([tests/timeval.at])
+m4_include([tests/lockfile.at])
+m4_include([tests/reconnect.at])
+m4_include([tests/ovsdb.at])
 m4_include([tests/stp.at])
 m4_include([tests/ovs-vsctl.at])
 m4_include([tests/lcov-post.at])
diff --git a/tests/timeval.at b/tests/timeval.at
new file mode 100644 (file)
index 0000000..992706f
--- /dev/null
@@ -0,0 +1,22 @@
+AT_BANNER([timeval unit tests])
+
+AT_SETUP([check that time advances])
+AT_KEYWORDS([timeval])
+OVS_CHECK_LCOV([test-timeval plain], [0])
+AT_CLEANUP
+
+AT_SETUP([check that time advances after daemonize()])
+AT_KEYWORDS([timeval])
+OVS_CHECK_LCOV([test-timeval daemon], [0])
+AT_CHECK(
+  [# First try a quick sleep, so that the test completes very quickly
+   # in the normal case.  POSIX doesn't require fractional times to
+   # work, so this might not work.
+   sleep 0.1; if test -e test-timeval.success; then echo success; exit 0; fi
+   # Then wait up to 2 seconds.
+   sleep 1; if test -e test-timeval.success; then echo success; exit 0; fi
+   sleep 1; if test -e test-timeval.success; then echo success; exit 0; fi
+   echo failure; exit 1],
+  [0], [success
+], [])
+AT_CLEANUP
diff --git a/tests/uuid.at b/tests/uuid.at
new file mode 100644 (file)
index 0000000..04b652a
--- /dev/null
@@ -0,0 +1,58 @@
+AT_BANNER([UUID unit tests])
+
+m4_define([UUID_REGEX], 
+  [[[0-9a-f]\{8\}-[0-9a-f]\{4\}-4[0-9a-f]\{3\}-[89ab][0-9a-f]\{3\}-[0-9a-f]\{12\}$]])
+
+m4_define([CHECK_UUID],
+  [if expr "$uuid" : 'UUID_REGEX' > /dev/null
+   then
+      :
+   else
+     echo "$uuid: not a random UUID"
+     exit 1
+   fi])
+
+# This test is a strict subset of the larger test down below, but it
+# allows us to get test coverage data via OVS_CHECK_LCOV.
+AT_SETUP([UUID generation])
+AT_KEYWORDS([UUID])
+OVS_CHECK_LCOV([test-uuid > uuid])
+AT_CHECK([
+  uuid=`cat uuid`
+  CHECK_UUID])
+AT_CLEANUP
+
+# This test is a strict subset of the larger test down below, but it
+# allows us to get test coverage data via OVS_CHECK_LCOV.
+AT_SETUP([UUID parsing and serialization])
+AT_KEYWORDS([UUID])
+OVS_CHECK_LCOV([test-uuid f47ac10b-58cc-4372-a567-0e02b2c3d479], [0],
+               [f47ac10b-58cc-4372-a567-0e02b2c3d479
+])
+AT_CLEANUP
+
+AT_SETUP([UUID generation, parsing, serialization])
+AT_KEYWORDS([UUID])
+AT_CHECK([
+  uuids=
+  for i in m4_for([count], [1], [100], [1], [count ]); do
+     # Generate random UUID and check that it is in the expected format.
+     uuid=`test-uuid`
+     CHECK_UUID
+
+     # Verify that $uuid does not duplicate any UUID generated so far.
+     case $uuids in
+       *$uuid*) 
+         echo "$uuid: generated duplicate UUID"
+         exit 1
+     esac
+     uuids="$uuids $uuid"
+
+     # Verify that test-uuid parses and re-serializes this UUID correctly.
+     serialized=`test-uuid $uuid`
+     if test "$uuid" != "$serialized"; then
+       echo "$uuid: test-uuid serialized this as $serialized"
+       exit 1
+     fi
+   done], [0])
+AT_CLEANUP
diff --git a/tests/uuidfilt.pl b/tests/uuidfilt.pl
new file mode 100755 (executable)
index 0000000..6f003a5
--- /dev/null
@@ -0,0 +1,21 @@
+#! /usr/bin/perl
+
+use strict;
+use warnings;
+
+our %uuids;
+our $n_uuids = 0;
+sub lookup_uuid {
+    my ($uuid) = @_;
+    if (!exists($uuids{$uuid})) {
+        $uuids{$uuid} = $n_uuids++;
+    }
+    return "<$uuids{$uuid}>";
+}
+
+my $u = '[0-9a-fA-F]';
+my $uuid_re = "${u}{8}-${u}{4}-${u}{4}-${u}{4}-${u}{12}";
+while (<>) {
+    s/($uuid_re)/lookup_uuid($1)/eg;
+    print $_;
+}
index 8e9ed3d..5e50d72 100644 (file)
 #include "vlog.h"
 #define THIS_MODULE VLM_dpctl
 
-struct command {
-    const char *name;
-    int min_args;
-    int max_args;
-    void (*handler)(int argc, char *argv[]);
-};
-
-static struct command all_commands[];
+static const struct command all_commands[];
 
 static void usage(void) NO_RETURN;
 static void parse_options(int argc, char *argv[]);
 
-int main(int argc, char *argv[])
+int
+main(int argc, char *argv[])
 {
-    struct command *p;
-
     set_program_name(argv[0]);
     time_init();
     vlog_init();
     parse_options(argc, argv);
     signal(SIGPIPE, SIG_IGN);
-
-    argc -= optind;
-    argv += optind;
-    if (argc < 1)
-        ovs_fatal(0, "missing command name; use --help for help");
-
-    for (p = all_commands; p->name != NULL; p++) {
-        if (!strcmp(p->name, argv[0])) {
-            int n_arg = argc - 1;
-            if (n_arg < p->min_args)
-                ovs_fatal(0, "'%s' command requires at least %d arguments",
-                          p->name, p->min_args);
-            else if (n_arg > p->max_args)
-                ovs_fatal(0, "'%s' command takes at most %d arguments",
-                          p->name, p->max_args);
-            else {
-                p->handler(argc, argv);
-                if (ferror(stdout)) {
-                    ovs_fatal(0, "write to stdout failed");
-                }
-                if (ferror(stderr)) {
-                    ovs_fatal(0, "write to stderr failed");
-                }
-                exit(0);
-            }
-        }
-    }
-    ovs_fatal(0, "unknown command '%s'; use --help for help", argv[0]);
-
+    run_command(argc - optind, argv + optind, all_commands);
     return 0;
 }
 
@@ -529,7 +493,7 @@ do_help(int argc UNUSED, char *argv[] UNUSED)
     usage();
 }
 
-static struct command all_commands[] = {
+static const struct command all_commands[] = {
     { "add-dp", 1, INT_MAX, do_add_dp },
     { "del-dp", 1, 1, do_del_dp },
     { "add-if", 2, INT_MAX, do_add_if },
index db6038a..89af18e 100644 (file)
 #define MOD_PORT_CMD_FLOOD   "flood"
 #define MOD_PORT_CMD_NOFLOOD "noflood"
 
+/* Use strict matching for flow mod commands? */
+static bool strict;
 
-/* Settings that may be configured by the user. */
-struct settings {
-    bool strict;        /* Use strict matching for flow mod commands */
-};
-
-struct command {
-    const char *name;
-    int min_args;
-    int max_args;
-    void (*handler)(const struct settings *, int argc, char *argv[]);
-};
-
-static struct command all_commands[];
+static const struct command all_commands[];
 
 static void usage(void) NO_RETURN;
-static void parse_options(int argc, char *argv[], struct settings *);
+static void parse_options(int argc, char *argv[]);
 
-int main(int argc, char *argv[])
+int
+main(int argc, char *argv[])
 {
-    struct settings s;
-    struct command *p;
-
     set_program_name(argv[0]);
     time_init();
     vlog_init();
-    parse_options(argc, argv, &s);
+    parse_options(argc, argv);
     signal(SIGPIPE, SIG_IGN);
-
-    argc -= optind;
-    argv += optind;
-    if (argc < 1)
-        ovs_fatal(0, "missing command name; use --help for help");
-
-    for (p = all_commands; p->name != NULL; p++) {
-        if (!strcmp(p->name, argv[0])) {
-            int n_arg = argc - 1;
-            if (n_arg < p->min_args)
-                ovs_fatal(0, "'%s' command requires at least %d arguments",
-                          p->name, p->min_args);
-            else if (n_arg > p->max_args)
-                ovs_fatal(0, "'%s' command takes at most %d arguments",
-                          p->name, p->max_args);
-            else {
-                p->handler(&s, argc, argv);
-                if (ferror(stdout)) {
-                    ovs_fatal(0, "write to stdout failed");
-                }
-                if (ferror(stderr)) {
-                    ovs_fatal(0, "write to stderr failed");
-                }
-                exit(0);
-            }
-        }
-    }
-    ovs_fatal(0, "unknown command '%s'; use --help for help", argv[0]);
-
+    run_command(argc - optind, argv + optind, all_commands);
     return 0;
 }
 
 static void
-parse_options(int argc, char *argv[], struct settings *s)
+parse_options(int argc, char *argv[])
 {
     enum {
         OPT_STRICT = UCHAR_MAX + 1
@@ -136,9 +96,6 @@ parse_options(int argc, char *argv[], struct settings *s)
     };
     char *short_options = long_options_to_short_options(long_options);
 
-    /* Set defaults that we can figure out before parsing options. */
-    s->strict = false;
-
     for (;;) {
         unsigned long int timeout;
         int c;
@@ -171,7 +128,7 @@ parse_options(int argc, char *argv[], struct settings *s)
             break;
 
         case OPT_STRICT:
-            s->strict = true;
+            strict = true;
             break;
 
         VCONN_SSL_OPTION_HANDLERS
@@ -374,14 +331,14 @@ dump_trivial_stats_transaction(const char *vconn_name, uint8_t stats_type)
 }
 
 static void
-do_show(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_show(int argc UNUSED, char *argv[])
 {
     dump_trivial_transaction(argv[1], OFPT_FEATURES_REQUEST);
     dump_trivial_transaction(argv[1], OFPT_GET_CONFIG_REQUEST);
 }
 
 static void
-do_status(const struct settings *s UNUSED, int argc, char *argv[])
+do_status(int argc, char *argv[])
 {
     struct nicira_header *request, *reply;
     struct vconn *vconn;
@@ -413,13 +370,13 @@ do_status(const struct settings *s UNUSED, int argc, char *argv[])
 }
 
 static void
-do_dump_desc(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_dump_desc(int argc UNUSED, char *argv[])
 {
     dump_trivial_stats_transaction(argv[1], OFPST_DESC);
 }
 
 static void
-do_dump_tables(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_dump_tables(int argc UNUSED, char *argv[])
 {
     dump_trivial_stats_transaction(argv[1], OFPST_TABLE);
 }
@@ -815,7 +772,7 @@ str_to_flow(char *string, struct ofp_match *match, struct ofpbuf *actions,
 }
 
 static void
-do_dump_flows(const struct settings *s UNUSED, int argc, char *argv[])
+do_dump_flows(int argc, char *argv[])
 {
     struct ofp_flow_stats_request *req;
     uint16_t out_port;
@@ -831,7 +788,7 @@ do_dump_flows(const struct settings *s UNUSED, int argc, char *argv[])
 }
 
 static void
-do_dump_aggregate(const struct settings *s UNUSED, int argc, char *argv[])
+do_dump_aggregate(int argc, char *argv[])
 {
     struct ofp_aggregate_stats_request *req;
     struct ofpbuf *request;
@@ -847,7 +804,7 @@ do_dump_aggregate(const struct settings *s UNUSED, int argc, char *argv[])
 }
 
 static void
-do_add_flow(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_add_flow(int argc UNUSED, char *argv[])
 {
     struct vconn *vconn;
     struct ofpbuf *buffer;
@@ -875,7 +832,7 @@ do_add_flow(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
 }
 
 static void
-do_add_flows(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_add_flows(int argc UNUSED, char *argv[])
 {
     struct vconn *vconn;
     FILE *file;
@@ -928,7 +885,7 @@ do_add_flows(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
 }
 
 static void
-do_mod_flows(const struct settings *s, int argc UNUSED, char *argv[])
+do_mod_flows(int argc UNUSED, char *argv[])
 {
     uint16_t priority, idle_timeout, hard_timeout;
     struct vconn *vconn;
@@ -943,7 +900,7 @@ do_mod_flows(const struct settings *s, int argc UNUSED, char *argv[])
                 NULL, NULL, &priority, &idle_timeout, &hard_timeout);
     ofm = buffer->data;
     ofm->match = match;
-    if (s->strict) {
+    if (strict) {
         ofm->command = htons(OFPFC_MODIFY_STRICT);
     } else {
         ofm->command = htons(OFPFC_MODIFY);
@@ -959,7 +916,7 @@ do_mod_flows(const struct settings *s, int argc UNUSED, char *argv[])
     vconn_close(vconn);
 }
 
-static void do_del_flows(const struct settings *s, int argc, char *argv[])
+static void do_del_flows(int argc, char *argv[])
 {
     struct vconn *vconn;
     uint16_t priority;
@@ -971,7 +928,7 @@ static void do_del_flows(const struct settings *s, int argc, char *argv[])
     ofm = make_openflow(sizeof *ofm, OFPT_FLOW_MOD, &buffer);
     str_to_flow(argc > 2 ? argv[2] : "", &ofm->match, NULL, NULL, 
                 &out_port, &priority, NULL, NULL);
-    if (s->strict) {
+    if (strict) {
         ofm->command = htons(OFPFC_DELETE_STRICT);
     } else {
         ofm->command = htons(OFPFC_DELETE);
@@ -989,7 +946,7 @@ static void do_del_flows(const struct settings *s, int argc, char *argv[])
 }
 
 static void
-do_monitor(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_monitor(int argc UNUSED, char *argv[])
 {
     struct vconn *vconn;
 
@@ -1014,13 +971,13 @@ do_monitor(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
 }
 
 static void
-do_dump_ports(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_dump_ports(int argc UNUSED, char *argv[])
 {
     dump_trivial_stats_transaction(argv[1], OFPST_PORT);
 }
 
 static void
-do_probe(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_probe(int argc UNUSED, char *argv[])
 {
     struct ofpbuf *request;
     struct vconn *vconn;
@@ -1037,7 +994,7 @@ do_probe(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
 }
 
 static void
-do_mod_port(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_mod_port(int argc UNUSED, char *argv[])
 {
     struct ofpbuf *request, *reply;
     struct ofp_switch_features *osf;
@@ -1117,7 +1074,7 @@ do_mod_port(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
 }
 
 static void
-do_ping(const struct settings *s UNUSED, int argc, char *argv[])
+do_ping(int argc, char *argv[])
 {
     size_t max_payload = 65535 - sizeof(struct ofp_header);
     unsigned int payload;
@@ -1164,7 +1121,7 @@ do_ping(const struct settings *s UNUSED, int argc, char *argv[])
 }
 
 static void
-do_benchmark(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_benchmark(int argc UNUSED, char *argv[])
 {
     size_t max_payload = 65535 - sizeof(struct ofp_header);
     struct timeval start, end;
@@ -1207,7 +1164,7 @@ do_benchmark(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
 }
 
 static void
-do_execute(const struct settings *s UNUSED, int argc, char *argv[])
+do_execute(int argc, char *argv[])
 {
     struct vconn *vconn;
     struct ofpbuf *request;
@@ -1272,12 +1229,12 @@ do_execute(const struct settings *s UNUSED, int argc, char *argv[])
 }
 
 static void
-do_help(const struct settings *s UNUSED, int argc UNUSED, char *argv[] UNUSED)
+do_help(int argc UNUSED, char *argv[] UNUSED)
 {
     usage();
 }
 
-static struct command all_commands[] = {
+static const struct command all_commands[] = {
     { "show", 1, 1, do_show },
     { "status", 1, 2, do_status },
     { "monitor", 1, 3, do_monitor },
index 8e27fc2..db8fdf6 100644 (file)
@@ -17,6 +17,8 @@ vswitchd_ovs_vswitchd_SOURCES = \
        vswitchd/proc-net-compat.h \
        vswitchd/ovs-vswitchd.c \
        vswitchd/ovs-vswitchd.h \
+       vswitchd/vswitch-idl.c \
+       vswitchd/vswitch-idl.h \
        vswitchd/xenserver.c \
        vswitchd/xenserver.h
 vswitchd_ovs_vswitchd_LDADD = \
@@ -36,3 +38,9 @@ EXTRA_DIST += \
        vswitchd/ovs-vswitchd.conf.5.in \
        vswitchd/ovs-vswitchd.8.in \
        vswitchd/ovs-brcompatd.8.in
+
+EXTRA_DIST += vswitchd/vswitch-idl.ovsidl
+BUILT_SOURCES += vswitchd/vswitch-idl.c vswitchd/vswitch-idl.h
+DISTCLEANFILES += vswitchd/vswitch-idl.c vswitchd/vswitch-idl.h
+noinst_DATA += vswitchd/vswitch-idl.ovsschema
+DISTCLEANFILES += vswitchd/vswitch-idl.ovsschema
index dbcf312..df4169f 100644 (file)
@@ -59,6 +59,7 @@
 #include "unixctl.h"
 #include "vconn.h"
 #include "vconn-ssl.h"
+#include "vswitchd/vswitch-idl.h"
 #include "xenserver.h"
 #include "xtoxll.h"
 
@@ -196,7 +197,7 @@ enum { DP_MAX = 256 };
 static struct bridge *bridge_create(const char *name);
 static void bridge_destroy(struct bridge *);
 static struct bridge *bridge_lookup(const char *name);
-static void bridge_unixctl_dump_flows(struct unixctl_conn *, const char *);
+static unixctl_cb_func bridge_unixctl_dump_flows;
 static int bridge_run_one(struct bridge *);
 static void bridge_reconfigure_one(struct bridge *);
 static void bridge_reconfigure_controller(struct bridge *);
@@ -212,7 +213,7 @@ static uint64_t bridge_pick_datapath_id(struct bridge *,
 static struct iface *bridge_get_local_iface(struct bridge *);
 static uint64_t dpid_from_hash(const void *, size_t nbytes);
 
-static void bridge_unixctl_fdb_show(struct unixctl_conn *, const char *args);
+static unixctl_cb_func bridge_unixctl_fdb_show;
 
 static void bond_init(void);
 static void bond_run(struct bridge *);
@@ -290,7 +291,7 @@ bridge_init(void)
     struct svec dpif_names;
     size_t i;
 
-    unixctl_command_register("fdb/show", bridge_unixctl_fdb_show);
+    unixctl_command_register("fdb/show", bridge_unixctl_fdb_show, NULL);
 
     svec_init(&dpif_names);
     dp_enumerate(&dpif_names);
@@ -319,7 +320,8 @@ bridge_init(void)
     }
     svec_destroy(&dpif_names);
 
-    unixctl_command_register("bridge/dump-flows", bridge_unixctl_dump_flows);
+    unixctl_command_register("bridge/dump-flows", bridge_unixctl_dump_flows,
+                             NULL);
 
     bond_init();
     bridge_reconfigure();
@@ -1012,7 +1014,8 @@ bridge_get_local_iface(struct bridge *br)
 \f
 /* Bridge unixctl user interface functions. */
 static void
-bridge_unixctl_fdb_show(struct unixctl_conn *conn, const char *args)
+bridge_unixctl_fdb_show(struct unixctl_conn *conn,
+                        const char *args, void *aux UNUSED)
 {
     struct ds ds = DS_EMPTY_INITIALIZER;
     const struct bridge *br;
@@ -1046,7 +1049,7 @@ bridge_create(const char *name)
     int error;
 
     assert(!bridge_lookup(name));
-    br = xcalloc(1, sizeof *br);
+    br = xzalloc(sizeof *br);
 
     error = dpif_create_and_open(name, &br->dpif);
     if (error) {
@@ -1136,7 +1139,8 @@ bridge_get_datapathid(const char *name)
 /* Handle requests for a listing of all flows known by the OpenFlow
  * stack, including those normally hidden. */
 static void
-bridge_unixctl_dump_flows(struct unixctl_conn *conn, const char *args)
+bridge_unixctl_dump_flows(struct unixctl_conn *conn,
+                          const char *args, void *aux UNUSED)
 {
     struct bridge *br;
     struct ds results;
@@ -2658,7 +2662,8 @@ bond_send_learning_packets(struct port *port)
 /* Bonding unixctl user interface functions. */
 
 static void
-bond_unixctl_list(struct unixctl_conn *conn, const char *args UNUSED)
+bond_unixctl_list(struct unixctl_conn *conn,
+                  const char *args UNUSED, void *aux UNUSED)
 {
     struct ds ds = DS_EMPTY_INITIALIZER;
     const struct bridge *br;
@@ -2708,7 +2713,8 @@ bond_find(const char *name)
 }
 
 static void
-bond_unixctl_show(struct unixctl_conn *conn, const char *args)
+bond_unixctl_show(struct unixctl_conn *conn,
+                  const char *args, void *aux UNUSED)
 {
     struct ds ds = DS_EMPTY_INITIALIZER;
     const struct port *port;
@@ -2773,7 +2779,8 @@ bond_unixctl_show(struct unixctl_conn *conn, const char *args)
 }
 
 static void
-bond_unixctl_migrate(struct unixctl_conn *conn, const char *args_)
+bond_unixctl_migrate(struct unixctl_conn *conn, const char *args_,
+                     void *aux UNUSED)
 {
     char *args = (char *) args_;
     char *save_ptr = NULL;
@@ -2829,7 +2836,8 @@ bond_unixctl_migrate(struct unixctl_conn *conn, const char *args_)
 }
 
 static void
-bond_unixctl_set_active_slave(struct unixctl_conn *conn, const char *args_)
+bond_unixctl_set_active_slave(struct unixctl_conn *conn, const char *args_,
+                              void *aux UNUSED)
 {
     char *args = (char *) args_;
     char *save_ptr = NULL;
@@ -2909,19 +2917,22 @@ enable_slave(struct unixctl_conn *conn, const char *args_, bool enable)
 }
 
 static void
-bond_unixctl_enable_slave(struct unixctl_conn *conn, const char *args)
+bond_unixctl_enable_slave(struct unixctl_conn *conn, const char *args,
+                          void *aux UNUSED)
 {
     enable_slave(conn, args, true);
 }
 
 static void
-bond_unixctl_disable_slave(struct unixctl_conn *conn, const char *args)
+bond_unixctl_disable_slave(struct unixctl_conn *conn, const char *args,
+                           void *aux UNUSED)
 {
     enable_slave(conn, args, false);
 }
 
 static void
-bond_unixctl_hash(struct unixctl_conn *conn, const char *args)
+bond_unixctl_hash(struct unixctl_conn *conn, const char *args,
+                  void *aux UNUSED)
 {
        uint8_t mac[ETH_ADDR_LEN];
        uint8_t hash;
@@ -2942,14 +2953,16 @@ bond_unixctl_hash(struct unixctl_conn *conn, const char *args)
 static void
 bond_init(void)
 {
-    unixctl_command_register("bond/list", bond_unixctl_list);
-    unixctl_command_register("bond/show", bond_unixctl_show);
-    unixctl_command_register("bond/migrate", bond_unixctl_migrate);
+    unixctl_command_register("bond/list", bond_unixctl_list, NULL);
+    unixctl_command_register("bond/show", bond_unixctl_show, NULL);
+    unixctl_command_register("bond/migrate", bond_unixctl_migrate, NULL);
     unixctl_command_register("bond/set-active-slave",
-                             bond_unixctl_set_active_slave);
-    unixctl_command_register("bond/enable-slave", bond_unixctl_enable_slave);
-    unixctl_command_register("bond/disable-slave", bond_unixctl_disable_slave);
-    unixctl_command_register("bond/hash", bond_unixctl_hash);
+                             bond_unixctl_set_active_slave, NULL);
+    unixctl_command_register("bond/enable-slave", bond_unixctl_enable_slave,
+                             NULL);
+    unixctl_command_register("bond/disable-slave", bond_unixctl_disable_slave,
+                             NULL);
+    unixctl_command_register("bond/hash", bond_unixctl_hash, NULL);
 }
 \f
 /* Port functions. */
@@ -2959,7 +2972,7 @@ port_create(struct bridge *br, const char *name)
 {
     struct port *port;
 
-    port = xcalloc(1, sizeof *port);
+    port = xzalloc(sizeof *port);
     port->bridge = br;
     port->port_idx = br->n_ports;
     port->vlan = -1;
@@ -3312,7 +3325,7 @@ iface_create(struct port *port, const char *name)
 {
     struct iface *iface;
 
-    iface = xcalloc(1, sizeof *iface);
+    iface = xzalloc(sizeof *iface);
     iface->port = port;
     iface->port_ifidx = port->n_ifaces;
     iface->name = xstrdup(name);
@@ -3543,7 +3556,7 @@ mirror_create(struct bridge *br, const char *name)
     VLOG_INFO("created port mirror %s on bridge %s", name, br->name);
     bridge_flush(br);
 
-    br->mirrors[i] = m = xcalloc(1, sizeof *m);
+    br->mirrors[i] = m = xzalloc(sizeof *m);
     m->bridge = br;
     m->idx = i;
     m->name = xstrdup(name);
index 28491fc..fd2144a 100644 (file)
@@ -50,7 +50,7 @@
 
 static void parse_options(int argc, char *argv[]);
 static void usage(void) NO_RETURN;
-static void reload(struct unixctl_conn *, const char *args);
+static unixctl_cb_func reload;
 
 static bool need_reconfigure;
 static struct unixctl_conn **conns;
@@ -79,7 +79,7 @@ main(int argc, char *argv[])
     if (retval) {
         ovs_fatal(retval, "could not listen for control connections");
     }
-    unixctl_command_register("vswitchd/reload", reload);
+    unixctl_command_register("vswitchd/reload", reload, NULL);
 
     retval = cfg_read();
     if (retval) {
@@ -122,7 +122,7 @@ main(int argc, char *argv[])
 }
 
 static void
-reload(struct unixctl_conn *conn, const char *args UNUSED)
+reload(struct unixctl_conn *conn, const char *args UNUSED, void *aux UNUSED)
 {
     need_reconfigure = true;
     conns = xrealloc(conns, sizeof *conns * (n_conns + 1));
index 7a59526..68ae1ac 100644 (file)
@@ -256,7 +256,7 @@ proc_net_compat_update_vlan(const char *tagged_dev, const char *trunk_dev,
         }
         if (!vlan) {
             /* Create a new compat_vlan for (trunk_dev,vid). */
-            vlan = xcalloc(1, sizeof *vlan);
+            vlan = xzalloc(sizeof *vlan);
             vlan->trunk_dev = xstrdup(trunk_dev);
             vlan->vid = vid;
             vlan->vlan_dev = xasprintf("%s.%d", trunk_dev, vid);
diff --git a/vswitchd/vswitch-idl.ovsidl b/vswitchd/vswitch-idl.ovsidl
new file mode 100644 (file)
index 0000000..df50bf0
--- /dev/null
@@ -0,0 +1,175 @@
+//
+// This is an ovsdb-idl schema.  The OVSDB IDL compiler, ovsdb-idlc,
+// can translate it into an OVSDB schema (which simply entails
+// deleting some members from the schema) or C headers or source for
+// use with the IDL at runtime.
+//
+
+{"name": "ovs_vswitchd_db",
+ "comment": "Configuration for one Open vSwitch daemon.",
+ "idlPrefix": "ovsrec_",
+ "idlHeader": "\"vswitchd/vswitch-idl.h\"",
+ "tables": {
+   "Open_vSwitch": {
+     "comment": "Configuration for an Open vSwitch daemon.",
+     "columns": {
+       "bridges": {
+         "comment": "Set of bridges managed by the daemon.",
+         "type": {"key": "uuid", "keyRefTable": "Bridge",
+                  "min": 0, "max": "unlimited"}},
+       "management_id": {
+         "comment": "Exactly 12 hex digits that identify the daemon.",
+         "type": "string"},
+       "controller": {
+         "comment": "Default Controller used by bridges.",
+         "type": {"key": "uuid", "keyRefTable": "Controller", "min": 0, "max": 1}},
+       "ssl": {
+         "comment": "SSL used globally by the daemon.",
+         "type": {"key": "uuid", "keyRefTable": "SSL", "min": 0, "max": 1}}}},
+   "Bridge": {
+     "comment": "Configuration for a bridge within an Open_vSwitch.",
+     "columns": {
+       "name": {
+         "comment": "Bridge identifier.  Should be alphanumeric and no more than about 8 bytes long.  Must be unique among the names of ports, interfaces, and bridges on a host.",
+         "type": "string"},
+       "datapath_id": {
+         "comment": "OpenFlow datapath ID.  Exactly 12 hex digits.",
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "hwaddr": {
+         "comment": "Ethernet address to use for bridge.  Exactly 12 hex digits in the form XX:XX:XX:XX:XX:XX.",
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "ports": {
+         "comment": "Ports included in the bridge.",
+         "type": {"key": "uuid", "keyRefTable": "Port", "min": 0, "max": "unlimited"}},
+       "mirrors": {
+         "comment": "Port mirroring configuration.",
+         "type": {"key": "uuid", "keyRefTable": "Mirror", "min": 0, "max": "unlimited"}},
+       "netflow": {
+         "comment": "NetFlow configuration.",
+         "type": {"key": "uuid", "keyRefTable": "NetFlow", "min": 0, "max": "unlimited"}},
+       "controller": {
+         "comment": "OpenFlow controller.  If unset, defaults to that specified by the parent Open_vSwitch.",
+         "type": {"key": "uuid", "keyRefTable": "Controller", "min": 0, "max": 1}}}},
+   "Port": {
+     "comment": "A port within a Bridge.  May contain a single Interface or multiple (bonded) Interfaces.",
+     "columns": {
+       "name": {
+         "comment": "Port name.  Should be alphanumeric and no more than about 8 bytes long.    May be the same as the interface name, for non-bonded ports.  Must otherwise be unique among the names of ports, interfaces, and bridges on a host.",
+         "type": "string"},
+       "interfaces": {
+         "comment": "The Port's Interfaces.  If there is more than one, this is a bonded Port.",
+         "type": {"key": "uuid", "keyRefTable": "Interface", "min": 1, "max": "unlimited"}},
+       "trunks": {
+         "comment": "The 802.1Q VLAN(s) that this port trunks.  Should be empty if this port trunks all VLAN(s) or if this is not a trunk port.",
+         "type": {"key": "integer", "min": 0, "max": 4096}},
+       "tag": {
+         "comment": "This port's implicitly tagged VLAN.  Should be empty if this is a trunk port.",
+         "type": {"key": "integer", "min": 0, "max": 1}},
+       "updelay": {
+         "comment": "For a bonded port, the number of milliseconds for which carrier must stay up on an interface before the interface is considered to be up.  Ignored for non-bonded ports.",
+         "type": "integer"},
+       "downdelay": {
+         "comment": "For a bonded port, the number of milliseconds for which carrier must stay down on an interface before the interface is considered to be down.  Ignored for non-bonded ports.",
+         "type": "integer"}}},
+   "Interface": {
+     "comment": "An interface within a Port.",
+     "columns": {
+       "name": {
+         "comment": "Interface name.  Should be alphanumeric and no more than about 8 bytes long.  May be the same as the port name, for non-bonded ports.  Must otherwise be unique among the names of ports, interfaces, and bridges on a host.",
+         "type": "string"},
+       "internal": {
+         "comment": "An \"internal\" port is one that is implemented in software as a logical device.",
+         "type": "boolean"},
+       "ingress_policing_rate": {
+         "comment": "Maximum rate for data received on this interface, in kbps.  Set to 0 to disable policing.",
+         "type": "integer"},
+       "ingress_policing_burst": {
+         "comment": "Maximum burst size for data received on this interface, in kb.  The default burst size if set to 0 is 10 kb.",
+         "type": "integer"}}},
+   "Mirror": {
+     "comment": "A port mirror within a Bridge.",
+     "columns": {
+       "name": {
+         "comment": "Arbitrary identifier for the Mirror.",
+         "type": "string"},
+       "select_src_port": {
+         "comment": "Ports on which arriving packets are selected for mirroring.",
+         "type": {"key": "uuid", "keyRefTable": "Port", "min": 0, "max": "unlimited"}},
+       "select_dst_port": {
+         "comment": "Ports on which departing packets are selected for mirroring.",
+         "type": {"key": "uuid", "keyRefTable": "Port", "min": 0, "max": "unlimited"}},
+       "select_vlan": {
+         "comment": "VLANs on which packets are selected for mirroring.",
+         "type": {"key": "integer", "min": 0, "max": 4096}},
+       "output_port": {
+         "comment": "Output port for selected packets.  Mutually exclusive with output_vlan.",
+         "type": {"key": "uuid", "keyRefTable": "Port", "min": 0, "max": 1}},
+       "output_vlan": {
+         "comment": "Output VLAN for selected packets.  Mutually exclusive with output_port.",
+         "type": {"key": "integer", "min": 0, "max": 1}}}},
+   "NetFlow": {
+     "comment": "A NetFlow target.",
+     "columns": {
+       "target": {
+         "comment": "NetFlow target in the form \"IP:PORT\".",
+         "type": "string"},
+       "engine_type": {
+         "comment": "Engine type to use in NetFlow messages.  Defaults to datapath index if not specified.",
+         "type": "integer", "min":0, "max":1},
+       "engine_id": {
+         "comment": "Engine ID to use in NetFlow messages.  Defaults to datapath index if not specified.",
+         "type": "integer", "min":0, "max":1},
+       "add_id_to_interface": {
+         "comment": "Place least-significant 7 bits of engine ID into most significant bits of ingress and egress interface fields of NetFlow records?",
+         "type": "boolean"}}},
+   "Controller": {
+     "comment": "An OpenFlow controller.",
+     "columns": {
+       "target": {
+         "comment": "Connection method for controller, e.g. \"ssl:...\", \"tcp:...\".  The special string \"discover\" enables controller discovery.",
+         "type": "string"},
+       "max_backoff": {
+         "comment": "Maximum number of milliseconds to wait between connection attempts.  Default is implementation-specific.",
+         "type": {"key": "integer", "min": 0, "max": 1}},
+       "inactivity_probe": {
+         "comment": "Maximum number of milliseconds of idle time on connection to controller before sending an inactivity probe message.  Default is implementation-specific.",
+         "type": {"key": "integer", "min": 0, "max": 1}},
+       "fail_mode": {
+         "comment": "Either \"standalone\" or \"secure\", or empty to use the implementation's default.",
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "discover_accept_regex": {
+         "comment": "If \"target\" is \"discover\", a POSIX extended regular expression against which the discovered controller location is validated.  If not specified, the default is implementation-specific.",
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "discover_update_resolv_conf": {
+         "comment": "If \"target\" is \"discover\", whether to update /etc/resolv.conf when the controller is discovered.  If not specified, the default is implementation-specific.",
+         "type": {"key": "boolean", "min": 0, "max": 1}},
+       "connection_mode": {
+         "comment": "Either \"in-band\" or \"out-of-band\".  If not specified, the default is implementation-specific.",
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "local_ip": {
+         "comment": "If \"target\" is not \"discover\", the IP address to configure on the local port.",
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "local_netmask": {
+         "comment": "If \"target\" is not \"discover\", the IP netmask to configure on the local port.",
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "local_gateway": {
+         "comment": "If \"target\" is not \"discover\", the IP gateway to configure on the local port.",
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "controller_rate_limit": {
+         "comment": "The maximum rate at which packets will be forwarded to the OpenFlow controller, in packets per second.  If not specified, the default is implementation-specific.",
+         "type": {"key": "integer", "min": 0, "max": 1}},
+       "controller_burst_limit": {
+         "comment": "The maximum number of unused packet credits that the bridge will allow to accumulate, in packets.  If not specified, the default is implementation-specific.",
+         "type": {"key": "integer", "min": 0, "max": 1}}}},
+   "SSL": {
+     "comment": "SSL configuration for an Open_vSwitch.",
+     "columns": {
+       "private_key": {
+         "comment": "Name of a PEM file containing the private key used as the switch's identity for SSL connections to the controller.",
+         "type": "string"},
+       "certificate": {
+         "comment": "Name of a PEM file containing a certificate, signed by the certificate authority (CA) used by the controller and manager, that certifies the switch's private key, identifying a trustworthy switch.",
+         "type": "string"},
+       "ca_cert": {
+         "comment": "Name of a PEM file containing the CA certificate used to verify that the switch is connected to a trustworthy controller.",
+         "type": "string"}}}}}