* [dpdk-dev] [PATCH 0/2] Elastic Flow Distributor
@ 2016-12-02 14:52 Pablo de Lara
2016-12-02 14:52 ` [dpdk-dev] [PATCH 1/2] efd: new Elastic Flow Distributor library Pablo de Lara
` (2 more replies)
0 siblings, 3 replies; 63+ messages in thread
From: Pablo de Lara @ 2016-12-02 14:52 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara
EFD is a distributor library that uses perfect hashing to determine a
target/value for a given incoming flow key. It has the following advantages:
first, because it uses perfect hashing it does not store the key itself and
hence lookup performance is not dependent on the key size. Second, the
target/value can be any arbitrary value hence the system designer and/or
operator can better optimize service rates and inter-cluster network traffic
locating. Third, since the storage requirement is much smaller than a
hash-based flow table (i.e. better fit for CPU cache), EFD can scale to millions
of flow keys. Finally, with current optimized library implementation performance
is fully scalable with number of CPU cores.
The basic idea of EFD is when a given key is to be inserted, a family of hash
functions is searched until the correct hash function that maps the input key to
the correct value is found. However, rather than explicitly storing all keys and
their associated values, EFD stores only indices of hash functions that map keys
to values, and thereby consumes much less space than conventional flow-based
tables. The lookup operation is very simple, similar to computational-based
scheme, given an input key the lookup operation is reduced to hashing that key
with the correct hash function.
Intuitively, finding a hash function that maps each of a large number (millions)
of input keys to the correct output value is effectively impossible, as a result
EFD, breaks the problem into smaller pieces (divide and conquer). EFD divides
the entire input key set into many small groups. Each group consists of
approximately 20-28 keys (a configurable parameter for the library), then, for
each small group, a brute force search to find a hash function that produces the
correct outputs for each key in the group.
It should be mentioned that since in the online lookup table for EFD doesn’t
store the key itself, the size of the EFD table is independent of the key size
and hence EFD lookup performance which is almost constant irrespective of the
length of the key which is a highly desirable feature especially for longer
keys.
Library code is included in the patch, plus an sample application that shows
how the library can be used.
RFC for this library was already sent:
http://dpdk.org/ml/archives/dev/2016-October/049238.html
For more information on the library, check out the following document:
https://github.com/pablodelara/perfect_hash_flow_distributor/blob/master/EFD_description.pdf
Pablo de Lara (2):
efd: new Elastic Flow Distributor library
examples/flow_distributor: sample app to demonstrate EFD usage
config/common_base | 5 +
examples/Makefile | 1 +
examples/flow_distributor/Makefile | 44 +
examples/flow_distributor/distributor/Makefile | 57 ++
examples/flow_distributor/distributor/args.c | 200 ++++
examples/flow_distributor/distributor/args.h | 39 +
examples/flow_distributor/distributor/init.c | 371 ++++++++
examples/flow_distributor/distributor/init.h | 76 ++
examples/flow_distributor/distributor/main.c | 362 +++++++
examples/flow_distributor/node/Makefile | 48 +
examples/flow_distributor/node/node.c | 417 ++++++++
examples/flow_distributor/shared/common.h | 99 ++
lib/Makefile | 1 +
lib/librte_eal/common/include/rte_log.h | 1 +
lib/librte_efd/Makefile | 56 ++
lib/librte_efd/rte_efd.c | 1203 ++++++++++++++++++++++++
lib/librte_efd/rte_efd.h | 312 ++++++
lib/librte_efd/rte_efd_version.map | 12 +
mk/rte.app.mk | 1 +
19 files changed, 3305 insertions(+)
create mode 100644 examples/flow_distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/args.c
create mode 100644 examples/flow_distributor/distributor/args.h
create mode 100644 examples/flow_distributor/distributor/init.c
create mode 100644 examples/flow_distributor/distributor/init.h
create mode 100644 examples/flow_distributor/distributor/main.c
create mode 100644 examples/flow_distributor/node/Makefile
create mode 100644 examples/flow_distributor/node/node.c
create mode 100644 examples/flow_distributor/shared/common.h
create mode 100644 lib/librte_efd/Makefile
create mode 100644 lib/librte_efd/rte_efd.c
create mode 100644 lib/librte_efd/rte_efd.h
create mode 100644 lib/librte_efd/rte_efd_version.map
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH 1/2] efd: new Elastic Flow Distributor library
2016-12-02 14:52 [dpdk-dev] [PATCH 0/2] Elastic Flow Distributor Pablo de Lara
@ 2016-12-02 14:52 ` Pablo de Lara
2016-12-02 14:52 ` [dpdk-dev] [PATCH 2/2] examples/flow_distributor: sample app to demonstrate EFD usage Pablo de Lara
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 0/5] Elastic Flow Distributor Pablo de Lara
2 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2016-12-02 14:52 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara
Elastic Flow Distributor (EFD) is a distributor library that uses
perfect hashing to determine a target/value for a given incoming flow key.
It has the following advantages:
- First, because it uses perfect hashing, it does not store the key itself and
hence lookup performance is not dependent on the key size.
- Second, the target/value can be any arbitrary value hence the system designer
and/or operator can better optimize service rates and inter-cluster
network traffic locating.
- Third, since the storage requirement is much smaller than a hash-based flow table
(i.e. better fit for CPU cache), EFD can scale to millions of flow keys.
Finally, with current optimized library implementation performance
is fully scalable with number of CPU cores.
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
---
config/common_base | 5 +
lib/Makefile | 1 +
lib/librte_eal/common/include/rte_log.h | 1 +
lib/librte_efd/Makefile | 56 ++
lib/librte_efd/rte_efd.c | 1203 +++++++++++++++++++++++++++++++
lib/librte_efd/rte_efd.h | 312 ++++++++
lib/librte_efd/rte_efd_version.map | 12 +
mk/rte.app.mk | 1 +
8 files changed, 1591 insertions(+)
create mode 100644 lib/librte_efd/Makefile
create mode 100644 lib/librte_efd/rte_efd.c
create mode 100644 lib/librte_efd/rte_efd.h
create mode 100644 lib/librte_efd/rte_efd_version.map
diff --git a/config/common_base b/config/common_base
index 4bff83a..376742a 100644
--- a/config/common_base
+++ b/config/common_base
@@ -458,6 +458,11 @@ CONFIG_RTE_LIBRTE_HASH=y
CONFIG_RTE_LIBRTE_HASH_DEBUG=n
#
+# Compile librte_efd
+#
+CONFIG_RTE_LIBRTE_EFD=y
+
+#
# Compile librte_jobstats
#
CONFIG_RTE_LIBRTE_JOBSTATS=y
diff --git a/lib/Makefile b/lib/Makefile
index 990f23a..9a41188 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -43,6 +43,7 @@ DIRS-$(CONFIG_RTE_LIBRTE_ETHER) += librte_ether
DIRS-$(CONFIG_RTE_LIBRTE_CRYPTODEV) += librte_cryptodev
DIRS-$(CONFIG_RTE_LIBRTE_VHOST) += librte_vhost
DIRS-$(CONFIG_RTE_LIBRTE_HASH) += librte_hash
+DIRS-$(CONFIG_RTE_LIBRTE_EFD) += librte_efd
DIRS-$(CONFIG_RTE_LIBRTE_LPM) += librte_lpm
DIRS-$(CONFIG_RTE_LIBRTE_ACL) += librte_acl
DIRS-$(CONFIG_RTE_LIBRTE_NET) += librte_net
diff --git a/lib/librte_eal/common/include/rte_log.h b/lib/librte_eal/common/include/rte_log.h
index 29f7d19..70e150d 100644
--- a/lib/librte_eal/common/include/rte_log.h
+++ b/lib/librte_eal/common/include/rte_log.h
@@ -79,6 +79,7 @@ extern struct rte_logs rte_logs;
#define RTE_LOGTYPE_PIPELINE 0x00008000 /**< Log related to pipeline. */
#define RTE_LOGTYPE_MBUF 0x00010000 /**< Log related to mbuf. */
#define RTE_LOGTYPE_CRYPTODEV 0x00020000 /**< Log related to cryptodev. */
+#define RTE_LOGTYPE_EFD 0x00040000 /**< Log related to cryptodev. */
/* these log types can be used in an application */
#define RTE_LOGTYPE_USER1 0x01000000 /**< User-defined log type 1. */
diff --git a/lib/librte_efd/Makefile b/lib/librte_efd/Makefile
new file mode 100644
index 0000000..ae21bc7
--- /dev/null
+++ b/lib/librte_efd/Makefile
@@ -0,0 +1,56 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+# library name
+LIB = librte_efd.a
+
+CFLAGS += $(WERROR_FLAGS) -I$(SRCDIR) -O3
+CFLAGS += -D_GNU_SOURCE
+
+EXPORT_MAP := rte_efd_version.map
+
+LIBABIVER := 1
+
+# all source are stored in SRCS-y
+SRCS-$(CONFIG_RTE_LIBRTE_EFD) := rte_efd.c
+
+# install this header file
+SYMLINK-$(CONFIG_RTE_LIBRTE_EFD)-include := rte_efd.h
+
+# this lib depends upon:
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_mbuf
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_mempool
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_eal
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_ether
+
+include $(RTE_SDK)/mk/rte.lib.mk
diff --git a/lib/librte_efd/rte_efd.c b/lib/librte_efd/rte_efd.c
new file mode 100644
index 0000000..0b8b51e
--- /dev/null
+++ b/lib/librte_efd/rte_efd.c
@@ -0,0 +1,1203 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <immintrin.h>
+#include <math.h>
+#include <sys/queue.h>
+
+#include <rte_log.h>
+#include <rte_eal_memconfig.h>
+#include <rte_errno.h>
+#include <rte_malloc.h>
+#include <rte_memzone.h>
+#include <rte_prefetch.h>
+#include <rte_branch_prediction.h>
+
+#include "rte_efd.h"
+
+/** Hash function used to determine chunk_id and bin_id for a group */
+#define EFD_HASH(key) (efd_hash_internal((key), 0xbc9f1d34))
+/** Hash function used as constant component of perfect hash search */
+#define EFD_HASHFUNCA(key) (efd_hash_internal((key), 0xbc9f1d35))
+/** Hash function used as multiplicative component of perfect hash search */
+#define EFD_HASHFUNCB(key) (efd_hash_internal((key), 0xbc9f1d36))
+
+/*************************************************************************
+ * Fixed constants
+ *************************************************************************/
+
+/* These parameters are fixed by the efd_bin_to_group balancing table */
+#define EFD_CHUNK_NUM_GROUPS (64)
+#define EFD_CHUNK_NUM_BINS (256)
+#define EFD_CHUNK_NUM_BIN_TO_GROUP_SETS (EFD_CHUNK_NUM_BINS / EFD_CHUNK_NUM_GROUPS)
+
+/*
+ * Target number of rules that each chunk is created to handle.
+ * Used when initially allocating the table
+ */
+#define EFD_TARGET_CHUNK_NUM_RULES (EFD_CHUNK_NUM_GROUPS * EFD_TARGET_GROUP_NUM_RULES)
+
+/** This is fixed based on the bin_to_group permutation array */
+#define EFD_MAX_GROUP_NUM_BINS (16)
+
+/**
+ * The end of the chunks array needs some extra padding to ensure
+ * that vectorization over-reads on the last online chunk stay within
+allocated memory
+ */
+#define EFD_NUM_CHUNK_PADDING_BYTES (256)
+
+#define EFD_LOOKUPTBL_SHIFT (32 - 4)
+typedef uint16_t efd_lookuptbl_t;
+typedef uint16_t efd_hashfunc_t;
+
+TAILQ_HEAD(rte_efd_list, rte_tailq_entry);
+
+static struct rte_tailq_elem rte_efd_tailq = {
+ .name = "RTE_EFD",
+};
+EAL_REGISTER_TAILQ(rte_efd_tailq);
+
+/** Internal permutation array used to shuffle bins into pseudorandom groups */
+const uint32_t efd_bin_to_group[EFD_CHUNK_NUM_BIN_TO_GROUP_SETS][EFD_CHUNK_NUM_BINS] = {
+ {
+ 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3,
+ 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
+ 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11,
+ 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15,
+ 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19,
+ 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23,
+ 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27,
+ 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31,
+ 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35,
+ 36, 36, 36, 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39,
+ 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, 42, 43, 43, 43, 43,
+ 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47,
+ 48, 48, 48, 48, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51,
+ 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, 54, 55, 55, 55, 55,
+ 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59,
+ 60, 60, 60, 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63
+ },
+ {
+ 34, 33, 48, 59, 0, 21, 36, 18, 9, 49, 54, 38, 51, 23, 31, 5,
+ 44, 23, 37, 52, 11, 4, 58, 20, 38, 40, 38, 22, 26, 28, 42, 6,
+ 46, 16, 31, 28, 46, 14, 60, 0, 35, 53, 16, 58, 16, 29, 39, 7,
+ 1, 54, 15, 11, 48, 3, 62, 9, 58, 5, 30, 43, 17, 7, 36, 34,
+ 6, 36, 2, 14, 10, 1, 47, 47, 20, 45, 62, 56, 34, 25, 39, 18,
+ 51, 41, 61, 25, 56, 40, 41, 37, 52, 35, 30, 57, 11, 42, 37, 27,
+ 54, 19, 26, 13, 48, 31, 46, 15, 12, 10, 16, 20, 43, 17, 12, 55,
+ 45, 18, 8, 41, 7, 31, 42, 63, 12, 14, 21, 57, 24, 40, 5, 41,
+ 13, 44, 23, 59, 25, 57, 52, 50, 62, 1, 2, 49, 32, 57, 26, 43,
+ 56, 60, 55, 5, 49, 6, 3, 50, 46, 39, 27, 33, 17, 4, 53, 13,
+ 2, 19, 36, 51, 63, 0, 22, 33, 59, 28, 29, 23, 45, 33, 53, 27,
+ 22, 21, 40, 56, 4, 18, 44, 47, 28, 17, 4, 50, 21, 62, 8, 39,
+ 0, 8, 15, 24, 29, 24, 9, 11, 48, 61, 35, 55, 43, 1, 54, 42,
+ 53, 60, 22, 3, 32, 52, 25, 8, 15, 60, 7, 55, 27, 63, 19, 10,
+ 63, 24, 61, 19, 12, 38, 6, 29, 13, 37, 10, 3, 45, 32, 32, 30,
+ 49, 61, 44, 14, 20, 58, 35, 30, 2, 26, 34, 51, 9, 59, 47, 50
+ },
+ {
+ 32, 35, 32, 34, 55, 5, 6, 23, 49, 11, 6, 23, 52, 37, 29, 54,
+ 55, 40, 63, 50, 29, 52, 61, 25, 12, 56, 39, 38, 29, 11, 46, 1,
+ 40, 11, 19, 56, 7, 28, 51, 16, 15, 48, 21, 51, 60, 31, 14, 22,
+ 41, 47, 59, 56, 53, 28, 58, 26, 43, 27, 41, 33, 24, 52, 44, 38,
+ 13, 59, 48, 51, 60, 15, 3, 30, 15, 0, 10, 62, 44, 14, 28, 51,
+ 38, 2, 41, 26, 25, 49, 10, 12, 55, 57, 27, 35, 19, 33, 0, 30,
+ 5, 36, 47, 53, 5, 53, 20, 43, 34, 37, 52, 41, 21, 63, 59, 9,
+ 24, 1, 45, 24, 39, 44, 45, 16, 9, 17, 7, 50, 57, 22, 18, 28,
+ 25, 45, 2, 40, 58, 15, 17, 3, 1, 27, 61, 39, 19, 0, 19, 21,
+ 57, 62, 54, 60, 54, 40, 48, 33, 36, 37, 4, 42, 1, 43, 58, 8,
+ 13, 42, 10, 56, 35, 22, 48, 61, 63, 10, 49, 9, 24, 9, 25, 57,
+ 33, 18, 13, 31, 42, 36, 36, 55, 30, 37, 53, 34, 59, 4, 4, 23,
+ 8, 16, 58, 14, 30, 11, 12, 63, 49, 62, 2, 39, 47, 22, 2, 60,
+ 18, 8, 46, 31, 6, 20, 32, 29, 46, 42, 20, 31, 32, 61, 34, 4,
+ 47, 26, 20, 43, 26, 21, 7, 3, 16, 35, 18, 44, 27, 62, 13, 23,
+ 6, 50, 12, 8, 45, 17, 3, 46, 50, 7, 14, 5, 17, 54, 38, 0
+ },
+ {
+ 29, 56, 5, 7, 54, 48, 23, 37, 35, 44, 52, 40, 33, 49, 60, 0,
+ 59, 51, 28, 12, 41, 26, 2, 23, 34, 5, 59, 40, 3, 19, 6, 26,
+ 35, 53, 45, 49, 29, 57, 28, 62, 58, 59, 19, 53, 59, 62, 6, 54,
+ 13, 15, 48, 50, 45, 21, 41, 12, 34, 40, 24, 56, 19, 21, 35, 18,
+ 55, 45, 9, 61, 47, 61, 19, 15, 16, 39, 17, 31, 3, 51, 21, 50,
+ 17, 25, 25, 11, 44, 16, 18, 28, 14, 2, 37, 61, 58, 27, 62, 4,
+ 14, 17, 1, 9, 46, 28, 37, 0, 53, 43, 57, 7, 57, 46, 21, 41,
+ 39, 14, 52, 60, 44, 53, 49, 60, 49, 63, 13, 11, 29, 1, 55, 47,
+ 55, 12, 60, 43, 54, 37, 13, 6, 42, 10, 36, 13, 9, 8, 34, 51,
+ 31, 32, 12, 7, 57, 2, 26, 14, 3, 30, 63, 3, 32, 1, 5, 11,
+ 27, 24, 26, 44, 31, 23, 56, 38, 62, 0, 40, 30, 6, 23, 38, 2,
+ 47, 5, 15, 27, 16, 10, 31, 25, 22, 63, 30, 25, 20, 33, 32, 50,
+ 29, 43, 55, 10, 50, 45, 56, 20, 4, 7, 27, 46, 11, 16, 22, 52,
+ 35, 20, 41, 54, 46, 33, 42, 18, 63, 8, 22, 58, 36, 4, 51, 42,
+ 38, 32, 38, 22, 17, 0, 47, 8, 48, 8, 48, 1, 61, 36, 33, 20,
+ 24, 39, 39, 18, 30, 36, 9, 43, 42, 24, 10, 58, 4, 15, 34, 52
+ },
+};
+
+/*************************************************************************
+ * Offline region structures
+ *************************************************************************/
+
+/** Online group containing number of rules, values, keys and their bins
+ * for EFD_MAX_GROUP_NUM_RULES rules.
+ */
+struct efd_offline_group_rules {
+ uint32_t num_rules;
+ /**< Sum of the number of rules in all bins assigned to this group. */
+
+ efd_key_t key[EFD_MAX_GROUP_NUM_RULES];
+ /**< Array with all keys of the group. */
+ efd_value_t value[EFD_MAX_GROUP_NUM_RULES];
+ /**< Array with all values of the keys of the group. */
+
+ uint8_t bin_id[EFD_MAX_GROUP_NUM_RULES];
+ /**< Stores the bin for each correspending key to
+ * avoid having to recompute it
+ */
+};
+
+/** Offline chunk record, containing EFD_TARGET_CHUNK_NUM_RULES rules.
+ * Those rules are split into EFD_CHUNK_NUM_GROUPS groups per chunk.
+ */
+struct efd_offline_chunk_rules {
+ uint16_t num_rules;
+ /**< Number of rules in the entire chunk; used to detect unbalanced groups */
+
+ struct efd_offline_group_rules group_rules[EFD_CHUNK_NUM_GROUPS];
+ /**< Array of all groups in the chunk. */
+};
+
+/*************************************************************************
+ * Online region structures
+ *************************************************************************/
+
+/** Online group containing values for EFD_MAX_GROUP_NUM_RULES rules. */
+struct efd_online_group_entry {
+ efd_hashfunc_t hash_idx[EFD_VALUE_NUM_BITS];
+ efd_lookuptbl_t lookup_table[EFD_VALUE_NUM_BITS];
+} __attribute__((__packed__));
+
+/**
+ * A single chunk record, containing EFD_TARGET_CHUNK_NUM_RULES rules.
+ * Those rules are split into EFD_CHUNK_NUM_GROUPS groups per chunk.
+ */
+struct efd_online_chunk {
+ uint8_t bin_choice_list[(EFD_CHUNK_NUM_BINS * 2 + 7) / 8];
+ /**< This is a packed indirection index into the 'groups' array.
+ * Each byte contains four two-bit values which index into
+ * the efd_bin_to_group array.
+ * The efd_bin_to_group array returns the index into the groups array
+ */
+
+ struct efd_online_group_entry groups[EFD_CHUNK_NUM_GROUPS];
+ /**< Array of all the groups in the chunk. */
+} __attribute__((__packed__));
+
+/**
+ * EFD table structure
+ */
+struct rte_efd_table {
+ char name[RTE_EFD_NAMESIZE]; /**< Name of the efd table. */
+
+ uint32_t max_num_rules;
+ /**< Static maximum number of entries the table was constructed to hold. */
+
+ uint32_t num_rules;
+ /**< Number of entries currently in the table . */
+
+ uint32_t num_chunks;
+ /**< Number of chunks in the table needed to support num_rules. */
+
+ uint32_t num_chunks_shift;
+ /**< Bits to shift to get chunk id, instead of dividing by num_chunk. */
+
+ struct efd_online_chunk *chunks[RTE_MAX_NUMA_NODES];
+ /**< Dynamic array of size num_chunks of chunk records. */
+
+ struct efd_offline_chunk_rules *offline_chunks;
+ /**< Dynamic array of size num_chunks of key-value pairs. */
+};
+
+
+/**
+ * Compute the hash of a key given a particular seed
+ *
+ * @param key
+ * key to hash
+ * @param seed
+ * seed for the hash function
+ *
+ * @return
+ * 32-bit hash
+ */
+static inline uint32_t
+efd_hash_internal(const efd_key_t *const key, const uint32_t seed)
+{
+ return (uint32_t)(rte_hash_crc(key->bytes, EFD_KEY_LEN, seed));
+}
+
+
+/**
+ * Computes the chunk ID for a given key hash
+ *
+ * @param table
+ * EFD table to reference
+ * @param hashed_key
+ * 32-bit key hash returned by EFD_HASH
+ *
+ * @return
+ * chunk ID containing this key hash
+ */
+static inline uint32_t
+efd_get_chunk_id(const struct rte_efd_table *const table,
+ const uint32_t hashed_key)
+{
+ return hashed_key & (table->num_chunks - 1);
+}
+
+/**
+ * Computes the bin ID for a given key hash
+ *
+ * @param table
+ * EFD table to reference
+ * @param hashed_key
+ * 32-bit key hash returned by EFD_HASH
+ *
+ * @return bin ID containing this key hash
+ */
+static inline uint32_t
+efd_get_bin_id(const struct rte_efd_table *const table,
+ const uint32_t hashed_key)
+{
+ return (hashed_key >> table->num_chunks_shift) & (EFD_CHUNK_NUM_BINS - 1);
+}
+
+/**
+ * Looks up the current permutation choice for a particular bin in the online table
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to look up existing values (ideally caller's socket id)
+ * @param chunk_id
+ * Chunk ID of bin to look up
+ * @param bin_id
+ * Bin ID to look up
+ *
+ * @return
+ * Currently active permutation choice in the online table
+ */
+static inline uint8_t
+efd_get_choice(const struct rte_efd_table *const table,
+ const unsigned int socket_id,
+ const uint32_t chunk_id,
+ const uint32_t bin_id)
+{
+ struct efd_online_chunk *chunk = &table->chunks[socket_id][chunk_id];
+
+ /*
+ * Grab the chunk (byte) that contains the choices
+ * for four neighboring bins.
+ */
+ uint8_t choice_chunk =
+ chunk->bin_choice_list[bin_id / EFD_CHUNK_NUM_BIN_TO_GROUP_SETS];
+
+ /*
+ * Compute the offset into the chunk that contains
+ * the group_id lookup position
+ */
+ int offset = (bin_id & 0x3) * 2;
+
+ /* Extract from the byte just the desired lookup position */
+ return (uint8_t)((choice_chunk >> offset) & 0x3);
+}
+
+/**
+ * Compute the chunk_id and bin_id for a given key
+ *
+ * @param table
+ * EFD table to reference
+ * @param key
+ * Key to hash and find location of
+ * @param chunk_id
+ * Computed chunk ID
+ * @param bin_id
+ * Computed bin ID
+ *
+ */
+static inline void
+efd_compute_ids(const struct rte_efd_table *const table,
+ const efd_key_t *const key, uint32_t *const chunk_id,
+ uint32_t *const bin_id)
+{
+ /* Compute the position of the entry in the hash table */
+ uint32_t h = EFD_HASH(key);
+
+ /* Compute the chunk_id where that entry can be found */
+ *chunk_id = efd_get_chunk_id(table, h);
+
+ /*
+ * Compute the bin within that chunk where the entry
+ * can be found (0 - 255)
+ */
+ *bin_id = efd_get_bin_id(table, h);
+}
+
+/**
+ * Search for a hash function for a group that satisfies all group results
+ */
+static inline int
+efd_search_hash(const struct efd_offline_group_rules *const off_group,
+ struct efd_online_group_entry *const on_group)
+{
+ efd_hashfunc_t hash_idx;
+ efd_hashfunc_t start_hash_idx[EFD_VALUE_NUM_BITS];
+ efd_lookuptbl_t start_lookup_table[EFD_VALUE_NUM_BITS];
+
+ uint32_t i, j, rule_id;
+ uint32_t hash_val_a[EFD_MAX_GROUP_NUM_RULES];
+ uint32_t hash_val_b[EFD_MAX_GROUP_NUM_RULES];
+ uint32_t hash_val[EFD_MAX_GROUP_NUM_RULES];
+
+
+ rte_prefetch0(off_group->value);
+
+ /*
+ * Prepopulate the hash_val tables by running the two hash functions
+ * for each provided rule
+ */
+ for (i = 0; i < off_group->num_rules; i++) {
+ hash_val_b[i] = EFD_HASHFUNCB(&off_group->key[i]);
+ hash_val_a[i] = EFD_HASHFUNCA(&off_group->key[i]);
+ }
+
+ for (i = 0; i < EFD_VALUE_NUM_BITS; i++) {
+ hash_idx = on_group->hash_idx[i];
+ start_hash_idx[i] = hash_idx;
+
+ do {
+ efd_lookuptbl_t lookup_table = 0;
+ efd_lookuptbl_t lookup_table_complement = 0;
+
+ for (rule_id = 0; rule_id < off_group->num_rules; rule_id++)
+ hash_val[rule_id] = hash_val_a[rule_id] + (hash_idx *
+ hash_val_b[rule_id]);
+
+ /*
+ * The goal here is to find a hash function for this
+ * particular bit entry that meets the following criteria:
+ * The most significant bits of the hash result define a
+ * shift into the lookup table where the bit will be stored
+ */
+
+ /* Iterate over each provided rule */
+ for (rule_id = 0; rule_id < off_group->num_rules;
+ rule_id++) {
+ /*
+ * Use the few most significant bits (number based on
+ * EFD_LOOKUPTBL_SIZE) to see what position the
+ * expected bit should be set in the lookup_table
+ */
+ uint32_t bucket_idx = hash_val[rule_id] >>
+ EFD_LOOKUPTBL_SHIFT;
+
+ /*
+ * Get the current bit of interest.
+ * This only find an appropriate hash function
+ * for one bit at a time of the rule
+ */
+ efd_lookuptbl_t expected =
+ (off_group->value[rule_id] >> i) & 0x1;
+
+ /*
+ * Add the expected bit (if set) to a map
+ * (lookup_table). Also set its complement
+ * in lookup_table_complement
+ */
+ lookup_table |= expected << bucket_idx;
+ lookup_table_complement |= (1 - expected)
+ << bucket_idx;
+
+ /*
+ * If ever the hash function of two different
+ * elements result in different values at the
+ * same location in the lookup_table,
+ * the current hash_idx is not valid.
+ */
+ if (lookup_table & lookup_table_complement)
+ break;
+ }
+
+ /*
+ * Check if the previous loop completed without
+ * breaking early
+ */
+ if (rule_id == off_group->num_rules) {
+ /*
+ * Current hash function worked, store it
+ * for the current group
+ */
+ on_group->hash_idx[i] = hash_idx;
+ on_group->lookup_table[i] = lookup_table;
+
+ /*
+ * Make sure that the hash function has changed
+ * from the starting value
+ */
+ hash_idx = start_hash_idx[i] + 1;
+ break;
+ }
+ hash_idx++;
+
+ } while (hash_idx != start_hash_idx[i]);
+
+ /* Failed to find perfect hash for this group */
+ if (hash_idx == start_hash_idx[i]) {
+ /*
+ * Restore previous hash_idx and lookup_table
+ * for all value bits
+ */
+ for (j = 0; j < i; j++) {
+ on_group->hash_idx[j] = start_hash_idx[j];
+ on_group->lookup_table[j] = start_lookup_table[j];
+ }
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+struct rte_efd_table *
+rte_efd_create(const char *name, uint32_t max_num_rules,
+ uint8_t online_cpu_socket_bitmask,
+ uint8_t offline_cpu_socket)
+{
+ struct rte_efd_table *table = NULL;
+ uint32_t num_chunks, num_chunks_shift;
+ uint8_t socket_id;
+ struct rte_efd_list *efd_list = NULL;
+ struct rte_tailq_entry *te;
+ uint64_t offline_table_size;
+
+ efd_list = RTE_TAILQ_CAST(rte_efd_tailq.head, rte_efd_list);
+
+ if (online_cpu_socket_bitmask == 0) {
+ RTE_LOG(ERR, EFD, "At least one CPU socket must be enabled "
+ "in the bitmask\n");
+ return NULL;
+ }
+
+ if (max_num_rules == 0) {
+ RTE_LOG(ERR, EFD, "Max num rules must be higher than 0\n");
+ return NULL;
+ }
+
+ /*
+ * Compute the minimum number of chunks (smallest power of 2)
+ * that can hold all of the rules
+ */
+ if (max_num_rules % EFD_TARGET_CHUNK_NUM_RULES == 0)
+ num_chunks = rte_align32pow2(max_num_rules /
+ EFD_TARGET_CHUNK_NUM_RULES);
+ else
+ num_chunks = rte_align32pow2((max_num_rules /
+ EFD_TARGET_CHUNK_NUM_RULES) + 1);
+
+ num_chunks_shift = log2(num_chunks);
+
+ rte_rwlock_write_lock(RTE_EAL_TAILQ_RWLOCK);
+
+ /*
+ * Guarantee there's no existing: this is normally already checked
+ * by ring creation above
+ */
+ TAILQ_FOREACH(te, efd_list, next) {
+ table = (struct rte_efd_table *) te->data;
+ if (strncmp(name, table->name, RTE_EFD_NAMESIZE) == 0)
+ break;
+ }
+
+ table = NULL;
+ if (te != NULL) {
+ rte_errno = EEXIST;
+ te = NULL;
+ goto error_unlock_exit;
+ }
+
+ te = rte_zmalloc("EFD_TAILQ_ENTRY", sizeof(*te), 0);
+ if (te == NULL) {
+ RTE_LOG(ERR, EFD, "tailq entry allocation failed\n");
+ goto error_unlock_exit;
+ }
+
+ /* Create a new EFD table management structure */
+ table = (struct rte_efd_table *) rte_zmalloc_socket(NULL,
+ sizeof(struct rte_efd_table),
+ RTE_CACHE_LINE_SIZE,
+ offline_cpu_socket);
+ if (table == NULL) {
+ RTE_LOG(ERR, EFD, "Allocating EFD table management structure"
+ " on socket %u failed\n",
+ offline_cpu_socket);
+ goto error_unlock_exit;
+ }
+
+
+ RTE_LOG(DEBUG, EFD, "Allocated EFD table management structure "
+ "on socket %u\n", offline_cpu_socket);
+
+ table->max_num_rules = num_chunks * EFD_TARGET_CHUNK_NUM_RULES;
+ table->num_rules = 0;
+ table->num_chunks = num_chunks;
+ table->num_chunks_shift = num_chunks_shift;
+ snprintf(table->name, sizeof(table->name), "%s", name);
+
+ RTE_LOG(DEBUG, EFD, "Creating an EFD table with %u chunks,"
+ " which potentially supports %u entries\n",
+ num_chunks, table->max_num_rules);
+
+ /* Make sure all the allocatable table pointers are NULL initially */
+ for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++)
+ table->chunks[socket_id] = NULL;
+ table->offline_chunks = NULL;
+
+ /*
+ * Allocate one online table per socket specified
+ * in the user-supplied bitmask
+ */
+ uint64_t online_table_size = num_chunks * sizeof(struct efd_online_chunk) +
+ EFD_NUM_CHUNK_PADDING_BYTES;
+
+ for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++) {
+ if ((online_cpu_socket_bitmask >> socket_id) & 0x01) {
+ /*
+ * Allocate all of the EFD table chunks (the online portion)
+ * as a continuous block
+ */
+ table->chunks[socket_id] =
+ (struct efd_online_chunk *) rte_zmalloc_socket(NULL,
+ online_table_size,
+ RTE_CACHE_LINE_SIZE,
+ socket_id);
+ if (table->chunks[socket_id] == NULL) {
+ RTE_LOG(ERR, EFD,
+ "Allocating EFD online table on "
+ "socket %u failed\n",
+ socket_id);
+ goto error_unlock_exit;
+ }
+ RTE_LOG(DEBUG, EFD,
+ "Allocated EFD online table of size %lu bytes "
+ "(%.2f MB) on socket %u\n", online_table_size,
+ (float)online_table_size / (1024.0F * 1024.0F),
+ socket_id);
+ }
+ }
+
+ /*
+ * Allocate the EFD table offline portion (with the actual rules
+ * mapping keys to values) as a continuous block.
+ * This could be several gigabytes of memory.
+ */
+ offline_table_size = num_chunks * sizeof(struct efd_offline_chunk_rules);
+ table->offline_chunks =
+ (struct efd_offline_chunk_rules *) rte_zmalloc_socket(NULL,
+ offline_table_size,
+ RTE_CACHE_LINE_SIZE,
+ offline_cpu_socket);
+ if (table->offline_chunks == NULL) {
+ RTE_LOG(ERR, EFD, "Allocating EFD offline table on socket %u "
+ "failed\n", offline_cpu_socket);
+ goto error_unlock_exit;
+ }
+
+ RTE_LOG(DEBUG, EFD,
+ "Allocated EFD offline table of size %lu bytes "
+ " (%.2f MB) on socket %u\n", offline_table_size,
+ (float)offline_table_size / (1024.0F * 1024.0F),
+ offline_cpu_socket);
+
+ te->data = (void *) table;
+ TAILQ_INSERT_TAIL(efd_list, te, next);
+ rte_rwlock_write_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+ return table;
+
+error_unlock_exit:
+ rte_rwlock_write_unlock(RTE_EAL_TAILQ_RWLOCK);
+ rte_efd_free(table);
+
+ return NULL;
+}
+
+struct rte_efd_table *
+rte_efd_find_existing(const char *name)
+{
+ struct rte_efd_table *table = NULL;
+ struct rte_tailq_entry *te;
+ struct rte_efd_list *efd_list;
+
+ efd_list = RTE_TAILQ_CAST(rte_efd_tailq.head, rte_efd_list);
+
+ rte_rwlock_read_lock(RTE_EAL_TAILQ_RWLOCK);
+ TAILQ_FOREACH(te, efd_list, next) {
+ table = (struct rte_efd_table *) te->data;
+ if (strncmp(name, table->name, RTE_EFD_NAMESIZE) == 0)
+ break;
+ }
+ rte_rwlock_read_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+ if (te == NULL) {
+ rte_errno = ENOENT;
+ return NULL;
+ }
+ return table;
+}
+
+void
+rte_efd_free(struct rte_efd_table *table)
+{
+ uint8_t socket_id;
+
+ if (table != NULL) {
+ for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++) {
+ if (table->chunks[socket_id] != NULL)
+ rte_free(table->chunks[socket_id]);
+ }
+
+ if (table->offline_chunks != NULL)
+ rte_free(table->offline_chunks);
+
+ rte_free(table);
+ }
+}
+
+/**
+ * Applies a previously computed table entry to the specified table for all
+ * socket-local copies of the online table.
+ * Intended to apply an update for only a single change
+ * to a key/value pair at a time
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing values (ideally caller's socket id)
+ * @param chunk_id
+ * Chunk index to update
+ * @param group_id
+ * Group index to update
+ * @param bin_id
+ * Bin within the group that this update affects
+ * @param new_bin_choice
+ * Newly chosen permutation which this bin should use - only lower 2 bits
+ * @param new_group_entry
+ * Previously computed updated chunk/group entry
+ */
+static inline void
+efd_apply_update(struct rte_efd_table *const table, const unsigned int socket_id,
+ const uint32_t chunk_id, const uint32_t group_id,
+ const uint32_t bin_id, const uint8_t new_bin_choice,
+ const struct efd_online_group_entry *const new_group_entry)
+{
+ int i;
+ struct efd_online_chunk *chunk = &table->chunks[socket_id][chunk_id];
+ uint8_t bin_index = bin_id / EFD_CHUNK_NUM_BIN_TO_GROUP_SETS;
+
+ /*
+ * Grab the current byte that contains the choices
+ * for four neighboring bins
+ */
+ uint8_t choice_chunk =
+ chunk->bin_choice_list[bin_index];
+
+
+ /* Compute the offset into the chunk that needs to be updated */
+ int offset = (bin_id & 0x3) * 2;
+
+ /* Zero the two bits of interest and set them to new_bin_choice */
+ choice_chunk = (choice_chunk & (~(0x03 << offset)))
+ | ((new_bin_choice & 0x03) << offset);
+
+ /* Update the online table with the new data across all sockets */
+ for (i = 0; i < RTE_MAX_NUMA_NODES; i++) {
+ if (table->chunks[i] != NULL) {
+ memcpy(&(table->chunks[i][chunk_id].groups[group_id]),
+ new_group_entry,
+ sizeof(struct efd_online_group_entry));
+ table->chunks[i][chunk_id].bin_choice_list[bin_index] =
+ choice_chunk;
+ }
+ }
+}
+
+
+/**
+ * Computes an updated table entry where the supplied key points to a new host.
+ * If no entry exists, one is inserted.
+ *
+ * This function does NOT modify the online table(s)
+ * This function DOES modify the offline table
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing values (ideally caller's socket id)
+ * @param key
+ * Key to insert
+ * @param value
+ * Value to associate with key
+ * @param chunk_id
+ * Chunk ID of the chunk that was modified
+ * @param group_id
+ * Group ID of the group that was modified
+ * @param bin_id
+ * Bin ID that was modified
+ * @param new_bin_choice
+ * Newly chosen permutation which this bin will use
+ * @param entry
+ * Newly computed online entry to apply later with efd_apply_update
+ *
+ * @return
+ * EFD_UPDATE_WARN_GROUP_FULL
+ * Operation is insert, and the last available space in the
+ * key's group was just used. Future inserts may fail as groups fill up.
+ * This operation was still successful, and entry contains a valid update
+ * EFD_UPDATE_FAILED
+ * Either the EFD failed to find a suitable perfect hash or the group was full
+ * This is a fatal error, and the table is now in an indeterminite state
+ * EFD_UPDATE_NO_CHANGE
+ * Operation resulted in no change to the table (same value already exists)
+ * 0
+ * Insert or update was successful, and the new efd_online_group_entry
+ * is stored in *entry
+ *
+ * @warning
+ * Note that entry will be UNCHANGED if the update has no effect, and thus any
+ * subsequent use of the entry content will likely be invalid
+ */
+static inline int
+efd_compute_update(struct rte_efd_table *const table, const unsigned int socket_id,
+ const efd_key_t *const key, const efd_value_t value,
+ uint32_t *const chunk_id, uint32_t *const group_id,
+ uint32_t *const bin_id, uint8_t *const new_bin_choice,
+ struct efd_online_group_entry *const entry)
+{
+ unsigned int i;
+ int ret;
+ int status = EXIT_SUCCESS;
+ int found = 0;
+
+ efd_compute_ids(table, key, chunk_id, bin_id);
+
+ struct efd_offline_chunk_rules *const chunk =
+ &table->offline_chunks[*chunk_id];
+ struct efd_offline_group_rules *new_group;
+ struct efd_offline_group_rules current_group_copy;
+ struct efd_offline_group_rules new_group_copy;
+
+ uint8_t current_choice = efd_get_choice(table, socket_id,
+ *chunk_id, *bin_id);
+ uint32_t current_group_id = efd_bin_to_group[current_choice][*bin_id];
+ struct efd_offline_group_rules *const current_group =
+ &chunk->group_rules[current_group_id];
+ uint8_t bin_size = 0;
+
+ /* Scan the current group and see if the key is already present */
+ for (i = 0; i < current_group->num_rules; i++) {
+ if (current_group->bin_id[i] == *bin_id)
+ bin_size++;
+
+ if (found == 0 && unlikely(memcmp(¤t_group->key[i],
+ key, EFD_KEY_LEN) == 0)) {
+ /* Key is already present */
+
+ /*
+ * If previous value is same as new value,
+ * no additional work is required
+ */
+ if (current_group->value[i] == value)
+ return EFD_UPDATE_NO_CHANGE;
+
+ /* Save the original group state, if update fails */
+ memcpy(¤t_group_copy, current_group,
+ sizeof(struct efd_offline_group_rules));
+ current_group->value[i] = value;
+ found = 1;
+ }
+ }
+
+ if (found == 0) {
+ /* Key does not exist. Insert the rule into the bin/group */
+ if (unlikely(current_group->num_rules >= EFD_MAX_GROUP_NUM_RULES)) {
+ RTE_LOG(ERR, EFD,
+ "Fatal: No room remaining for insert into "
+ "chunk %u group %u bin %u\n",
+ *chunk_id,
+ current_group_id, *bin_id);
+ return EFD_UPDATE_FAILED;
+ }
+ /* Save the original group state, if update fails */
+ memcpy(¤t_group_copy, current_group,
+ sizeof(struct efd_offline_group_rules));
+
+ if (unlikely(current_group->num_rules ==
+ (EFD_MAX_GROUP_NUM_RULES - 1))) {
+ RTE_LOG(INFO, EFD, "Warn: Insert into last "
+ "available slot in chunk %u "
+ "group %u bin %u\n", *chunk_id,
+ current_group_id, *bin_id);
+ status = EFD_UPDATE_WARN_GROUP_FULL;
+ }
+ memcpy(¤t_group->key[current_group->num_rules],
+ key, EFD_KEY_LEN);
+ current_group->value[current_group->num_rules] = value;
+ current_group->bin_id[current_group->num_rules] = *bin_id;
+ table->num_rules++;
+ current_group->num_rules++;
+ bin_size++;
+ }
+
+ /* Group need to be rebalanced when it starts to get loaded */
+ if (current_group->num_rules > EFD_MIN_BALANCED_NUM_RULES) {
+
+ /*
+ * Subtract the number of entries in the bin from
+ * the original group
+ */
+ current_group->num_rules -= bin_size;
+
+ /*
+ * Figure out which of the available groups that this bin
+ * can map to is the smallest (using the current group
+ * as baseline)
+ */
+ uint8_t smallest_choice = current_choice;
+ uint8_t smallest_size = current_group->num_rules;
+ uint32_t smallest_group_id = current_group_id;
+ unsigned char choice;
+
+ for (choice = 0; choice < EFD_CHUNK_NUM_BIN_TO_GROUP_SETS;
+ choice++) {
+ uint32_t test_group_id =
+ efd_bin_to_group[choice][*bin_id];
+ uint32_t num_rules =
+ chunk->group_rules[test_group_id].num_rules;
+ if (num_rules < smallest_size) {
+ smallest_choice = choice;
+ smallest_size = num_rules;
+ smallest_group_id = test_group_id;
+ }
+ }
+
+ *new_bin_choice = smallest_choice;
+ *group_id = smallest_group_id;
+ new_group = &chunk->group_rules[smallest_group_id];
+
+ if (smallest_group_id == current_group_id) {
+ new_group->num_rules += bin_size;
+ RTE_LOG(DEBUG, EFD, "chunk %u: Left bin %u (%u entries) "
+ "in group %u (%u entries)\n", *chunk_id,
+ *bin_id, bin_size, *group_id,
+ new_group->num_rules);
+ } else {
+ /*
+ * Remove the bin from the group it was previously
+ * assigned to and add it to the new group
+ */
+
+ RTE_LOG(DEBUG, EFD, "chunk %u: Moving bin %u (%u entries) "
+ "from group %u (%u entries) to %u "
+ "(%u entries)\n", *chunk_id, *bin_id,
+ bin_size, current_group_id,
+ current_group->num_rules + bin_size,
+ smallest_group_id,
+ new_group->num_rules + bin_size);
+
+ /* Save the original group state, if update fails */
+ memcpy(&new_group_copy, new_group,
+ sizeof(struct efd_offline_group_rules));
+
+ uint8_t empty_idx = 0;
+
+ for (i = 0; i < current_group->num_rules + bin_size; i++) {
+ /*
+ * Move keys that belong to the same bin
+ * to the new group
+ */
+ if (current_group->bin_id[i] == *bin_id) {
+ new_group->key[new_group->num_rules] =
+ current_group->key[i];
+ new_group->value[new_group->num_rules] =
+ current_group->value[i];
+ new_group->bin_id[new_group->num_rules] =
+ current_group->bin_id[i];
+ new_group->num_rules++;
+ } else {
+ if (i != empty_idx) {
+ /*
+ * Need to move this key towards
+ * the top of the array
+ */
+ current_group->key[empty_idx] =
+ current_group->key[i];
+ current_group->value[empty_idx] =
+ current_group->value[i];
+ current_group->bin_id[empty_idx] =
+ current_group->bin_id[i];
+ }
+ empty_idx++;
+ }
+
+ }
+ }
+ } else {
+ *new_bin_choice = current_choice;
+ *group_id = current_group_id;
+ new_group = current_group;
+ }
+
+ /*
+ * Recompute the hash function for the modified group,
+ * and return it to the caller
+ */
+ ret = efd_search_hash(new_group, entry);
+
+ if (ret != 0) {
+ RTE_LOG(ERR, EFD,
+ "Failed to find perfect hash for group "
+ "containing %u entries\n",
+ new_group->num_rules);
+ /* Restore table to the previous state */
+ if (new_group != current_group)
+ memcpy(new_group, &new_group_copy,
+ sizeof(struct efd_offline_group_rules));
+ memcpy(current_group, ¤t_group_copy,
+ sizeof(struct efd_offline_group_rules));
+
+ return EFD_UPDATE_FAILED;
+ }
+
+ return status;
+}
+
+int
+rte_efd_update(struct rte_efd_table *const table, const unsigned int socket_id,
+ const efd_key_t *const key,
+ const efd_value_t value)
+{
+ uint32_t chunk_id, group_id, bin_id;
+ uint8_t new_bin_choice;
+ struct efd_online_group_entry entry;
+
+ int status = efd_compute_update(table, socket_id, key, value,
+ &chunk_id, &group_id, &bin_id,
+ &new_bin_choice, &entry);
+
+ if (status == EFD_UPDATE_NO_CHANGE)
+ return EXIT_SUCCESS;
+
+ if (status != EXIT_SUCCESS)
+ return status;
+
+ efd_apply_update(table, socket_id, chunk_id, group_id, bin_id,
+ new_bin_choice, &entry);
+ return status;
+}
+
+int
+rte_efd_delete(struct rte_efd_table *const table, const unsigned int socket_id,
+ const efd_key_t *const key, efd_value_t *const prev_value)
+{
+ unsigned int i;
+ uint32_t chunk_id, bin_id;
+ uint8_t not_found = 1;
+
+ efd_compute_ids(table, key, &chunk_id, &bin_id);
+
+ struct efd_offline_chunk_rules *const chunk =
+ &table->offline_chunks[chunk_id];
+
+ uint8_t current_choice = efd_get_choice(table, socket_id,
+ chunk_id, bin_id);
+ uint32_t current_group_id = efd_bin_to_group[current_choice][bin_id];
+ struct efd_offline_group_rules *const current_group =
+ &chunk->group_rules[current_group_id];
+
+ /*
+ * Search the current group for the specified key.
+ * If it exists, remove it and re-pack the other values
+ */
+ for (i = 0; i < current_group->num_rules; i++) {
+ if (not_found) {
+ /* Found key that needs to be removed */
+ if (memcmp(¤t_group->key[i], key, EFD_KEY_LEN) == 0) {
+ /* Store previous value if requested by caller */
+ if (prev_value != NULL)
+ *prev_value = current_group->value[i];
+
+ not_found = 0;
+ }
+ } else {
+ /*
+ * If the desired key has been found,
+ * need to shift other values up one
+ */
+
+ /* Need to shift this entry back up one index */
+ current_group->key[i - 1] = current_group->key[i];
+ current_group->value[i - 1] = current_group->value[i];
+ current_group->bin_id[i - 1] = current_group->bin_id[i];
+ }
+ }
+
+ if (not_found == 0) {
+ table->num_rules--;
+ current_group->num_rules--;
+ }
+
+ return not_found;
+}
+
+
+#if (EFD_VALUE_NUM_BITS == 8 || EFD_VALUE_NUM_BITS == 16 || \
+ EFD_VALUE_NUM_BITS == 24 || EFD_VALUE_NUM_BITS == 32)
+#define EFD_LOAD_SI128(val) _mm_load_si128(val)
+#else
+#define EFD_LOAD_SI128(val) _mm_lddqu_si128(val)
+#endif
+
+static inline efd_value_t
+efd_lookup_internal(const struct efd_online_group_entry *const group,
+ const uint32_t hash_val_a, const uint32_t hash_val_b)
+{
+ efd_value_t value = 0;
+
+#if EFD_VALUE_NUM_BITS > 3 && defined(RTE_MACHINE_CPUFLAG_AVX2)
+ uint32_t byte_idx;
+
+ __m256i vhash_val_a = _mm256_set1_epi32(hash_val_a);
+ __m256i vhash_val_b = _mm256_set1_epi32(hash_val_b);
+
+ for (byte_idx = 0; byte_idx < EFD_VALUE_NUM_BITS; byte_idx += 8) {
+ __m256i vhash_idx =
+ _mm256_cvtepu16_epi32(EFD_LOAD_SI128((__m128i const *)
+ &group->hash_idx[byte_idx]));
+ __m256i vlookup_table = _mm256_cvtepu16_epi32(
+ EFD_LOAD_SI128((__m128i const *)
+ &group->lookup_table[byte_idx]));
+ __m256i vhash = _mm256_add_epi32(vhash_val_a,
+ _mm256_mullo_epi32(vhash_idx, vhash_val_b));
+ __m256i vbucket_idx = _mm256_srli_epi32(vhash, EFD_LOOKUPTBL_SHIFT);
+ __m256i vresult = _mm256_srlv_epi32(vlookup_table, vbucket_idx);
+
+ value |= (_mm256_movemask_ps((__m256)_mm256_slli_epi32(vresult, 31))
+ & ((1 << (EFD_VALUE_NUM_BITS - byte_idx)) - 1)) << byte_idx;
+ }
+#else
+ uint32_t bit;
+
+ for (bit = 0; bit < EFD_VALUE_NUM_BITS; bit++) {
+ value <<= 1;
+
+ uint32_t h = hash_val_a + (hash_val_b *
+ group->hash_idx[EFD_VALUE_NUM_BITS - bit - 1]);
+ uint16_t bucket_idx = h >> EFD_LOOKUPTBL_SHIFT;
+
+ value |= (group->lookup_table[EFD_VALUE_NUM_BITS - bit - 1] >>
+ bucket_idx) & 0x1;
+ }
+#endif
+
+ return value;
+}
+
+efd_value_t
+rte_efd_lookup(const struct rte_efd_table *const table, const unsigned int socket_id,
+ const efd_key_t *const key)
+{
+ uint32_t chunk_id, group_id, bin_id;
+ uint8_t bin_choice;
+ const struct efd_online_group_entry *group;
+ const struct efd_online_chunk *const chunks = table->chunks[socket_id];
+
+ /* Determine the chunk and group location for the given key */
+ efd_compute_ids(table, key, &chunk_id, &bin_id);
+ bin_choice = efd_get_choice(table, socket_id, chunk_id, bin_id);
+ group_id = efd_bin_to_group[bin_choice][bin_id];
+ group = &chunks[chunk_id].groups[group_id];
+
+ return efd_lookup_internal(group, EFD_HASHFUNCA(key), EFD_HASHFUNCB(key));
+}
+
+void rte_efd_lookup_bulk(const struct rte_efd_table *const table,
+ const unsigned int socket_id, const int num_keys,
+ const efd_key_t *const *const key_list,
+ efd_value_t *const value_list)
+{
+ int i;
+ uint32_t chunk_id_list[EFD_BURST_MAX];
+ uint32_t bin_id_list[EFD_BURST_MAX];
+ uint8_t bin_choice_list[EFD_BURST_MAX];
+ uint32_t group_id_list[EFD_BURST_MAX];
+ struct efd_online_group_entry *group;
+
+ struct efd_online_chunk *chunks = table->chunks[socket_id];
+
+ for (i = 0; i < num_keys; i++) {
+ efd_compute_ids(table, key_list[i], &chunk_id_list[i],
+ &bin_id_list[i]);
+ rte_prefetch0(&chunks[chunk_id_list[i]].bin_choice_list);
+ }
+
+ for (i = 0; i < num_keys; i++) {
+ bin_choice_list[i] = efd_get_choice(table, socket_id,
+ chunk_id_list[i], bin_id_list[i]);
+ group_id_list[i] =
+ efd_bin_to_group[bin_choice_list[i]][bin_id_list[i]];
+ group = &chunks[chunk_id_list[i]].groups[group_id_list[i]];
+ rte_prefetch0(group);
+ }
+
+ for (i = 0; i < num_keys; i++) {
+ group = &chunks[chunk_id_list[i]].groups[group_id_list[i]];
+ value_list[i] = efd_lookup_internal(group,
+ EFD_HASHFUNCA(key_list[i]),
+ EFD_HASHFUNCB(key_list[i]));
+ }
+}
+
diff --git a/lib/librte_efd/rte_efd.h b/lib/librte_efd/rte_efd.h
new file mode 100644
index 0000000..ede5acf
--- /dev/null
+++ b/lib/librte_efd/rte_efd.h
@@ -0,0 +1,312 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __LIBEFD_H__
+#define __LIBEFD_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <rte_hash_crc.h>
+#include "rte_lcore.h"
+
+/*************************************************************************
+ * User selectable constants
+ *************************************************************************/
+
+/*
+ * If possible, best lookup performance will be achieved by ensuring that
+ * the entire table fits in the L3 cache.
+ *
+ * Some formulas for calculating various sizes are listed below:
+ *
+ * # of chunks =
+ * 2 ^ (ceiling(log2((requested # of rules) /
+ * (EFD_CHUNK_NUM_GROUPS * EFD_TARGET_GROUP_NUM_RULES))))
+ *
+ * Target # of rules = (# of chunks) * EFD_CHUNK_NUM_GROUPS *
+ * EFD_TARGET_GROUP_NUM_RULES
+ *
+ * Group Size (in bytes) = 4 (per value bit)
+ *
+ * Table size (in bytes) = EFD_VALUE_NUM_BITS * (# of chunks) *
+ * EFD_CHUNK_NUM_GROUPS * (group size)
+ */
+
+/**
+ * !!! This parameter should be adjusted for your application !!!
+ *
+ * This parameter adjusts the number of bits of value that can be
+ * stored in the table.
+ * For example, setting the number of bits to 3 will allow storing 8 values
+ * in the table (between 0 and 7).
+ *
+ * This number directly affects the performance of both lookups and insertion.
+ * In general, performance decreases as more bits are stored in the table.
+ *
+ * This number is directly proportional to the size of the online region
+ * used for lookups.
+ *
+ * Note that due to the way the CPU operates on memory, best lookup performance
+ * will be achieved when EFD_VALUE_NUM_BITS is a multiple of 8.
+ * These values align the hash indexes on 16-byte boundaries.
+ * The greatest performance drop is moving from 8->9 bits, 16->17 bits, etc.
+ *
+ * This value must be between 1 and 32
+ */
+#ifndef EFD_VALUE_NUM_BITS
+#define EFD_VALUE_NUM_BITS (8)
+#endif
+
+/**
+ * !!! This parameter should be adjusted for your application !!!
+ *
+ * This parameter adjusts the size of the keys stored in the table (in bytes).
+ * There are no restrictions around valid key sizes, and larger keys
+ * do not change the size of the online table used for lookups.
+ */
+#ifndef EFD_KEY_LEN
+#define EFD_KEY_LEN (4)
+#endif
+
+/*
+ * EFD_TARGET_GROUP_NUM_RULES:
+ * Adjusts how many groups/chunks are allocated at table creation time
+ * to support the requested number of rules. Higher values pack entries
+ * more tightly in memory, resulting in a smaller memory footprint
+ * for the online table.
+ * This comes at the cost of lower insert/update performance.
+ *
+ * EFD_MAX_GROUP_NUM_RULES:
+ * This adjusts the amount of offline memory allocated to store key/value
+ * pairs for the table. The recommended numbers are upper-bounds for
+ * this parameter
+ * - any higher and it becomes very unlikely that a perfect hash function
+ * can be found for that group size. This value should be at
+ * least 40% larger than EFD_TARGET_GROUP_NUM_RULES
+ *
+ * Recommended values for various lookuptable and hashfunc sizes are:
+ *
+ * HASH_FUNC_SIZE = 16, LOOKUPTBL_SIZE = 16:
+ * EFD_TARGET_GROUP_NUM_RULES = 22
+ * EFD_MAX_GROUP_NUM_RULES = 28
+ */
+#define EFD_TARGET_GROUP_NUM_RULES (22)
+#define EFD_MAX_GROUP_NUM_RULES (28)
+
+#define EFD_MIN_BALANCED_NUM_RULES 5
+
+/**
+ * Maximum number of keys that can be looked up in one call to efd_lookup_bulk
+ */
+#ifndef EFD_BURST_MAX
+#define EFD_BURST_MAX (32)
+#endif
+
+/** Maximum number of characters in efd name.*/
+#define RTE_EFD_NAMESIZE 32
+
+#if (EFD_VALUE_NUM_BITS > 0 && EFD_VALUE_NUM_BITS <= 8)
+typedef uint8_t efd_value_t;
+#elif (EFD_VALUE_NUM_BITS > 8 && EFD_VALUE_NUM_BITS <= 16)
+typedef uint16_t efd_value_t;
+#elif (EFD_VALUE_NUM_BITS > 16 && EFD_VALUE_NUM_BITS <= 32)
+typedef uint32_t efd_value_t;
+#else
+#error("EFD_VALUE_NUM_BITS must be in the range [1:32]")
+#endif
+
+#if (EFD_KEY_LEN <= 0)
+#error("EFD_KEY_LEN must be an integer greater than 0")
+#endif
+
+/* Variable size keys */
+typedef struct {
+ uint8_t bytes[EFD_KEY_LEN];
+} efd_key_t;
+
+/**
+ * Creates an EFD table with a single offline region and multiple per-socket
+ * internally-managed copies of the online table used for lookups
+ *
+ * @param name
+ * EFD table name
+ * @param max_num_rules
+ * Minimum number of rules the table should be sized to hold.
+ * Will be rounded up to the next smallest valid table size
+ * @param online_cpu_socket_bitmask
+ * Bitmask specifying which sockets should get a copy of the online table.
+ * LSB = socket 0, etc.
+ * @param offline_cpu_socket
+ * Identifies the socket where the offline table will be allocated
+ * (and most efficiently accessed in the case of updates/insertions)
+ *
+ * @return
+ * EFD table, or NULL if table allocation failed or the bitmask is invalid
+ */
+struct rte_efd_table *
+rte_efd_create(const char *name, uint32_t max_num_rules,
+ uint8_t online_cpu_socket_bitmask, uint8_t offline_cpu_socket);
+
+/**
+ * Releases the resources from an EFD table
+ *
+ * @param table
+ * Table to free
+ */
+void
+rte_efd_free(struct rte_efd_table *table);
+
+/**
+ * Find an existing EFD table object and return a pointer to it.
+ *
+ * @param name
+ * Name of the EFD table as passed to rte_hash_create()
+ * @return
+ * Pointer to EFD table or NULL if object not found
+ * with rte_errno set appropriately. Possible rte_errno values include:
+ * - ENOENT - value not available for return
+ */
+struct rte_efd_table*
+rte_efd_find_existing(const char *name);
+
+#define EFD_UPDATE_WARN_GROUP_FULL (1)
+#define EFD_UPDATE_NO_CHANGE (2)
+#define EFD_UPDATE_FAILED (3)
+
+/**
+ * Computes an updated table entry for the supplied key/value pair.
+ * The update is then immediately applied to the provided table and
+ * all socket-local copies of the chunks are updated.
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param key
+ * EFD table key to modify
+ * @param value
+ * Value to associate with the key
+ *
+ * @return
+ * EFD_UPDATE_WARN_GROUP_FULL
+ * Operation is insert, and the last available space in the
+ * key's group was just used
+ * Future inserts may fail as groups fill up
+ * This operation was still successful, and entry contains a valid update
+ * EFD_UPDATE_FAILED
+ * Either the EFD failed to find a suitable perfect hash or the group was full
+ * This is a fatal error, and the table is now in an indeterminite state
+ * EFD_UPDATE_NO_CHANGE
+ * Operation resulted in no change to the table (same value already exists)
+ * 0 - success
+ */
+int
+rte_efd_update(struct rte_efd_table *table, unsigned int socket_id,
+ const efd_key_t *key, efd_value_t value);
+
+/**
+ * Removes any value currently associated with the specified key from the table
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param key
+ * EFD table key to delete
+ * @param prev_value
+ * If not NULL, will store the previous value here before deleting it
+ *
+ * @return
+ * 0 - successfully found and deleted the key
+ * nonzero otherwise
+ */
+int
+rte_efd_delete(struct rte_efd_table *table, unsigned int socket_id,
+ const efd_key_t *key, efd_value_t *prev_value);
+
+/**
+ * Looks up the value associated with a key
+ *
+ * NOTE: Lookups will *always* succeed - this is a property of
+ * using a perfect hash table.
+ * If the specified key was never inserted, a pseudorandom answer will be returned.
+ * There is no way to know based on the lookup if the key was ever inserted
+ * originally, so this must be tracked elsewhere.
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param key
+ * EFD table key to look up
+ *
+ * @return
+ * Value associated with the key, or random junk if they key was never inserted
+ */
+efd_value_t
+rte_efd_lookup(const struct rte_efd_table *table, unsigned int socket_id,
+ const efd_key_t *key);
+
+/**
+ * Looks up the value associated with several keys.
+ *
+ * NOTE: Lookups will *always* succeed - this is a property of
+ * using a perfect hash table.
+ * If the specified key was never inserted, a pseudorandom answer will be returned.
+ * There is no way to know based on the lookup if the key was ever inserted
+ * originally, so this must be tracked elsewhere.
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param num_keys
+ * Number of keys in the key_list array, must be less than EFD_BURST_MAX
+ * @param key_list
+ * Array of num_keys pointers which point to keys to look up
+ * @param value_list
+ * Array of size num_keys where lookup values will be stored
+ */
+void
+rte_efd_lookup_bulk(const struct rte_efd_table *table, unsigned int socket_id,
+ int num_keys, const efd_key_t *const *key_list,
+ efd_value_t *value_list);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LIBEFD_H__ */
+
diff --git a/lib/librte_efd/rte_efd_version.map b/lib/librte_efd/rte_efd_version.map
new file mode 100644
index 0000000..91810b3
--- /dev/null
+++ b/lib/librte_efd/rte_efd_version.map
@@ -0,0 +1,12 @@
+DPDK_17.02 {
+ global:
+
+ rte_efd_create;
+ rte_efd_delete;
+ rte_efd_free;
+ rte_efd_lookup;
+ rte_efd_lookup_bulk;
+ rte_efd_update;
+
+ local: *;
+};
diff --git a/mk/rte.app.mk b/mk/rte.app.mk
index f75f0e2..3956849 100644
--- a/mk/rte.app.mk
+++ b/mk/rte.app.mk
@@ -86,6 +86,7 @@ _LDLIBS-y += --whole-archive
_LDLIBS-$(CONFIG_RTE_LIBRTE_TIMER) += -lrte_timer
_LDLIBS-$(CONFIG_RTE_LIBRTE_HASH) += -lrte_hash
+_LDLIBS-$(CONFIG_RTE_LIBRTE_EFD) += -lrte_efd
_LDLIBS-$(CONFIG_RTE_LIBRTE_VHOST) += -lrte_vhost
_LDLIBS-$(CONFIG_RTE_LIBRTE_KVARGS) += -lrte_kvargs
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH 2/2] examples/flow_distributor: sample app to demonstrate EFD usage
2016-12-02 14:52 [dpdk-dev] [PATCH 0/2] Elastic Flow Distributor Pablo de Lara
2016-12-02 14:52 ` [dpdk-dev] [PATCH 1/2] efd: new Elastic Flow Distributor library Pablo de Lara
@ 2016-12-02 14:52 ` Pablo de Lara
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 0/5] Elastic Flow Distributor Pablo de Lara
2 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2016-12-02 14:52 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara
This new sample app, based on the client/server sample app,
shows the user an scenario using the EFD library.
It consists of:
- A front-end server which has an EFD table that stores the
node id for each flow key, which will distribute the incoming
packets to the different nodes
- A back-end node, which has a hash table where node checks,
after reading packets coming from the server, whether the packet
is meant to be used in such node, in which case it will be TXed,
or not, in which case, packet will be dropped.
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
---
examples/Makefile | 1 +
examples/flow_distributor/Makefile | 44 +++
examples/flow_distributor/distributor/Makefile | 57 ++++
examples/flow_distributor/distributor/args.c | 200 ++++++++++++
examples/flow_distributor/distributor/args.h | 39 +++
examples/flow_distributor/distributor/init.c | 371 ++++++++++++++++++++++
examples/flow_distributor/distributor/init.h | 76 +++++
examples/flow_distributor/distributor/main.c | 362 +++++++++++++++++++++
examples/flow_distributor/node/Makefile | 48 +++
examples/flow_distributor/node/node.c | 417 +++++++++++++++++++++++++
examples/flow_distributor/shared/common.h | 99 ++++++
11 files changed, 1714 insertions(+)
create mode 100644 examples/flow_distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/args.c
create mode 100644 examples/flow_distributor/distributor/args.h
create mode 100644 examples/flow_distributor/distributor/init.c
create mode 100644 examples/flow_distributor/distributor/init.h
create mode 100644 examples/flow_distributor/distributor/main.c
create mode 100644 examples/flow_distributor/node/Makefile
create mode 100644 examples/flow_distributor/node/node.c
create mode 100644 examples/flow_distributor/shared/common.h
diff --git a/examples/Makefile b/examples/Makefile
index d49c7f2..b404982 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -45,6 +45,7 @@ DIRS-y += dpdk_qat
endif
DIRS-y += ethtool
DIRS-y += exception_path
+DIRS-$(CONFIG_RTE_LIBRTE_EFD) += flow_distributor
DIRS-y += helloworld
DIRS-$(CONFIG_RTE_LIBRTE_PIPELINE) += ip_pipeline
ifeq ($(CONFIG_RTE_LIBRTE_LPM),y)
diff --git a/examples/flow_distributor/Makefile b/examples/flow_distributor/Makefile
new file mode 100644
index 0000000..d085e49
--- /dev/null
+++ b/examples/flow_distributor/Makefile
@@ -0,0 +1,44 @@
+# BSD LICENSE
+#
+# Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overridden by command line or environment
+RTE_TARGET ?= x86_64-native-linuxapp-gcc
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+DIRS-$(CONFIG_RTE_EXEC_ENV_LINUXAPP) += distributor
+DIRS-$(CONFIG_RTE_EXEC_ENV_LINUXAPP) += node
+
+include $(RTE_SDK)/mk/rte.extsubdir.mk
diff --git a/examples/flow_distributor/distributor/Makefile b/examples/flow_distributor/distributor/Makefile
new file mode 100644
index 0000000..5a709ab
--- /dev/null
+++ b/examples/flow_distributor/distributor/Makefile
@@ -0,0 +1,57 @@
+# BSD LICENSE
+#
+# Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overridden by command line or environment
+RTE_TARGET ?= x86_64-native-linuxapp-gcc
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+ifneq ($(CONFIG_RTE_EXEC_ENV), "linuxapp")
+$(error This application can only operate in a linuxapp environment, \
+please change the definition of the RTE_TARGET environment variable)
+endif
+
+# binary name
+APP = distributor
+
+# all source are stored in SRCS-y
+SRCS-y := main.c init.c args.c
+
+INC := $(wildcard *.h)
+
+CFLAGS += $(WERROR_FLAGS) -O3
+CFLAGS += -I$(SRCDIR)/../shared
+
+include $(RTE_SDK)/mk/rte.extapp.mk
diff --git a/examples/flow_distributor/distributor/args.c b/examples/flow_distributor/distributor/args.c
new file mode 100644
index 0000000..3bd1ad6
--- /dev/null
+++ b/examples/flow_distributor/distributor/args.c
@@ -0,0 +1,200 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <getopt.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include <rte_memory.h>
+#include <rte_string_fns.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+/* 1M flows by default */
+#define DEFAULT_NUM_FLOWS 0x100000
+
+/* global var for number of nodes - extern in header */
+uint8_t num_nodes;
+/* global var for number of flows - extern in header */
+uint32_t num_flows = DEFAULT_NUM_FLOWS;
+
+static const char *progname;
+
+/**
+ * Prints out usage information to stdout
+ */
+static void
+usage(void)
+{
+ printf("%s [EAL options] -- -p PORTMASK -n NUM_NODES -f NUM_FLOWS\n"
+ " -p PORTMASK: hexadecimal bitmask of ports to use\n"
+ " -n NUM_NODES: number of node processes to use\n"
+ " -f NUM_FLOWS: number of flows to be added in the EFD table\n",
+ progname);
+}
+
+/**
+ * The ports to be used by the application are passed in
+ * the form of a bitmask. This function parses the bitmask
+ * and places the port numbers to be used into the port[]
+ * array variable
+ */
+static int
+parse_portmask(uint8_t max_ports, const char *portmask)
+{
+ char *end = NULL;
+ unsigned long pm;
+ uint8_t count = 0;
+
+ if (portmask == NULL || *portmask == '\0')
+ return -1;
+
+ /* convert parameter to a number and verify */
+ pm = strtoul(portmask, &end, 16);
+ if (end == NULL || *end != '\0' || pm == 0)
+ return -1;
+
+ /* loop through bits of the mask and mark ports */
+ while (pm != 0) {
+ if (pm & 0x01) { /* bit is set in mask, use port */
+ if (count >= max_ports)
+ printf("WARNING: requested port %u not present"
+ " - ignoring\n", (unsigned int)count);
+ else
+ info->id[info->num_ports++] = count;
+ }
+ pm = (pm >> 1);
+ count++;
+ }
+
+ return 0;
+}
+
+/**
+ * Take the number of nodes parameter passed to the app
+ * and convert to a number to store in the num_nodes variable
+ */
+static int
+parse_num_nodes(const char *nodes)
+{
+ char *end = NULL;
+ unsigned long temp;
+
+ if (nodes == NULL || *nodes == '\0')
+ return -1;
+
+ temp = strtoul(nodes, &end, 10);
+ if (end == NULL || *end != '\0' || temp == 0)
+ return -1;
+
+ num_nodes = (uint8_t)temp;
+ return 0;
+}
+
+static int
+parse_num_flows(const char *flows)
+{
+ char *end = NULL;
+
+ /* parse hexadecimal string */
+ num_flows = strtoul(flows, &end, 16);
+ if ((flows[0] == '\0') || (end == NULL) || (*end != '\0'))
+ return -1;
+
+ if (num_flows == 0)
+ return -1;
+
+ return 0;
+}
+
+/**
+ * The application specific arguments follow the DPDK-specific
+ * arguments which are stripped by the DPDK init. This function
+ * processes these application arguments, printing usage info
+ * on error.
+ */
+int
+parse_app_args(uint8_t max_ports, int argc, char *argv[])
+{
+ int option_index, opt;
+ char **argvopt = argv;
+ static struct option lgopts[] = { /* no long options */
+ {NULL, 0, 0, 0 }
+ };
+ progname = argv[0];
+
+ while ((opt = getopt_long(argc, argvopt, "n:f:p:", lgopts,
+ &option_index)) != EOF) {
+ switch (opt) {
+ case 'p':
+ if (parse_portmask(max_ports, optarg) != 0) {
+ usage();
+ return -1;
+ }
+ break;
+ case 'n':
+ if (parse_num_nodes(optarg) != 0) {
+ usage();
+ return -1;
+ }
+ break;
+ case 'f':
+ if (parse_num_flows(optarg) != 0) {
+ usage();
+ return -1;
+ }
+ break;
+ default:
+ printf("ERROR: Unknown option '%c'\n", opt);
+ usage();
+ return -1;
+ }
+ }
+
+ if (info->num_ports == 0 || num_nodes == 0) {
+ usage();
+ return -1;
+ }
+
+ if (info->num_ports % 2 != 0) {
+ printf("ERROR: application requires an even "
+ "number of ports to use\n");
+ return -1;
+ }
+ return 0;
+}
diff --git a/examples/flow_distributor/distributor/args.h b/examples/flow_distributor/distributor/args.h
new file mode 100644
index 0000000..8b36148
--- /dev/null
+++ b/examples/flow_distributor/distributor/args.h
@@ -0,0 +1,39 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _ARGS_H_
+#define _ARGS_H_
+
+int parse_app_args(uint8_t max_ports, int argc, char *argv[]);
+
+#endif /* ifndef _ARGS_H_ */
diff --git a/examples/flow_distributor/distributor/init.c b/examples/flow_distributor/distributor/init.c
new file mode 100644
index 0000000..dff466c
--- /dev/null
+++ b/examples/flow_distributor/distributor/init.c
@@ -0,0 +1,371 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/queue.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <inttypes.h>
+
+#include <rte_common.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_byteorder.h>
+#include <rte_atomic.h>
+#include <rte_launch.h>
+#include <rte_per_lcore.h>
+#include <rte_lcore.h>
+#include <rte_branch_prediction.h>
+#include <rte_debug.h>
+#include <rte_ring.h>
+#include <rte_log.h>
+#include <rte_mempool.h>
+#include <rte_memcpy.h>
+#include <rte_mbuf.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ether.h>
+#include <rte_ethdev.h>
+#include <rte_malloc.h>
+#include <rte_string_fns.h>
+#include <rte_cycles.h>
+#include <rte_efd.h>
+#include <rte_hash.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+#define MBUFS_PER_NODE 1536
+#define MBUFS_PER_PORT 1536
+#define MBUF_CACHE_SIZE 512
+
+#define RTE_MP_RX_DESC_DEFAULT 512
+#define RTE_MP_TX_DESC_DEFAULT 512
+#define NODE_QUEUE_RINGSIZE 128
+
+#define NO_FLAGS 0
+
+/* The mbuf pool for packet rx */
+struct rte_mempool *pktmbuf_pool;
+
+/* array of info/queues for nodes */
+struct node *nodes;
+
+/* Flow distributor table */
+struct rte_efd_table *efd_table;
+
+/* Shared info between distributor and nodes */
+struct shared_info *info;
+
+/**
+ * Initialise the mbuf pool for packet reception for the NIC, and any other
+ * buffer pools needed by the app - currently none.
+ */
+static int
+init_mbuf_pools(void)
+{
+ const unsigned int num_mbufs = (num_nodes * MBUFS_PER_NODE) +
+ (info->num_ports * MBUFS_PER_PORT);
+
+ /*
+ * Don't pass single-producer/single-consumer flags to mbuf create as it
+ * seems faster to use a cache instead
+ */
+ printf("Creating mbuf pool '%s' [%u mbufs] ...\n",
+ PKTMBUF_POOL_NAME, num_mbufs);
+ pktmbuf_pool = rte_pktmbuf_pool_create(PKTMBUF_POOL_NAME, num_mbufs,
+ MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
+
+ return pktmbuf_pool == NULL; /* 0 on success */
+}
+
+/**
+ * Initialise an individual port:
+ * - configure number of rx and tx rings
+ * - set up each rx ring, to pull from the main mbuf pool
+ * - set up each tx ring
+ * - start the port and report its status to stdout
+ */
+static int
+init_port(uint8_t port_num)
+{
+ /* for port configuration all features are off by default */
+ const struct rte_eth_conf port_conf = {
+ .rxmode = {
+ .mq_mode = ETH_MQ_RX_RSS
+ }
+ };
+ const uint16_t rx_rings = 1, tx_rings = num_nodes;
+ const uint16_t rx_ring_size = RTE_MP_RX_DESC_DEFAULT;
+ const uint16_t tx_ring_size = RTE_MP_TX_DESC_DEFAULT;
+
+ uint16_t q;
+ int retval;
+
+ printf("Port %u init ... ", (unsigned int)port_num);
+ fflush(stdout);
+
+ /*
+ * Standard DPDK port initialisation - config port, then set up
+ * rx and tx rings.
+ */
+ retval = rte_eth_dev_configure(port_num, rx_rings, tx_rings, &port_conf);
+ if (retval != 0)
+ return retval;
+
+ for (q = 0; q < rx_rings; q++) {
+ retval = rte_eth_rx_queue_setup(port_num, q, rx_ring_size,
+ rte_eth_dev_socket_id(port_num),
+ NULL, pktmbuf_pool);
+ if (retval < 0)
+ return retval;
+ }
+
+ for (q = 0; q < tx_rings; q++) {
+ retval = rte_eth_tx_queue_setup(port_num, q, tx_ring_size,
+ rte_eth_dev_socket_id(port_num),
+ NULL);
+ if (retval < 0)
+ return retval;
+ }
+
+ rte_eth_promiscuous_enable(port_num);
+
+ retval = rte_eth_dev_start(port_num);
+ if (retval < 0)
+ return retval;
+
+ printf("done:\n");
+
+ return 0;
+}
+
+/**
+ * Set up the DPDK rings which will be used to pass packets, via
+ * pointers, between the multi-process distributor and node processes.
+ * Each node needs one RX queue.
+ */
+static int
+init_shm_rings(void)
+{
+ unsigned int i;
+ unsigned int socket_id;
+ const char *q_name;
+ const unsigned int ringsize = NODE_QUEUE_RINGSIZE;
+
+ nodes = rte_malloc("node details",
+ sizeof(*nodes) * num_nodes, 0);
+ if (nodes == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot allocate memory for "
+ "node program details\n");
+
+ for (i = 0; i < num_nodes; i++) {
+ /* Create an RX queue for each node */
+ socket_id = rte_socket_id();
+ q_name = get_rx_queue_name(i);
+ nodes[i].rx_q = rte_ring_create(q_name,
+ ringsize, socket_id,
+ RING_F_SP_ENQ | RING_F_SC_DEQ);
+ if (nodes[i].rx_q == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot create rx ring queue "
+ "for node %u\n", i);
+ }
+ return 0;
+}
+
+/*
+ * Create flow distributor table which will contain all the flows
+ * that will be distributed among the nodes
+ */
+static void
+create_flow_distributor_table(void)
+{
+ uint8_t socket_id = rte_socket_id();
+
+ /* create table */
+ efd_table = rte_efd_create("flow table", num_flows * 2, 1 << socket_id,
+ socket_id);
+
+ if (efd_table == NULL)
+ rte_exit(EXIT_FAILURE, "Problem creating the flow table\n");
+}
+
+static void
+populate_flow_distributor_table(void)
+{
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint8_t socket_id = rte_socket_id();
+ uint64_t node_id;
+
+ /* Add flows in tables */
+ for (i = 0; i < num_flows; i++) {
+ node_id = i % num_nodes;
+
+ ip_dst = rte_cpu_to_be_32(i);
+ ret = rte_efd_update(efd_table, socket_id,
+ (efd_key_t *)&ip_dst, (efd_value_t)node_id);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u in "
+ "flow distributor table\n", i);
+ }
+
+ printf("EFD table: Adding 0x%x keys\n", num_flows);
+}
+
+/* Check the link status of all ports in up to 9s, and print them finally */
+static void
+check_all_ports_link_status(uint8_t port_num, uint32_t port_mask)
+{
+#define CHECK_INTERVAL 100 /* 100ms */
+#define MAX_CHECK_TIME 90 /* 9s (90 * 100ms) in total */
+ uint8_t portid, count, all_ports_up, print_flag = 0;
+ struct rte_eth_link link;
+
+ printf("\nChecking link status");
+ fflush(stdout);
+ for (count = 0; count <= MAX_CHECK_TIME; count++) {
+ all_ports_up = 1;
+ for (portid = 0; portid < port_num; portid++) {
+ if ((port_mask & (1 << info->id[portid])) == 0)
+ continue;
+ memset(&link, 0, sizeof(link));
+ rte_eth_link_get_nowait(info->id[portid], &link);
+ /* print link status if flag set */
+ if (print_flag == 1) {
+ if (link.link_status)
+ printf("Port %d Link Up - speed %u "
+ "Mbps - %s\n", info->id[portid],
+ (unsigned int)link.link_speed,
+ (link.link_duplex == ETH_LINK_FULL_DUPLEX) ?
+ ("full-duplex") : ("half-duplex\n"));
+ else
+ printf("Port %d Link Down\n",
+ (uint8_t)info->id[portid]);
+ continue;
+ }
+ /* clear all_ports_up flag if any link down */
+ if (link.link_status == ETH_LINK_DOWN) {
+ all_ports_up = 0;
+ break;
+ }
+ }
+ /* after finally printing all link status, get out */
+ if (print_flag == 1)
+ break;
+
+ if (all_ports_up == 0) {
+ printf(".");
+ fflush(stdout);
+ rte_delay_ms(CHECK_INTERVAL);
+ }
+
+ /* set the print_flag if all ports up or timeout */
+ if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
+ print_flag = 1;
+ printf("done\n");
+ }
+ }
+}
+
+/**
+ * Main init function for the multi-process distributor app,
+ * calls subfunctions to do each stage of the initialisation.
+ */
+int
+init(int argc, char *argv[])
+{
+ int retval;
+ const struct rte_memzone *mz;
+ uint8_t i, total_ports;
+
+ /* init EAL, parsing EAL args */
+ retval = rte_eal_init(argc, argv);
+ if (retval < 0)
+ return -1;
+ argc -= retval;
+ argv += retval;
+
+ /* get total number of ports */
+ total_ports = rte_eth_dev_count();
+
+ /* set up array for port data */
+ mz = rte_memzone_reserve(MZ_SHARED_INFO, sizeof(*info),
+ rte_socket_id(), NO_FLAGS);
+ if (mz == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot reserve memory zone "
+ "for port information\n");
+ memset(mz->addr, 0, sizeof(*info));
+ info = mz->addr;
+
+ /* parse additional, application arguments */
+ retval = parse_app_args(total_ports, argc, argv);
+ if (retval != 0)
+ return -1;
+
+ /* initialise mbuf pools */
+ retval = init_mbuf_pools();
+ if (retval != 0)
+ rte_exit(EXIT_FAILURE, "Cannot create needed mbuf pools\n");
+
+ /* now initialise the ports we will use */
+ for (i = 0; i < info->num_ports; i++) {
+ retval = init_port(info->id[i]);
+ if (retval != 0)
+ rte_exit(EXIT_FAILURE, "Cannot initialise port %u\n",
+ (unsigned int) i);
+ }
+
+ check_all_ports_link_status(info->num_ports, (~0x0));
+
+ /* initialise the node queues/rings for inter-eu comms */
+ init_shm_rings();
+
+ /* Create the flow distributor table */
+ create_flow_distributor_table();
+
+ /* Populate the flow distributor table */
+ populate_flow_distributor_table();
+
+ /* Share the total number of nodes */
+ info->num_nodes = num_nodes;
+
+ /* Share the total number of flows */
+ info->num_flows = num_flows;
+ return 0;
+}
diff --git a/examples/flow_distributor/distributor/init.h b/examples/flow_distributor/distributor/init.h
new file mode 100644
index 0000000..ea0c0d4
--- /dev/null
+++ b/examples/flow_distributor/distributor/init.h
@@ -0,0 +1,76 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _INIT_H_
+#define _INIT_H_
+
+/*
+ * #include <rte_ring.h>
+ * #include "args.h"
+ */
+
+/*
+ * Define a node structure with all needed info, including
+ * stats from the nodes.
+ */
+struct node {
+ struct rte_ring *rx_q;
+ unsigned int node_id;
+ /* these stats hold how many packets the node will actually receive,
+ * and how many packets were dropped because the node's queue was full.
+ * The port-info stats, in contrast, record how many packets were received
+ * or transmitted on an actual NIC port.
+ */
+ struct {
+ uint64_t rx;
+ uint64_t rx_drop;
+ } stats;
+};
+
+extern struct rte_efd_table *efd_table;
+extern struct node *nodes;
+
+/*
+ * shared information between distributor and nodes: number of clients,
+ * port numbers, rx and tx stats etc.
+ */
+extern struct shared_info *info;
+
+extern struct rte_mempool *pktmbuf_pool;
+extern uint8_t num_nodes;
+extern unsigned int num_sockets;
+extern uint32_t num_flows;
+
+int init(int argc, char *argv[]);
+
+#endif /* ifndef _INIT_H_ */
diff --git a/examples/flow_distributor/distributor/main.c b/examples/flow_distributor/distributor/main.c
new file mode 100644
index 0000000..efe6960
--- /dev/null
+++ b/examples/flow_distributor/distributor/main.c
@@ -0,0 +1,362 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <inttypes.h>
+#include <sys/queue.h>
+#include <errno.h>
+#include <netinet/ip.h>
+
+#include <rte_common.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_byteorder.h>
+#include <rte_launch.h>
+#include <rte_per_lcore.h>
+#include <rte_lcore.h>
+#include <rte_branch_prediction.h>
+#include <rte_atomic.h>
+#include <rte_ring.h>
+#include <rte_log.h>
+#include <rte_debug.h>
+#include <rte_mempool.h>
+#include <rte_memcpy.h>
+#include <rte_mbuf.h>
+#include <rte_ether.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ethdev.h>
+#include <rte_byteorder.h>
+#include <rte_malloc.h>
+#include <rte_string_fns.h>
+#include <rte_efd.h>
+#include <rte_ip.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+/*
+ * When doing reads from the NIC or the node queues,
+ * use this batch size
+ */
+#define PACKET_READ_SIZE 32
+
+/*
+ * Local buffers to put packets in, used to send packets in bursts to the
+ * nodes
+ */
+struct node_rx_buf {
+ struct rte_mbuf *buffer[PACKET_READ_SIZE];
+ uint16_t count;
+};
+
+struct flow_distributor_stats {
+ uint64_t distributed;
+ uint64_t drop;
+} flow_dist_stats;
+
+/* One buffer per node rx queue - dynamically allocate array */
+static struct node_rx_buf *cl_rx_buf;
+
+static const char *
+get_printable_mac_addr(uint8_t port)
+{
+ static const char err_address[] = "00:00:00:00:00:00";
+ static char addresses[RTE_MAX_ETHPORTS][sizeof(err_address)];
+ struct ether_addr mac;
+
+ if (unlikely(port >= RTE_MAX_ETHPORTS))
+ return err_address;
+ if (unlikely(addresses[port][0] == '\0')) {
+ rte_eth_macaddr_get(port, &mac);
+ snprintf(addresses[port], sizeof(addresses[port]),
+ "%02x:%02x:%02x:%02x:%02x:%02x\n",
+ mac.addr_bytes[0], mac.addr_bytes[1],
+ mac.addr_bytes[2], mac.addr_bytes[3],
+ mac.addr_bytes[4], mac.addr_bytes[5]);
+ }
+ return addresses[port];
+}
+
+/*
+ * This function displays the recorded statistics for each port
+ * and for each node. It uses ANSI terminal codes to clear
+ * screen when called. It is called from a single non-master
+ * thread in the distributor process, when the process is run with more
+ * than one lcore enabled.
+ */
+static void
+do_stats_display(void)
+{
+ unsigned int i, j;
+ const char clr[] = {27, '[', '2', 'J', '\0'};
+ const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
+ uint64_t port_tx[RTE_MAX_ETHPORTS], port_tx_drop[RTE_MAX_ETHPORTS];
+ uint64_t node_tx[MAX_NODES], node_tx_drop[MAX_NODES];
+
+ /* to get TX stats, we need to do some summing calculations */
+ memset(port_tx, 0, sizeof(port_tx));
+ memset(port_tx_drop, 0, sizeof(port_tx_drop));
+ memset(node_tx, 0, sizeof(node_tx));
+ memset(node_tx_drop, 0, sizeof(node_tx_drop));
+
+ for (i = 0; i < num_nodes; i++) {
+ const struct tx_stats *tx = &info->tx_stats[i];
+
+ for (j = 0; j < info->num_ports; j++) {
+ const uint64_t tx_val = tx->tx[info->id[j]];
+ const uint64_t drop_val = tx->tx_drop[info->id[j]];
+
+ port_tx[j] += tx_val;
+ port_tx_drop[j] += drop_val;
+ node_tx[i] += tx_val;
+ node_tx_drop[i] += drop_val;
+ }
+ }
+
+ /* Clear screen and move to top left */
+ printf("%s%s", clr, topLeft);
+
+ printf("PORTS\n");
+ printf("-----\n");
+ for (i = 0; i < info->num_ports; i++)
+ printf("Port %u: '%s'\t", (unsigned int)info->id[i],
+ get_printable_mac_addr(info->id[i]));
+ printf("\n\n");
+ for (i = 0; i < info->num_ports; i++) {
+ printf("Port %u - rx: %9"PRIu64"\t"
+ "tx: %9"PRIu64"\n",
+ (unsigned int)info->id[i], info->rx_stats.rx[i],
+ port_tx[i]);
+ }
+
+ printf("\nFLOW DISTRIBUTOR\n");
+ printf("-----\n");
+ printf("distributed: %9"PRIu64", drop: %9"PRIu64"\n",
+ flow_dist_stats.distributed, flow_dist_stats.drop);
+
+ printf("\nNODES\n");
+ printf("-------\n");
+ for (i = 0; i < num_nodes; i++) {
+ const unsigned long long rx = nodes[i].stats.rx;
+ const unsigned long long rx_drop = nodes[i].stats.rx_drop;
+ const struct filter_stats *filter = &info->filter_stats[i];
+
+ printf("Node %2u - rx: %9llu, rx_drop: %9llu\n"
+ " tx: %9"PRIu64", tx_drop: %9"PRIu64"\n"
+ " filter_passed: %9"PRIu64", "
+ "filter_drop: %9"PRIu64"\n",
+ i, rx, rx_drop, node_tx[i], node_tx_drop[i],
+ filter->passed, filter->drop);
+ }
+
+ printf("\n");
+}
+
+/*
+ * The function called from each non-master lcore used by the process.
+ * The test_and_set function is used to randomly pick a single lcore on which
+ * the code to display the statistics will run. Otherwise, the code just
+ * repeatedly sleeps.
+ */
+static int
+sleep_lcore(__attribute__((unused)) void *dummy)
+{
+ /* Used to pick a display thread - static, so zero-initialised */
+ static rte_atomic32_t display_stats;
+
+ /* Only one core should display stats */
+ if (rte_atomic32_test_and_set(&display_stats)) {
+ const unsigned int sleeptime = 1;
+
+ printf("Core %u displaying statistics\n", rte_lcore_id());
+
+ /* Longer initial pause so above printf is seen */
+ sleep(sleeptime * 3);
+
+ /* Loop forever: sleep always returns 0 or <= param */
+ while (sleep(sleeptime) <= sleeptime)
+ do_stats_display();
+ }
+ return 0;
+}
+
+/*
+ * Function to set all the node statistic values to zero.
+ * Called at program startup.
+ */
+static void
+clear_stats(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_nodes; i++)
+ nodes[i].stats.rx = nodes[i].stats.rx_drop = 0;
+}
+
+/*
+ * send a burst of traffic to a node, assuming there are packets
+ * available to be sent to this node
+ */
+static void
+flush_rx_queue(uint16_t node)
+{
+ uint16_t j;
+ struct node *cl;
+
+ if (cl_rx_buf[node].count == 0)
+ return;
+
+ cl = &nodes[node];
+ if (rte_ring_enqueue_bulk(cl->rx_q, (void **)cl_rx_buf[node].buffer,
+ cl_rx_buf[node].count) != 0){
+ for (j = 0; j < cl_rx_buf[node].count; j++)
+ rte_pktmbuf_free(cl_rx_buf[node].buffer[j]);
+ cl->stats.rx_drop += cl_rx_buf[node].count;
+ } else
+ cl->stats.rx += cl_rx_buf[node].count;
+
+ cl_rx_buf[node].count = 0;
+}
+
+/*
+ * marks a packet down to be sent to a particular node process
+ */
+static inline void
+enqueue_rx_packet(uint8_t node, struct rte_mbuf *buf)
+{
+ cl_rx_buf[node].buffer[cl_rx_buf[node].count++] = buf;
+}
+
+/*
+ * This function takes a group of packets and routes them
+ * individually to the node process. Very simply round-robins the packets
+ * without checking any of the packet contents.
+ */
+static void
+process_packets(uint32_t port_num __rte_unused, struct rte_mbuf *pkts[],
+ uint16_t rx_count, unsigned int socket_id)
+{
+ uint16_t i;
+ uint8_t node;
+ efd_value_t data[EFD_BURST_MAX];
+ const efd_key_t *key_ptrs[EFD_BURST_MAX];
+
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[EFD_BURST_MAX];
+
+ for (i = 0; i < rx_count; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(pkts[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = (efd_key_t *)&ipv4_dst_ip[i];
+ }
+
+ rte_efd_lookup_bulk(efd_table, socket_id, rx_count,
+ (const efd_key_t **) key_ptrs, data);
+ for (i = 0; i < rx_count; i++) {
+ node = (uint8_t) ((uintptr_t)data[i]);
+
+ if (node >= num_nodes) {
+ /*
+ * Node is out of range, which means that
+ * flow has not been inserted
+ */
+ flow_dist_stats.drop++;
+ rte_pktmbuf_free(pkts[i]);
+ } else {
+ flow_dist_stats.distributed++;
+ enqueue_rx_packet(node, pkts[i]);
+ }
+ }
+
+ for (i = 0; i < num_nodes; i++)
+ flush_rx_queue(i);
+}
+
+/*
+ * Function called by the master lcore of the DPDK process.
+ */
+static void
+do_packet_forwarding(void)
+{
+ unsigned int port_num = 0; /* indexes the port[] array */
+ unsigned int socket_id = rte_socket_id();
+
+ for (;;) {
+ struct rte_mbuf *buf[PACKET_READ_SIZE];
+ uint16_t rx_count;
+
+ /* read a port */
+ rx_count = rte_eth_rx_burst(info->id[port_num], 0,
+ buf, PACKET_READ_SIZE);
+ info->rx_stats.rx[port_num] += rx_count;
+
+ /* Now process the NIC packets read */
+ if (likely(rx_count > 0))
+ process_packets(port_num, buf, rx_count, socket_id);
+
+ /* move to next port */
+ if (++port_num == info->num_ports)
+ port_num = 0;
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ /* initialise the system */
+ if (init(argc, argv) < 0)
+ return -1;
+ RTE_LOG(INFO, APP, "Finished Process Init.\n");
+
+ cl_rx_buf = calloc(num_nodes, sizeof(cl_rx_buf[0]));
+
+ /* clear statistics */
+ clear_stats();
+
+ /* put all other cores to sleep bar master */
+ rte_eal_mp_remote_launch(sleep_lcore, NULL, SKIP_MASTER);
+
+ do_packet_forwarding();
+ return 0;
+}
diff --git a/examples/flow_distributor/node/Makefile b/examples/flow_distributor/node/Makefile
new file mode 100644
index 0000000..59aac28
--- /dev/null
+++ b/examples/flow_distributor/node/Makefile
@@ -0,0 +1,48 @@
+# BSD LICENSE
+#
+# Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overridden by command line or environment
+include $(RTE_SDK)/mk/rte.vars.mk
+
+# binary name
+APP = node
+
+# all source are stored in SRCS-y
+SRCS-y := node.c
+
+CFLAGS += $(WERROR_FLAGS) -O3
+CFLAGS += -I$(SRCDIR)/../shared
+
+include $(RTE_SDK)/mk/rte.extapp.mk
diff --git a/examples/flow_distributor/node/node.c b/examples/flow_distributor/node/node.c
new file mode 100644
index 0000000..e68f252
--- /dev/null
+++ b/examples/flow_distributor/node/node.c
@@ -0,0 +1,417 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <sys/queue.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_malloc.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_atomic.h>
+#include <rte_branch_prediction.h>
+#include <rte_log.h>
+#include <rte_per_lcore.h>
+#include <rte_launch.h>
+#include <rte_lcore.h>
+#include <rte_ring.h>
+#include <rte_launch.h>
+#include <rte_lcore.h>
+#include <rte_debug.h>
+#include <rte_mempool.h>
+#include <rte_mbuf.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ether.h>
+#include <rte_ethdev.h>
+#include <rte_string_fns.h>
+#include <rte_ip.h>
+
+#include "common.h"
+
+/* Number of packets to attempt to read from queue */
+#define PKT_READ_SIZE ((uint16_t)32)
+
+/*
+ * Our node id number - tells us which rx queue to read, and NIC TX
+ * queue to write to.
+ */
+static uint8_t node_id;
+
+#define MBQ_CAPACITY 32
+
+/* maps input ports to output ports for packets */
+static uint8_t output_ports[RTE_MAX_ETHPORTS];
+
+/* buffers up a set of packet that are ready to send */
+struct rte_eth_dev_tx_buffer *tx_buffer[RTE_MAX_ETHPORTS];
+
+/* shared data from distributor. We update statistics here */
+static struct tx_stats *tx_stats;
+
+static struct filter_stats *filter_stats;
+
+/*
+ * print a usage message
+ */
+static void
+usage(const char *progname)
+{
+ printf("Usage: %s [EAL args] -- -n <node_id>\n\n", progname);
+}
+
+/*
+ * Convert the node id number from a string to an int.
+ */
+static int
+parse_node_num(const char *node)
+{
+ char *end = NULL;
+ unsigned long temp;
+
+ if (node == NULL || *node == '\0')
+ return -1;
+
+ temp = strtoul(node, &end, 10);
+ if (end == NULL || *end != '\0')
+ return -1;
+
+ node_id = (uint8_t)temp;
+ return 0;
+}
+
+/*
+ * Parse the application arguments to the node app.
+ */
+static int
+parse_app_args(int argc, char *argv[])
+{
+ int option_index, opt;
+ char **argvopt = argv;
+ const char *progname = NULL;
+ static struct option lgopts[] = { /* no long options */
+ {NULL, 0, 0, 0 }
+ };
+ progname = argv[0];
+
+ while ((opt = getopt_long(argc, argvopt, "n:", lgopts,
+ &option_index)) != EOF) {
+ switch (opt) {
+ case 'n':
+ if (parse_node_num(optarg) != 0) {
+ usage(progname);
+ return -1;
+ }
+ break;
+ default:
+ usage(progname);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Tx buffer error callback
+ */
+static void
+flush_tx_error_callback(struct rte_mbuf **unsent, uint16_t count,
+ void *userdata) {
+ int i;
+ uint8_t port_id = (uintptr_t)userdata;
+
+ tx_stats->tx_drop[port_id] += count;
+
+ /* free the mbufs which failed from transmit */
+ for (i = 0; i < count; i++)
+ rte_pktmbuf_free(unsent[i]);
+
+}
+
+static void
+configure_tx_buffer(uint8_t port_id, uint16_t size)
+{
+ int ret;
+
+ /* Initialize TX buffers */
+ tx_buffer[port_id] = rte_zmalloc_socket("tx_buffer",
+ RTE_ETH_TX_BUFFER_SIZE(size), 0,
+ rte_eth_dev_socket_id(port_id));
+ if (tx_buffer[port_id] == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot allocate buffer for tx "
+ "on port %u\n", (unsigned int) port_id);
+
+ rte_eth_tx_buffer_init(tx_buffer[port_id], size);
+
+ ret = rte_eth_tx_buffer_set_err_callback(tx_buffer[port_id],
+ flush_tx_error_callback, (void *)(intptr_t)port_id);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Cannot set error callback for "
+ "tx buffer on port %u\n", (unsigned int) port_id);
+}
+
+/*
+ * set up output ports so that all traffic on port gets sent out
+ * its paired port. Index using actual port numbers since that is
+ * what comes in the mbuf structure.
+ */
+static void
+configure_output_ports(const struct shared_info *info)
+{
+ int i;
+
+ if (info->num_ports > RTE_MAX_ETHPORTS)
+ rte_exit(EXIT_FAILURE, "Too many ethernet ports. "
+ "RTE_MAX_ETHPORTS = %u\n",
+ (unsigned int)RTE_MAX_ETHPORTS);
+ for (i = 0; i < info->num_ports - 1; i += 2) {
+ uint8_t p1 = info->id[i];
+ uint8_t p2 = info->id[i+1];
+
+ output_ports[p1] = p2;
+ output_ports[p2] = p1;
+
+ configure_tx_buffer(p1, MBQ_CAPACITY);
+ configure_tx_buffer(p2, MBQ_CAPACITY);
+
+ }
+}
+
+/*
+ * Create the hash table that will contain the flows that
+ * the node will handle, which will be used to decide if packet
+ * is transmitted or dropped.
+ */
+static struct rte_hash *
+create_hash_table(const struct shared_info *info)
+{
+ uint32_t num_flows_node = info->num_flows / info->num_nodes;
+ char name[RTE_HASH_NAMESIZE];
+ struct rte_hash *h;
+
+ /* create table */
+ struct rte_hash_parameters hash_params = {
+ .entries = num_flows_node * 2, /* table load = 50% */
+ .key_len = sizeof(uint32_t), /* Store IPv4 dest IP address */
+ .socket_id = rte_socket_id(),
+ .hash_func_init_val = 0,
+ };
+
+ snprintf(name, sizeof(name), "hash_table_%d", node_id);
+ hash_params.name = name;
+ h = rte_hash_create(&hash_params);
+
+ if (h == NULL)
+ rte_exit(EXIT_FAILURE,
+ "Problem creating the hash table for node %d\n",
+ node_id);
+ return h;
+}
+
+static void
+populate_hash_table(const struct rte_hash *h, const struct shared_info *info)
+{
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint32_t num_flows_node = 0;
+ uint64_t target_node;
+
+ /* Add flows in table */
+ for (i = 0; i < info->num_flows; i++) {
+ target_node = i % info->num_nodes;
+ if (target_node != node_id)
+ continue;
+
+ ip_dst = rte_cpu_to_be_32(i);
+
+ ret = rte_hash_add_key(h, (void *) &ip_dst);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u "
+ "in hash table\n", i);
+ else
+ num_flows_node++;
+
+ }
+
+ printf("Hash table: Adding 0x%x keys\n", num_flows_node);
+}
+
+/*
+ * This function performs routing of packets
+ * Just sends each input packet out an output port based solely on the input
+ * port it arrived on.
+ */
+static inline void
+transmit_packet(struct rte_mbuf *buf)
+{
+ int sent;
+ const uint8_t in_port = buf->port;
+ const uint8_t out_port = output_ports[in_port];
+ struct rte_eth_dev_tx_buffer *buffer = tx_buffer[out_port];
+
+ sent = rte_eth_tx_buffer(out_port, node_id, buffer, buf);
+ if (sent)
+ tx_stats->tx[out_port] += sent;
+
+}
+
+static inline int
+handle_packets(struct rte_hash *h, struct rte_mbuf **bufs, uint16_t num_packets)
+{
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[PKT_READ_SIZE];
+ const void *key_ptrs[PKT_READ_SIZE];
+ unsigned int i;
+ int32_t positions[PKT_READ_SIZE] = {0};
+
+ for (i = 0; i < num_packets; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(bufs[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = &ipv4_dst_ip[i];
+ }
+ /* Check if packets belongs to any flows handled by this node */
+ rte_hash_lookup_bulk(h, key_ptrs, num_packets, positions);
+
+ for (i = 0; i < num_packets; i++) {
+ if (likely(positions[i] >= 0)) {
+ filter_stats->passed++;
+ transmit_packet(bufs[i]);
+ } else {
+ filter_stats->drop++;
+ /* Drop packet, as flow is not handled by this node */
+ rte_pktmbuf_free(bufs[i]);
+ }
+ }
+
+ return 1;
+}
+
+/*
+ * Application main function - loops through
+ * receiving and processing packets. Never returns
+ */
+int
+main(int argc, char *argv[])
+{
+ const struct rte_memzone *mz;
+ struct rte_ring *rx_ring;
+ struct rte_hash *h;
+ struct rte_mempool *mp;
+ struct shared_info *info;
+ int need_flush = 0; /* indicates whether we have unsent packets */
+ int retval;
+ void *pkts[PKT_READ_SIZE];
+ uint16_t sent;
+
+ retval = rte_eal_init(argc, argv);
+ if (retval < 0)
+ return -1;
+ argc -= retval;
+ argv += retval;
+
+ if (parse_app_args(argc, argv) < 0)
+ rte_exit(EXIT_FAILURE, "Invalid command-line arguments\n");
+
+ if (rte_eth_dev_count() == 0)
+ rte_exit(EXIT_FAILURE, "No Ethernet ports - bye\n");
+
+ rx_ring = rte_ring_lookup(get_rx_queue_name(node_id));
+ if (rx_ring == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get RX ring - "
+ "is distributor process running?\n");
+
+ mp = rte_mempool_lookup(PKTMBUF_POOL_NAME);
+ if (mp == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get mempool for mbufs\n");
+
+ mz = rte_memzone_lookup(MZ_SHARED_INFO);
+ if (mz == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get port info structure\n");
+ info = mz->addr;
+ tx_stats = &(info->tx_stats[node_id]);
+ filter_stats = &(info->filter_stats[node_id]);
+
+ configure_output_ports(info);
+
+ h = create_hash_table(info);
+
+ populate_hash_table(h, info);
+
+ RTE_LOG(INFO, APP, "Finished Process Init.\n");
+
+ printf("\nNode process %d handling packets\n", node_id);
+ printf("[Press Ctrl-C to quit ...]\n");
+
+ for (;;) {
+ uint16_t rx_pkts = PKT_READ_SIZE;
+ uint8_t port;
+
+ /*
+ * Try dequeuing max possible packets first, if that fails,
+ * get the most we can. Loop body should only execute once,
+ * maximum
+ */
+ while (rx_pkts > 0 &&
+ unlikely(rte_ring_dequeue_bulk(rx_ring, pkts,
+ rx_pkts) != 0))
+ rx_pkts = (uint16_t)RTE_MIN(rte_ring_count(rx_ring),
+ PKT_READ_SIZE);
+
+ if (unlikely(rx_pkts == 0)) {
+ if (need_flush)
+ for (port = 0; port < info->num_ports; port++) {
+ sent = rte_eth_tx_buffer_flush(info->id[port],
+ node_id, tx_buffer[port]);
+ if (unlikely(sent))
+ tx_stats->tx[port] += sent;
+ }
+ need_flush = 0;
+ continue;
+ }
+
+ handle_packets(h, (struct rte_mbuf **)pkts, rx_pkts);
+
+ need_flush = 1;
+ }
+}
diff --git a/examples/flow_distributor/shared/common.h b/examples/flow_distributor/shared/common.h
new file mode 100644
index 0000000..66c5574
--- /dev/null
+++ b/examples/flow_distributor/shared/common.h
@@ -0,0 +1,99 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _COMMON_H_
+#define _COMMON_H_
+
+#include <rte_hash_crc.h>
+#include <rte_hash.h>
+
+#define MAX_NODES 16
+/*
+ * Shared port info, including statistics information for display by distributor.
+ * Structure will be put in a memzone.
+ * - All port id values share one cache line as this data will be read-only
+ * during operation.
+ * - All rx statistic values share cache lines, as this data is written only
+ * by the distributor process. (rare reads by stats display)
+ * - The tx statistics have values for all ports per cache line, but the stats
+ * themselves are written by the nodes, so we have a distinct set, on different
+ * cache lines for each node to use.
+ */
+struct rx_stats {
+ uint64_t rx[RTE_MAX_ETHPORTS];
+} __rte_cache_aligned;
+
+struct tx_stats {
+ uint64_t tx[RTE_MAX_ETHPORTS];
+ uint64_t tx_drop[RTE_MAX_ETHPORTS];
+} __rte_cache_aligned;
+
+struct filter_stats {
+ uint64_t drop;
+ uint64_t passed;
+} __rte_cache_aligned;
+
+struct shared_info {
+ uint8_t num_nodes;
+ uint8_t num_ports;
+ uint32_t num_flows;
+ uint8_t id[RTE_MAX_ETHPORTS];
+ struct rx_stats rx_stats;
+ struct tx_stats tx_stats[MAX_NODES];
+ struct filter_stats filter_stats[MAX_NODES];
+};
+
+/* define common names for structures shared between distributor and node */
+#define MP_NODE_RXQ_NAME "MProc_Node_%u_RX"
+#define PKTMBUF_POOL_NAME "MProc_pktmbuf_pool"
+#define MZ_SHARED_INFO "MProc_shared_info"
+
+/*
+ * Given the rx queue name template above, get the queue name
+ */
+static inline const char *
+get_rx_queue_name(unsigned int id)
+{
+ /*
+ * Buffer for return value. Size calculated by %u being replaced
+ * by maximum 3 digits (plus an extra byte for safety)
+ */
+ static char buffer[sizeof(MP_NODE_RXQ_NAME) + 2];
+
+ snprintf(buffer, sizeof(buffer) - 1, MP_NODE_RXQ_NAME, id);
+ return buffer;
+}
+
+#define RTE_LOGTYPE_APP RTE_LOGTYPE_USER1
+
+#endif
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v2 0/5] Elastic Flow Distributor
2016-12-02 14:52 [dpdk-dev] [PATCH 0/2] Elastic Flow Distributor Pablo de Lara
2016-12-02 14:52 ` [dpdk-dev] [PATCH 1/2] efd: new Elastic Flow Distributor library Pablo de Lara
2016-12-02 14:52 ` [dpdk-dev] [PATCH 2/2] examples/flow_distributor: sample app to demonstrate EFD usage Pablo de Lara
@ 2017-01-07 1:06 ` Pablo de Lara
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 1/5] efd: new Elastic Flow Distributor library Pablo de Lara
` (6 more replies)
2 siblings, 7 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-07 1:06 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara
EFD is a distributor library that uses perfect hashing to determine a
target/value for a given incoming flow key. It has the following advantages:
first, because it uses perfect hashing it does not store the key itself and
hence lookup performance is not dependent on the key size. Second, the
target/value can be any arbitrary value hence the system designer and/or
operator can better optimize service rates and inter-cluster network traffic
locating. Third, since the storage requirement is much smaller than a
hash-based flow table (i.e. better fit for CPU cache), EFD can scale to millions
of flow keys. Finally, with current optimized library implementation performance
is fully scalable with number of CPU cores.
The basic idea of EFD is when a given key is to be inserted, a family of hash
functions is searched until the correct hash function that maps the input key to
the correct value is found. However, rather than explicitly storing all keys and
their associated values, EFD stores only indices of hash functions that map keys
to values, and thereby consumes much less space than conventional flow-based
tables. The lookup operation is very simple, similar to computational-based
scheme, given an input key the lookup operation is reduced to hashing that key
with the correct hash function.
Intuitively, finding a hash function that maps each of a large number (millions)
of input keys to the correct output value is effectively impossible, as a result
EFD, breaks the problem into smaller pieces (divide and conquer). EFD divides
the entire input key set into many small groups. Each group consists of
approximately 20-28 keys (a configurable parameter for the library), then, for
each small group, a brute force search to find a hash function that produces the
correct outputs for each key in the group.
It should be mentioned that since in the online lookup table for EFD doesn’t
store the key itself, the size of the EFD table is independent of the key size
and hence EFD lookup performance which is almost constant irrespective of the
length of the key which is a highly desirable feature especially for longer
keys.
Library code is included in the patch, plus an sample application that shows
how the library can be used.
RFC for this library was already sent:
http://dpdk.org/ml/archives/dev/2016-October/049238.html
For more information on the library, check out the following document:
https://github.com/pablodelara/perfect_hash_flow_distributor/blob/master/EFD_description.pdf
Changes in v2:
- Added documentation for library and sample app
- Fixed checkpatch errors/warnings
- Added functional and performance tests
- Made key size variable at runtime
- Made code multi-architecture compatible at runtime
Pablo de Lara (5):
efd: new Elastic Flow Distributor library
app/test: add EFD functional and perf tests
examples/flow_distributor: sample app to demonstrate EFD usage
doc: add EFD library section in Programmers guide
doc: add flow distributor guide
MAINTAINERS | 9 +
app/test/Makefile | 3 +
app/test/test_efd.c | 494 ++++++++
app/test/test_efd_perf.c | 407 ++++++
config/common_base | 5 +
doc/api/doxy-api-index.md | 3 +-
doc/api/doxy-api.conf | 1 +
doc/api/examples.dox | 4 +
doc/guides/prog_guide/efd_lib.rst | 413 ++++++
doc/guides/prog_guide/img/efd_i1.svg | 78 ++
doc/guides/prog_guide/img/efd_i10.svg | 1272 +++++++++++++++++++
doc/guides/prog_guide/img/efd_i11.svg | 413 ++++++
doc/guides/prog_guide/img/efd_i12.svg | 590 +++++++++
doc/guides/prog_guide/img/efd_i13.svg | 1407 +++++++++++++++++++++
doc/guides/prog_guide/img/efd_i2.svg | 209 +++
doc/guides/prog_guide/img/efd_i3.svg | 420 ++++++
doc/guides/prog_guide/img/efd_i4.svg | 364 ++++++
doc/guides/prog_guide/img/efd_i5.svg | 578 +++++++++
doc/guides/prog_guide/img/efd_i6.svg | 413 ++++++
doc/guides/prog_guide/img/efd_i8.svg | 776 ++++++++++++
doc/guides/prog_guide/img/efd_i9.svg | 271 ++++
doc/guides/prog_guide/index.rst | 1 +
doc/guides/rel_notes/release_17_02.rst | 15 +
doc/guides/sample_app_ug/flow_distributor.rst | 492 +++++++
doc/guides/sample_app_ug/img/flow_distributor.svg | 417 ++++++
doc/guides/sample_app_ug/index.rst | 1 +
examples/Makefile | 1 +
examples/flow_distributor/Makefile | 44 +
examples/flow_distributor/distributor/Makefile | 57 +
examples/flow_distributor/distributor/args.c | 200 +++
examples/flow_distributor/distributor/args.h | 39 +
examples/flow_distributor/distributor/init.c | 371 ++++++
examples/flow_distributor/distributor/init.h | 76 ++
examples/flow_distributor/distributor/main.c | 362 ++++++
examples/flow_distributor/node/Makefile | 48 +
examples/flow_distributor/node/node.c | 417 ++++++
examples/flow_distributor/shared/common.h | 99 ++
lib/Makefile | 1 +
lib/librte_eal/common/include/rte_log.h | 1 +
lib/librte_efd/Makefile | 56 +
lib/librte_efd/rte_efd.c | 1354 ++++++++++++++++++++
lib/librte_efd/rte_efd.h | 294 +++++
lib/librte_efd/rte_efd_version.map | 12 +
mk/rte.app.mk | 1 +
44 files changed, 12488 insertions(+), 1 deletion(-)
create mode 100644 app/test/test_efd.c
create mode 100644 app/test/test_efd_perf.c
create mode 100644 doc/guides/prog_guide/efd_lib.rst
create mode 100644 doc/guides/prog_guide/img/efd_i1.svg
create mode 100644 doc/guides/prog_guide/img/efd_i10.svg
create mode 100644 doc/guides/prog_guide/img/efd_i11.svg
create mode 100644 doc/guides/prog_guide/img/efd_i12.svg
create mode 100644 doc/guides/prog_guide/img/efd_i13.svg
create mode 100644 doc/guides/prog_guide/img/efd_i2.svg
create mode 100644 doc/guides/prog_guide/img/efd_i3.svg
create mode 100644 doc/guides/prog_guide/img/efd_i4.svg
create mode 100644 doc/guides/prog_guide/img/efd_i5.svg
create mode 100644 doc/guides/prog_guide/img/efd_i6.svg
create mode 100644 doc/guides/prog_guide/img/efd_i8.svg
create mode 100644 doc/guides/prog_guide/img/efd_i9.svg
create mode 100644 doc/guides/sample_app_ug/flow_distributor.rst
create mode 100644 doc/guides/sample_app_ug/img/flow_distributor.svg
create mode 100644 examples/flow_distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/args.c
create mode 100644 examples/flow_distributor/distributor/args.h
create mode 100644 examples/flow_distributor/distributor/init.c
create mode 100644 examples/flow_distributor/distributor/init.h
create mode 100644 examples/flow_distributor/distributor/main.c
create mode 100644 examples/flow_distributor/node/Makefile
create mode 100644 examples/flow_distributor/node/node.c
create mode 100644 examples/flow_distributor/shared/common.h
create mode 100644 lib/librte_efd/Makefile
create mode 100644 lib/librte_efd/rte_efd.c
create mode 100644 lib/librte_efd/rte_efd.h
create mode 100644 lib/librte_efd/rte_efd_version.map
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v2 1/5] efd: new Elastic Flow Distributor library
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 0/5] Elastic Flow Distributor Pablo de Lara
@ 2017-01-07 1:06 ` Pablo de Lara
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 2/5] app/test: add EFD functional and perf tests Pablo de Lara
` (5 subsequent siblings)
6 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-07 1:06 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Byron Marohn, Saikrishna Edupuganti
Elastic Flow Distributor (EFD) is a distributor library that uses
perfect hashing to determine a target/value for a given incoming flow key.
It has the following advantages:
- First, because it uses perfect hashing, it does not store
the key itself and hence lookup performance is not dependent
on the key size.
- Second, the target/value can be any arbitrary value hence
the system designer and/or operator can better optimize service rates
and inter-cluster network traffic locating.
- Third, since the storage requirement is much smaller than a hash-based
flow table (i.e. better fit for CPU cache), EFD can scale to
millions of flow keys.
Finally, with current optimized library implementation performance
is fully scalable with number of CPU cores.
Signed-off-by: Byron Marohn <byron.marohn@intel.com>
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
---
MAINTAINERS | 5 +
config/common_base | 5 +
doc/api/doxy-api-index.md | 3 +-
doc/api/doxy-api.conf | 1 +
doc/guides/rel_notes/release_17_02.rst | 12 +
lib/Makefile | 1 +
lib/librte_eal/common/include/rte_log.h | 1 +
lib/librte_efd/Makefile | 56 ++
lib/librte_efd/rte_efd.c | 1354 +++++++++++++++++++++++++++++++
lib/librte_efd/rte_efd.h | 294 +++++++
lib/librte_efd/rte_efd_version.map | 12 +
mk/rte.app.mk | 1 +
12 files changed, 1744 insertions(+), 1 deletion(-)
create mode 100644 lib/librte_efd/Makefile
create mode 100644 lib/librte_efd/rte_efd.c
create mode 100644 lib/librte_efd/rte_efd.h
create mode 100644 lib/librte_efd/rte_efd_version.map
diff --git a/MAINTAINERS b/MAINTAINERS
index 9645c9b..9c60d67 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -528,6 +528,11 @@ F: app/test/test_acl.*
F: examples/l3fwd-acl/
F: doc/guides/sample_app_ug/l3_forward_access_ctrl.rst
+EFD
+M: Byron Marohn <byron.marohn@intel.com>
+M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
+F: lib/librte_efd/
+
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
M: Pablo de Lara <pablo.de.lara.guarch@intel.com>
diff --git a/config/common_base b/config/common_base
index 8e9dcfa..869d8fb 100644
--- a/config/common_base
+++ b/config/common_base
@@ -467,6 +467,11 @@ CONFIG_RTE_LIBRTE_HASH=y
CONFIG_RTE_LIBRTE_HASH_DEBUG=n
#
+# Compile librte_efd
+#
+CONFIG_RTE_LIBRTE_EFD=y
+
+#
# Compile librte_jobstats
#
CONFIG_RTE_LIBRTE_JOBSTATS=y
diff --git a/doc/api/doxy-api-index.md b/doc/api/doxy-api-index.md
index 72d59b2..0d34e2f 100644
--- a/doc/api/doxy-api-index.md
+++ b/doc/api/doxy-api-index.md
@@ -90,7 +90,8 @@ There are many libraries, so their headers may be grouped by topics:
[frag/reass] (@ref rte_ip_frag.h),
[LPM IPv4 route] (@ref rte_lpm.h),
[LPM IPv6 route] (@ref rte_lpm6.h),
- [ACL] (@ref rte_acl.h)
+ [ACL] (@ref rte_acl.h),
+ [EFD] (@ref rte_efd.h)
- **QoS**:
[metering] (@ref rte_meter.h),
diff --git a/doc/api/doxy-api.conf b/doc/api/doxy-api.conf
index b340fcf..6892315 100644
--- a/doc/api/doxy-api.conf
+++ b/doc/api/doxy-api.conf
@@ -40,6 +40,7 @@ INPUT = doc/api/doxy-api-index.md \
lib/librte_compat \
lib/librte_cryptodev \
lib/librte_distributor \
+ lib/librte_efd \
lib/librte_ether \
lib/librte_hash \
lib/librte_ip_frag \
diff --git a/doc/guides/rel_notes/release_17_02.rst b/doc/guides/rel_notes/release_17_02.rst
index 180af82..5023038 100644
--- a/doc/guides/rel_notes/release_17_02.rst
+++ b/doc/guides/rel_notes/release_17_02.rst
@@ -53,6 +53,18 @@ New Features
information.
+* **Added Elastic Flow Distributor library (rte_efd).**
+
+ This new library uses perfect hashing to determine a target/value for a
+ given incoming flow key.
+
+ It does not store the key itself for lookup operations, and therefore,
+ lookup performance is not dependent on the key size. Also, the target/value
+ can be any arbitrary value (8 bits by default). Finally, the storage requirement
+ is much smaller than a hash-based flow table and therefore, it can better fit for
+ CPU cache, being able to scale to millions of flow keys.
+
+
Resolved Issues
---------------
diff --git a/lib/Makefile b/lib/Makefile
index 990f23a..9a41188 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -43,6 +43,7 @@ DIRS-$(CONFIG_RTE_LIBRTE_ETHER) += librte_ether
DIRS-$(CONFIG_RTE_LIBRTE_CRYPTODEV) += librte_cryptodev
DIRS-$(CONFIG_RTE_LIBRTE_VHOST) += librte_vhost
DIRS-$(CONFIG_RTE_LIBRTE_HASH) += librte_hash
+DIRS-$(CONFIG_RTE_LIBRTE_EFD) += librte_efd
DIRS-$(CONFIG_RTE_LIBRTE_LPM) += librte_lpm
DIRS-$(CONFIG_RTE_LIBRTE_ACL) += librte_acl
DIRS-$(CONFIG_RTE_LIBRTE_NET) += librte_net
diff --git a/lib/librte_eal/common/include/rte_log.h b/lib/librte_eal/common/include/rte_log.h
index 671e274..f8342ae 100644
--- a/lib/librte_eal/common/include/rte_log.h
+++ b/lib/librte_eal/common/include/rte_log.h
@@ -79,6 +79,7 @@ extern struct rte_logs rte_logs;
#define RTE_LOGTYPE_PIPELINE 0x00008000 /**< Log related to pipeline. */
#define RTE_LOGTYPE_MBUF 0x00010000 /**< Log related to mbuf. */
#define RTE_LOGTYPE_CRYPTODEV 0x00020000 /**< Log related to cryptodev. */
+#define RTE_LOGTYPE_EFD 0x00040000 /**< Log related to EFD. */
/* these log types can be used in an application */
#define RTE_LOGTYPE_USER1 0x01000000 /**< User-defined log type 1. */
diff --git a/lib/librte_efd/Makefile b/lib/librte_efd/Makefile
new file mode 100644
index 0000000..577469e
--- /dev/null
+++ b/lib/librte_efd/Makefile
@@ -0,0 +1,56 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+# library name
+LIB = librte_efd.a
+
+CFLAGS += -O3
+CFLAGS += $(WERROR_FLAGS) -I$(SRCDIR)
+
+EXPORT_MAP := rte_efd_version.map
+
+LIBABIVER := 1
+
+# all source are stored in SRCS-y
+SRCS-$(CONFIG_RTE_LIBRTE_EFD) := rte_efd.c
+
+# install this header file
+SYMLINK-$(CONFIG_RTE_LIBRTE_EFD)-include := rte_efd.h
+
+# this lib depends upon:
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_mbuf
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_mempool
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_eal
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_ether
+
+include $(RTE_SDK)/mk/rte.lib.mk
diff --git a/lib/librte_efd/rte_efd.c b/lib/librte_efd/rte_efd.c
new file mode 100644
index 0000000..53f2b92
--- /dev/null
+++ b/lib/librte_efd/rte_efd.c
@@ -0,0 +1,1354 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <immintrin.h>
+#include <math.h>
+#include <sys/queue.h>
+
+#include <rte_log.h>
+#include <rte_eal_memconfig.h>
+#include <rte_errno.h>
+#include <rte_malloc.h>
+#include <rte_memzone.h>
+#include <rte_prefetch.h>
+#include <rte_branch_prediction.h>
+#include <rte_memcpy.h>
+#include <rte_ring.h>
+#include <rte_jhash.h>
+#include <rte_hash_crc.h>
+
+#include "rte_efd.h"
+
+#define EFD_KEY(key_idx, table) (table->keys + ((key_idx) * table->key_len))
+/** Hash function used to determine chunk_id and bin_id for a group */
+#define EFD_HASH(key, table) \
+ (uint32_t)(rte_jhash(key, table->key_len, 0xbc9f1d34))
+/** Hash function used as constant component of perfect hash search */
+#define EFD_HASHFUNCA(key, table) \
+ (uint32_t)(rte_hash_crc(key, table->key_len, 0xbc9f1d35))
+/** Hash function used as multiplicative component of perfect hash search */
+#define EFD_HASHFUNCB(key, table) \
+ (uint32_t)(rte_hash_crc(key, table->key_len, 0xbc9f1d36))
+
+/*************************************************************************
+ * Fixed constants
+ *************************************************************************/
+
+/* These parameters are fixed by the efd_bin_to_group balancing table */
+#define EFD_CHUNK_NUM_GROUPS (64)
+#define EFD_CHUNK_NUM_BINS (256)
+#define EFD_CHUNK_NUM_BIN_TO_GROUP_SETS \
+ (EFD_CHUNK_NUM_BINS / EFD_CHUNK_NUM_GROUPS)
+
+/*
+ * Target number of rules that each chunk is created to handle.
+ * Used when initially allocating the table
+ */
+#define EFD_TARGET_CHUNK_NUM_RULES \
+ (EFD_CHUNK_NUM_GROUPS * EFD_TARGET_GROUP_NUM_RULES)
+/*
+ * Max number of rules that each chunk is created to handle.
+ * Used when initially allocating the table
+ */
+#define EFD_TARGET_CHUNK_MAX_NUM_RULES \
+ (EFD_CHUNK_NUM_GROUPS * EFD_MAX_GROUP_NUM_RULES)
+
+/** This is fixed based on the bin_to_group permutation array */
+#define EFD_MAX_GROUP_NUM_BINS (16)
+
+/**
+ * The end of the chunks array needs some extra padding to ensure
+ * that vectorization over-reads on the last online chunk stay within
+allocated memory
+ */
+#define EFD_NUM_CHUNK_PADDING_BYTES (256)
+
+#define EFD_LOOKUPTBL_SHIFT (32 - 4)
+typedef uint16_t efd_lookuptbl_t;
+typedef uint16_t efd_hashfunc_t;
+
+/* All different signature compare functions */
+enum rte_efd_compare_function {
+ RTE_HASH_COMPARE_SCALAR = 0,
+ RTE_HASH_COMPARE_AVX2,
+ RTE_HASH_COMPARE_NUM
+};
+
+TAILQ_HEAD(rte_efd_list, rte_tailq_entry);
+
+static struct rte_tailq_elem rte_efd_tailq = {
+ .name = "RTE_EFD",
+};
+EAL_REGISTER_TAILQ(rte_efd_tailq);
+
+/** Internal permutation array used to shuffle bins into pseudorandom groups */
+const uint32_t efd_bin_to_group[EFD_CHUNK_NUM_BIN_TO_GROUP_SETS][EFD_CHUNK_NUM_BINS] = {
+ {
+ 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3,
+ 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
+ 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11,
+ 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15,
+ 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19,
+ 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23,
+ 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27,
+ 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31,
+ 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35,
+ 36, 36, 36, 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39,
+ 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, 42, 43, 43, 43, 43,
+ 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47,
+ 48, 48, 48, 48, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51,
+ 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, 54, 55, 55, 55, 55,
+ 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59,
+ 60, 60, 60, 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63
+ },
+ {
+ 34, 33, 48, 59, 0, 21, 36, 18, 9, 49, 54, 38, 51, 23, 31, 5,
+ 44, 23, 37, 52, 11, 4, 58, 20, 38, 40, 38, 22, 26, 28, 42, 6,
+ 46, 16, 31, 28, 46, 14, 60, 0, 35, 53, 16, 58, 16, 29, 39, 7,
+ 1, 54, 15, 11, 48, 3, 62, 9, 58, 5, 30, 43, 17, 7, 36, 34,
+ 6, 36, 2, 14, 10, 1, 47, 47, 20, 45, 62, 56, 34, 25, 39, 18,
+ 51, 41, 61, 25, 56, 40, 41, 37, 52, 35, 30, 57, 11, 42, 37, 27,
+ 54, 19, 26, 13, 48, 31, 46, 15, 12, 10, 16, 20, 43, 17, 12, 55,
+ 45, 18, 8, 41, 7, 31, 42, 63, 12, 14, 21, 57, 24, 40, 5, 41,
+ 13, 44, 23, 59, 25, 57, 52, 50, 62, 1, 2, 49, 32, 57, 26, 43,
+ 56, 60, 55, 5, 49, 6, 3, 50, 46, 39, 27, 33, 17, 4, 53, 13,
+ 2, 19, 36, 51, 63, 0, 22, 33, 59, 28, 29, 23, 45, 33, 53, 27,
+ 22, 21, 40, 56, 4, 18, 44, 47, 28, 17, 4, 50, 21, 62, 8, 39,
+ 0, 8, 15, 24, 29, 24, 9, 11, 48, 61, 35, 55, 43, 1, 54, 42,
+ 53, 60, 22, 3, 32, 52, 25, 8, 15, 60, 7, 55, 27, 63, 19, 10,
+ 63, 24, 61, 19, 12, 38, 6, 29, 13, 37, 10, 3, 45, 32, 32, 30,
+ 49, 61, 44, 14, 20, 58, 35, 30, 2, 26, 34, 51, 9, 59, 47, 50
+ },
+ {
+ 32, 35, 32, 34, 55, 5, 6, 23, 49, 11, 6, 23, 52, 37, 29, 54,
+ 55, 40, 63, 50, 29, 52, 61, 25, 12, 56, 39, 38, 29, 11, 46, 1,
+ 40, 11, 19, 56, 7, 28, 51, 16, 15, 48, 21, 51, 60, 31, 14, 22,
+ 41, 47, 59, 56, 53, 28, 58, 26, 43, 27, 41, 33, 24, 52, 44, 38,
+ 13, 59, 48, 51, 60, 15, 3, 30, 15, 0, 10, 62, 44, 14, 28, 51,
+ 38, 2, 41, 26, 25, 49, 10, 12, 55, 57, 27, 35, 19, 33, 0, 30,
+ 5, 36, 47, 53, 5, 53, 20, 43, 34, 37, 52, 41, 21, 63, 59, 9,
+ 24, 1, 45, 24, 39, 44, 45, 16, 9, 17, 7, 50, 57, 22, 18, 28,
+ 25, 45, 2, 40, 58, 15, 17, 3, 1, 27, 61, 39, 19, 0, 19, 21,
+ 57, 62, 54, 60, 54, 40, 48, 33, 36, 37, 4, 42, 1, 43, 58, 8,
+ 13, 42, 10, 56, 35, 22, 48, 61, 63, 10, 49, 9, 24, 9, 25, 57,
+ 33, 18, 13, 31, 42, 36, 36, 55, 30, 37, 53, 34, 59, 4, 4, 23,
+ 8, 16, 58, 14, 30, 11, 12, 63, 49, 62, 2, 39, 47, 22, 2, 60,
+ 18, 8, 46, 31, 6, 20, 32, 29, 46, 42, 20, 31, 32, 61, 34, 4,
+ 47, 26, 20, 43, 26, 21, 7, 3, 16, 35, 18, 44, 27, 62, 13, 23,
+ 6, 50, 12, 8, 45, 17, 3, 46, 50, 7, 14, 5, 17, 54, 38, 0
+ },
+ {
+ 29, 56, 5, 7, 54, 48, 23, 37, 35, 44, 52, 40, 33, 49, 60, 0,
+ 59, 51, 28, 12, 41, 26, 2, 23, 34, 5, 59, 40, 3, 19, 6, 26,
+ 35, 53, 45, 49, 29, 57, 28, 62, 58, 59, 19, 53, 59, 62, 6, 54,
+ 13, 15, 48, 50, 45, 21, 41, 12, 34, 40, 24, 56, 19, 21, 35, 18,
+ 55, 45, 9, 61, 47, 61, 19, 15, 16, 39, 17, 31, 3, 51, 21, 50,
+ 17, 25, 25, 11, 44, 16, 18, 28, 14, 2, 37, 61, 58, 27, 62, 4,
+ 14, 17, 1, 9, 46, 28, 37, 0, 53, 43, 57, 7, 57, 46, 21, 41,
+ 39, 14, 52, 60, 44, 53, 49, 60, 49, 63, 13, 11, 29, 1, 55, 47,
+ 55, 12, 60, 43, 54, 37, 13, 6, 42, 10, 36, 13, 9, 8, 34, 51,
+ 31, 32, 12, 7, 57, 2, 26, 14, 3, 30, 63, 3, 32, 1, 5, 11,
+ 27, 24, 26, 44, 31, 23, 56, 38, 62, 0, 40, 30, 6, 23, 38, 2,
+ 47, 5, 15, 27, 16, 10, 31, 25, 22, 63, 30, 25, 20, 33, 32, 50,
+ 29, 43, 55, 10, 50, 45, 56, 20, 4, 7, 27, 46, 11, 16, 22, 52,
+ 35, 20, 41, 54, 46, 33, 42, 18, 63, 8, 22, 58, 36, 4, 51, 42,
+ 38, 32, 38, 22, 17, 0, 47, 8, 48, 8, 48, 1, 61, 36, 33, 20,
+ 24, 39, 39, 18, 30, 36, 9, 43, 42, 24, 10, 58, 4, 15, 34, 52
+ },
+};
+
+/*************************************************************************
+ * Offline region structures
+ *************************************************************************/
+
+/** Online group containing number of rules, values, keys and their bins
+ * for EFD_MAX_GROUP_NUM_RULES rules.
+ */
+struct efd_offline_group_rules {
+ uint32_t num_rules;
+ /**< Sum of the number of rules in all bins assigned to this group. */
+
+ uint32_t key_idx[EFD_MAX_GROUP_NUM_RULES];
+ /**< Array with all keys of the group. */
+ efd_value_t value[EFD_MAX_GROUP_NUM_RULES];
+ /**< Array with all values of the keys of the group. */
+
+ uint8_t bin_id[EFD_MAX_GROUP_NUM_RULES];
+ /**< Stores the bin for each correspending key to
+ * avoid having to recompute it
+ */
+};
+
+/** Offline chunk record, containing EFD_TARGET_CHUNK_NUM_RULES rules.
+ * Those rules are split into EFD_CHUNK_NUM_GROUPS groups per chunk.
+ */
+struct efd_offline_chunk_rules {
+ uint16_t num_rules;
+ /**< Number of rules in the entire chunk;
+ * used to detect unbalanced groups
+ */
+
+ struct efd_offline_group_rules group_rules[EFD_CHUNK_NUM_GROUPS];
+ /**< Array of all groups in the chunk. */
+};
+
+/*************************************************************************
+ * Online region structures
+ *************************************************************************/
+
+/** Online group containing values for EFD_MAX_GROUP_NUM_RULES rules. */
+struct efd_online_group_entry {
+ efd_hashfunc_t hash_idx[RTE_EFD_VALUE_NUM_BITS];
+ efd_lookuptbl_t lookup_table[RTE_EFD_VALUE_NUM_BITS];
+} __attribute__((__packed__));
+
+/**
+ * A single chunk record, containing EFD_TARGET_CHUNK_NUM_RULES rules.
+ * Those rules are split into EFD_CHUNK_NUM_GROUPS groups per chunk.
+ */
+struct efd_online_chunk {
+ uint8_t bin_choice_list[(EFD_CHUNK_NUM_BINS * 2 + 7) / 8];
+ /**< This is a packed indirection index into the 'groups' array.
+ * Each byte contains four two-bit values which index into
+ * the efd_bin_to_group array.
+ * The efd_bin_to_group array returns the index into the groups array
+ */
+
+ struct efd_online_group_entry groups[EFD_CHUNK_NUM_GROUPS];
+ /**< Array of all the groups in the chunk. */
+} __attribute__((__packed__));
+
+/**
+ * EFD table structure
+ */
+struct rte_efd_table {
+ char name[RTE_EFD_NAMESIZE]; /**< Name of the efd table. */
+
+ uint32_t key_len; /**< Length of the key stored offline */
+
+ uint32_t max_num_rules;
+ /**< Static maximum number of entries the table was constructed to hold. */
+
+ uint32_t num_rules;
+ /**< Number of entries currently in the table . */
+
+ uint32_t num_chunks;
+ /**< Number of chunks in the table needed to support num_rules. */
+
+ uint32_t num_chunks_shift;
+ /**< Bits to shift to get chunk id, instead of dividing by num_chunk. */
+
+ enum rte_efd_compare_function cmp_fn;
+ /**< Indicates which compare function to use. */
+
+ struct efd_online_chunk *chunks[RTE_MAX_NUMA_NODES];
+ /**< Dynamic array of size num_chunks of chunk records. */
+
+ struct efd_offline_chunk_rules *offline_chunks;
+ /**< Dynamic array of size num_chunks of key-value pairs. */
+
+ struct rte_ring *free_slots;
+ /**< Ring that stores all indexes of the free slots in the key table */
+
+ uint8_t *keys; /**< Dynamic array of size max_num_rules of keys */
+};
+
+/**
+ * Computes the chunk ID for a given key hash
+ *
+ * @param table
+ * EFD table to reference
+ * @param hashed_key
+ * 32-bit key hash returned by EFD_HASH
+ *
+ * @return
+ * chunk ID containing this key hash
+ */
+static inline uint32_t
+efd_get_chunk_id(const struct rte_efd_table * const table,
+ const uint32_t hashed_key)
+{
+ return hashed_key & (table->num_chunks - 1);
+}
+
+/**
+ * Computes the bin ID for a given key hash
+ *
+ * @param table
+ * EFD table to reference
+ * @param hashed_key
+ * 32-bit key hash returned by EFD_HASH
+ *
+ * @return bin ID containing this key hash
+ */
+static inline uint32_t
+efd_get_bin_id(const struct rte_efd_table * const table,
+ const uint32_t hashed_key)
+{
+ return (hashed_key >> table->num_chunks_shift) & (EFD_CHUNK_NUM_BINS - 1);
+}
+
+/**
+ * Looks up the current permutation choice for a particular bin in the online table
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to look up existing values (ideally caller's socket id)
+ * @param chunk_id
+ * Chunk ID of bin to look up
+ * @param bin_id
+ * Bin ID to look up
+ *
+ * @return
+ * Currently active permutation choice in the online table
+ */
+static inline uint8_t
+efd_get_choice(const struct rte_efd_table * const table,
+ const unsigned int socket_id, const uint32_t chunk_id,
+ const uint32_t bin_id)
+{
+ struct efd_online_chunk *chunk = &table->chunks[socket_id][chunk_id];
+
+ /*
+ * Grab the chunk (byte) that contains the choices
+ * for four neighboring bins.
+ */
+ uint8_t choice_chunk =
+ chunk->bin_choice_list[bin_id / EFD_CHUNK_NUM_BIN_TO_GROUP_SETS];
+
+ /*
+ * Compute the offset into the chunk that contains
+ * the group_id lookup position
+ */
+ int offset = (bin_id & 0x3) * 2;
+
+ /* Extract from the byte just the desired lookup position */
+ return (uint8_t) ((choice_chunk >> offset) & 0x3);
+}
+
+/**
+ * Compute the chunk_id and bin_id for a given key
+ *
+ * @param table
+ * EFD table to reference
+ * @param key
+ * Key to hash and find location of
+ * @param chunk_id
+ * Computed chunk ID
+ * @param bin_id
+ * Computed bin ID
+ *
+ */
+static inline void
+efd_compute_ids(const struct rte_efd_table * const table,
+ const void *key, uint32_t * const chunk_id, uint32_t * const bin_id)
+{
+ /* Compute the position of the entry in the hash table */
+ uint32_t h = EFD_HASH(key, table);
+
+ /* Compute the chunk_id where that entry can be found */
+ *chunk_id = efd_get_chunk_id(table, h);
+
+ /*
+ * Compute the bin within that chunk where the entry
+ * can be found (0 - 255)
+ */
+ *bin_id = efd_get_bin_id(table, h);
+}
+
+/**
+ * Search for a hash function for a group that satisfies all group results
+ */
+static inline int
+efd_search_hash(struct rte_efd_table * const table,
+ const struct efd_offline_group_rules * const off_group,
+ struct efd_online_group_entry * const on_group)
+{
+ efd_hashfunc_t hash_idx;
+ efd_hashfunc_t start_hash_idx[RTE_EFD_VALUE_NUM_BITS];
+ efd_lookuptbl_t start_lookup_table[RTE_EFD_VALUE_NUM_BITS];
+
+ uint32_t i, j, rule_id;
+ uint32_t hash_val_a[EFD_MAX_GROUP_NUM_RULES];
+ uint32_t hash_val_b[EFD_MAX_GROUP_NUM_RULES];
+ uint32_t hash_val[EFD_MAX_GROUP_NUM_RULES];
+
+
+ rte_prefetch0(off_group->value);
+
+ /*
+ * Prepopulate the hash_val tables by running the two hash functions
+ * for each provided rule
+ */
+ for (i = 0; i < off_group->num_rules; i++) {
+ void *key_stored = EFD_KEY(off_group->key_idx[i], table);
+ hash_val_b[i] = EFD_HASHFUNCB(key_stored, table);
+ hash_val_a[i] = EFD_HASHFUNCA(key_stored, table);
+ }
+
+ for (i = 0; i < RTE_EFD_VALUE_NUM_BITS; i++) {
+ hash_idx = on_group->hash_idx[i];
+ start_hash_idx[i] = hash_idx;
+ start_lookup_table[i] = on_group->lookup_table[i];
+
+ do {
+ efd_lookuptbl_t lookup_table = 0;
+ efd_lookuptbl_t lookup_table_complement = 0;
+
+ for (rule_id = 0; rule_id < off_group->num_rules; rule_id++)
+ hash_val[rule_id] = hash_val_a[rule_id] + (hash_idx *
+ hash_val_b[rule_id]);
+
+ /*
+ * The goal here is to find a hash function for this
+ * particular bit entry that meets the following criteria:
+ * The most significant bits of the hash result define a
+ * shift into the lookup table where the bit will be stored
+ */
+
+ /* Iterate over each provided rule */
+ for (rule_id = 0; rule_id < off_group->num_rules;
+ rule_id++) {
+ /*
+ * Use the few most significant bits (number based on
+ * EFD_LOOKUPTBL_SIZE) to see what position the
+ * expected bit should be set in the lookup_table
+ */
+ uint32_t bucket_idx = hash_val[rule_id] >>
+ EFD_LOOKUPTBL_SHIFT;
+
+ /*
+ * Get the current bit of interest.
+ * This only find an appropriate hash function
+ * for one bit at a time of the rule
+ */
+ efd_lookuptbl_t expected =
+ (off_group->value[rule_id] >> i) & 0x1;
+
+ /*
+ * Add the expected bit (if set) to a map
+ * (lookup_table). Also set its complement
+ * in lookup_table_complement
+ */
+ lookup_table |= expected << bucket_idx;
+ lookup_table_complement |= (1 - expected)
+ << bucket_idx;
+
+ /*
+ * If ever the hash function of two different
+ * elements result in different values at the
+ * same location in the lookup_table,
+ * the current hash_idx is not valid.
+ */
+ if (lookup_table & lookup_table_complement)
+ break;
+ }
+
+ /*
+ * Check if the previous loop completed without
+ * breaking early
+ */
+ if (rule_id == off_group->num_rules) {
+ /*
+ * Current hash function worked, store it
+ * for the current group
+ */
+ on_group->hash_idx[i] = hash_idx;
+ on_group->lookup_table[i] = lookup_table;
+
+ /*
+ * Make sure that the hash function has changed
+ * from the starting value
+ */
+ hash_idx = start_hash_idx[i] + 1;
+ break;
+ }
+ hash_idx++;
+
+ } while (hash_idx != start_hash_idx[i]);
+
+ /* Failed to find perfect hash for this group */
+ if (hash_idx == start_hash_idx[i]) {
+ /*
+ * Restore previous hash_idx and lookup_table
+ * for all value bits
+ */
+ for (j = 0; j < i; j++) {
+ on_group->hash_idx[j] = start_hash_idx[j];
+ on_group->lookup_table[j] = start_lookup_table[j];
+ }
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+struct rte_efd_table *
+rte_efd_create(const char *name, uint32_t max_num_rules, uint32_t key_len,
+ uint8_t online_cpu_socket_bitmask, uint8_t offline_cpu_socket)
+{
+ struct rte_efd_table *table = NULL;
+ uint8_t *key_array = NULL;
+ uint32_t num_chunks, num_chunks_shift;
+ uint8_t socket_id;
+ struct rte_efd_list *efd_list = NULL;
+ struct rte_tailq_entry *te;
+ uint64_t offline_table_size;
+ char ring_name[RTE_RING_NAMESIZE];
+ struct rte_ring *r = NULL;
+ unsigned int i;
+
+ efd_list = RTE_TAILQ_CAST(rte_efd_tailq.head, rte_efd_list);
+
+ if (online_cpu_socket_bitmask == 0) {
+ RTE_LOG(ERR, EFD, "At least one CPU socket must be enabled "
+ "in the bitmask\n");
+ return NULL;
+ }
+
+ if (max_num_rules == 0) {
+ RTE_LOG(ERR, EFD, "Max num rules must be higher than 0\n");
+ return NULL;
+ }
+
+ /*
+ * Compute the minimum number of chunks (smallest power of 2)
+ * that can hold all of the rules
+ */
+ if (max_num_rules % EFD_TARGET_CHUNK_NUM_RULES == 0)
+ num_chunks = rte_align32pow2(max_num_rules /
+ EFD_TARGET_CHUNK_NUM_RULES);
+ else
+ num_chunks = rte_align32pow2((max_num_rules /
+ EFD_TARGET_CHUNK_NUM_RULES) + 1);
+
+ num_chunks_shift = log2(num_chunks);
+
+ rte_rwlock_write_lock(RTE_EAL_TAILQ_RWLOCK);
+
+ /*
+ * Guarantee there's no existing: this is normally already checked
+ * by ring creation above
+ */
+ TAILQ_FOREACH(te, efd_list, next)
+ {
+ table = (struct rte_efd_table *) te->data;
+ if (strncmp(name, table->name, RTE_EFD_NAMESIZE) == 0)
+ break;
+ }
+
+ table = NULL;
+ if (te != NULL) {
+ rte_errno = EEXIST;
+ te = NULL;
+ goto error_unlock_exit;
+ }
+
+ te = rte_zmalloc("EFD_TAILQ_ENTRY", sizeof(*te), 0);
+ if (te == NULL) {
+ RTE_LOG(ERR, EFD, "tailq entry allocation failed\n");
+ goto error_unlock_exit;
+ }
+
+ /* Create a new EFD table management structure */
+ table = (struct rte_efd_table *) rte_zmalloc_socket(NULL,
+ sizeof(struct rte_efd_table),
+ RTE_CACHE_LINE_SIZE,
+ offline_cpu_socket);
+ if (table == NULL) {
+ RTE_LOG(ERR, EFD, "Allocating EFD table management structure"
+ " on socket %u failed\n",
+ offline_cpu_socket);
+ goto error_unlock_exit;
+ }
+
+
+ RTE_LOG(DEBUG, EFD, "Allocated EFD table management structure "
+ "on socket %u\n", offline_cpu_socket);
+
+ table->max_num_rules = num_chunks * EFD_TARGET_CHUNK_MAX_NUM_RULES;
+ table->num_rules = 0;
+ table->num_chunks = num_chunks;
+ table->num_chunks_shift = num_chunks_shift;
+ table->key_len = key_len;
+
+ /* key_array */
+ key_array = (uint8_t *) rte_zmalloc_socket(NULL,
+ table->max_num_rules * table->key_len,
+ RTE_CACHE_LINE_SIZE,
+ offline_cpu_socket);
+ if (key_array == NULL) {
+ RTE_LOG(ERR, EFD, "Allocating key array"
+ " on socket %u failed\n",
+ offline_cpu_socket);
+ goto error_unlock_exit;
+ }
+ table->keys = key_array;
+ snprintf(table->name, sizeof(table->name), "%s", name);
+
+ RTE_LOG(DEBUG, EFD, "Creating an EFD table with %u chunks,"
+ " which potentially supports %u entries\n",
+ num_chunks, table->max_num_rules);
+
+ /* Make sure all the allocatable table pointers are NULL initially */
+ for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++)
+ table->chunks[socket_id] = NULL;
+ table->offline_chunks = NULL;
+
+ /*
+ * Allocate one online table per socket specified
+ * in the user-supplied bitmask
+ */
+ uint64_t online_table_size = num_chunks * sizeof(struct efd_online_chunk) +
+ EFD_NUM_CHUNK_PADDING_BYTES;
+
+ for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++) {
+ if ((online_cpu_socket_bitmask >> socket_id) & 0x01) {
+ /*
+ * Allocate all of the EFD table chunks (the online portion)
+ * as a continuous block
+ */
+ table->chunks[socket_id] =
+ (struct efd_online_chunk *) rte_zmalloc_socket(
+ NULL,
+ online_table_size,
+ RTE_CACHE_LINE_SIZE,
+ socket_id);
+ if (table->chunks[socket_id] == NULL) {
+ RTE_LOG(ERR, EFD,
+ "Allocating EFD online table on "
+ "socket %u failed\n",
+ socket_id);
+ goto error_unlock_exit;
+ }
+ RTE_LOG(DEBUG, EFD,
+ "Allocated EFD online table of size "
+ "%"PRIu64" bytes (%.2f MB) on socket %u\n",
+ online_table_size,
+ (float) online_table_size /
+ (1024.0F * 1024.0F),
+ socket_id);
+ }
+ }
+
+#if defined(RTE_ARCH_X86)
+ if (RTE_EFD_VALUE_NUM_BITS > 3 && rte_cpu_get_flag_enabled(RTE_CPUFLAG_AVX2))
+ table->cmp_fn = RTE_HASH_COMPARE_AVX2;
+ else
+#endif
+ table->cmp_fn = RTE_HASH_COMPARE_SCALAR;
+
+ /*
+ * Allocate the EFD table offline portion (with the actual rules
+ * mapping keys to values) as a continuous block.
+ * This could be several gigabytes of memory.
+ */
+ offline_table_size = num_chunks * sizeof(struct efd_offline_chunk_rules);
+ table->offline_chunks =
+ (struct efd_offline_chunk_rules *) rte_zmalloc_socket(NULL,
+ offline_table_size,
+ RTE_CACHE_LINE_SIZE,
+ offline_cpu_socket);
+ if (table->offline_chunks == NULL) {
+ RTE_LOG(ERR, EFD, "Allocating EFD offline table on socket %u "
+ "failed\n", offline_cpu_socket);
+ goto error_unlock_exit;
+ }
+
+ RTE_LOG(DEBUG, EFD,
+ "Allocated EFD offline table of size %"PRIu64" bytes "
+ " (%.2f MB) on socket %u\n", offline_table_size,
+ (float) offline_table_size / (1024.0F * 1024.0F),
+ offline_cpu_socket);
+
+ te->data = (void *) table;
+ TAILQ_INSERT_TAIL(efd_list, te, next);
+ rte_rwlock_write_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+ snprintf(ring_name, sizeof(ring_name), "HT_%s", table->name);
+ /* Create ring (Dummy slot index is not enqueued) */
+ r = rte_ring_create(ring_name, rte_align32pow2(table->max_num_rules),
+ offline_cpu_socket, 0);
+ if (r == NULL) {
+ RTE_LOG(ERR, EFD, "memory allocation failed\n");
+ goto error_unlock_exit;
+ }
+
+ /* Populate free slots ring. Entry zero is reserved for key misses. */
+ for (i = 0; i < table->max_num_rules; i++)
+ rte_ring_sp_enqueue(r, (void *) ((uintptr_t) i));
+
+ table->free_slots = r;
+ return table;
+
+error_unlock_exit:
+ rte_rwlock_write_unlock(RTE_EAL_TAILQ_RWLOCK);
+ rte_efd_free(table);
+
+ return NULL;
+}
+
+struct rte_efd_table *
+rte_efd_find_existing(const char *name)
+{
+ struct rte_efd_table *table = NULL;
+ struct rte_tailq_entry *te;
+ struct rte_efd_list *efd_list;
+
+ efd_list = RTE_TAILQ_CAST(rte_efd_tailq.head, rte_efd_list);
+
+ rte_rwlock_read_lock(RTE_EAL_TAILQ_RWLOCK);
+
+ TAILQ_FOREACH(te, efd_list, next)
+ {
+ table = (struct rte_efd_table *) te->data;
+ if (strncmp(name, table->name, RTE_EFD_NAMESIZE) == 0)
+ break;
+ }
+ rte_rwlock_read_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+ if (te == NULL) {
+ rte_errno = ENOENT;
+ return NULL;
+ }
+ return table;
+}
+
+void
+rte_efd_free(struct rte_efd_table *table)
+{
+ uint8_t socket_id;
+
+ if (table == NULL)
+ return;
+
+ for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++)
+ rte_free(table->chunks[socket_id]);
+
+ rte_ring_free(table->free_slots);
+ rte_free(table->offline_chunks);
+ rte_free(table->keys);
+ rte_free(table);
+}
+
+/**
+ * Applies a previously computed table entry to the specified table for all
+ * socket-local copies of the online table.
+ * Intended to apply an update for only a single change
+ * to a key/value pair at a time
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing values (ideally caller's socket id)
+ * @param chunk_id
+ * Chunk index to update
+ * @param group_id
+ * Group index to update
+ * @param bin_id
+ * Bin within the group that this update affects
+ * @param new_bin_choice
+ * Newly chosen permutation which this bin should use - only lower 2 bits
+ * @param new_group_entry
+ * Previously computed updated chunk/group entry
+ */
+static inline void
+efd_apply_update(struct rte_efd_table * const table, const unsigned int socket_id,
+ const uint32_t chunk_id, const uint32_t group_id,
+ const uint32_t bin_id, const uint8_t new_bin_choice,
+ const struct efd_online_group_entry * const new_group_entry)
+{
+ int i;
+ struct efd_online_chunk *chunk = &table->chunks[socket_id][chunk_id];
+ uint8_t bin_index = bin_id / EFD_CHUNK_NUM_BIN_TO_GROUP_SETS;
+
+ /*
+ * Grab the current byte that contains the choices
+ * for four neighboring bins
+ */
+ uint8_t choice_chunk =
+ chunk->bin_choice_list[bin_index];
+
+
+ /* Compute the offset into the chunk that needs to be updated */
+ int offset = (bin_id & 0x3) * 2;
+
+ /* Zero the two bits of interest and set them to new_bin_choice */
+ choice_chunk = (choice_chunk & (~(0x03 << offset)))
+ | ((new_bin_choice & 0x03) << offset);
+
+ /* Update the online table with the new data across all sockets */
+ for (i = 0; i < RTE_MAX_NUMA_NODES; i++) {
+ if (table->chunks[i] != NULL) {
+ memcpy(&(table->chunks[i][chunk_id].groups[group_id]),
+ new_group_entry,
+ sizeof(struct efd_online_group_entry));
+ table->chunks[i][chunk_id].bin_choice_list[bin_index] =
+ choice_chunk;
+ }
+ }
+}
+
+/*
+ * Move the bin from prev group to the new group
+ */
+static inline void
+move_groups(uint32_t bin_id, uint8_t bin_size,
+ struct efd_offline_group_rules *new_group,
+ struct efd_offline_group_rules * const current_group)
+{
+
+ uint8_t empty_idx = 0;
+ unsigned int i;
+
+ if (new_group == current_group)
+ return;
+
+ for (i = 0; i < current_group->num_rules; i++) {
+ /*
+ * Move keys that belong to the same bin
+ * to the new group
+ */
+ if (current_group->bin_id[i] == bin_id) {
+ new_group->key_idx[new_group->num_rules] =
+ current_group->key_idx[i];
+ new_group->value[new_group->num_rules] =
+ current_group->value[i];
+ new_group->bin_id[new_group->num_rules] =
+ current_group->bin_id[i];
+ new_group->num_rules++;
+ } else {
+ if (i != empty_idx) {
+ /*
+ * Need to move this key towards
+ * the top of the array
+ */
+ current_group->key_idx[empty_idx] =
+ current_group->key_idx[i];
+ current_group->value[empty_idx] =
+ current_group->value[i];
+ current_group->bin_id[empty_idx] =
+ current_group->bin_id[i];
+ }
+ empty_idx++;
+ }
+
+ }
+ current_group->num_rules -= bin_size;
+}
+
+/*
+ * Revert group/s to their previous state before
+ * trying to insert/add a new key
+ */
+static inline void
+revert_groups(struct efd_offline_group_rules *previous_group,
+ struct efd_offline_group_rules *current_group, uint8_t bin_size)
+{
+ unsigned int i;
+
+ if (current_group == previous_group)
+ return;
+
+ /* Move keys back to previous group */
+ for (i = current_group->num_rules - bin_size;
+ i < current_group->num_rules; i++) {
+ previous_group->key_idx[previous_group->num_rules] =
+ current_group->key_idx[i];
+ previous_group->value[previous_group->num_rules] =
+ current_group->value[i];
+ previous_group->bin_id[previous_group->num_rules] =
+ current_group->bin_id[i];
+ previous_group->num_rules++;
+ }
+
+ /*
+ * Decrease number of rules after the move
+ * in the new group
+ */
+ current_group->num_rules -= bin_size;
+}
+
+/**
+ * Computes an updated table entry where the supplied key points to a new host.
+ * If no entry exists, one is inserted.
+ *
+ * This function does NOT modify the online table(s)
+ * This function DOES modify the offline table
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing values (ideally caller's socket id)
+ * @param key
+ * Key to insert
+ * @param value
+ * Value to associate with key
+ * @param chunk_id
+ * Chunk ID of the chunk that was modified
+ * @param group_id
+ * Group ID of the group that was modified
+ * @param bin_id
+ * Bin ID that was modified
+ * @param new_bin_choice
+ * Newly chosen permutation which this bin will use
+ * @param entry
+ * Newly computed online entry to apply later with efd_apply_update
+ *
+ * @return
+ * RTE_EFD_UPDATE_WARN_GROUP_FULL
+ * Operation is insert, and the last available space in the
+ * key's group was just used. Future inserts may fail as groups fill up.
+ * This operation was still successful, and entry contains a valid update
+ * RTE_EFD_UPDATE_FAILED
+ * Either the EFD failed to find a suitable perfect hash or the group was full
+ * This is a fatal error, and the table is now in an indeterminite state
+ * RTE_EFD_UPDATE_NO_CHANGE
+ * Operation resulted in no change to the table (same value already exists)
+ * 0
+ * Insert or update was successful, and the new efd_online_group_entry
+ * is stored in *entry
+ *
+ * @warning
+ * Note that entry will be UNCHANGED if the update has no effect, and thus any
+ * subsequent use of the entry content will likely be invalid
+ */
+static inline int
+efd_compute_update(struct rte_efd_table * const table,
+ const unsigned int socket_id, const void *key,
+ const efd_value_t value, uint32_t * const chunk_id,
+ uint32_t * const group_id, uint32_t * const bin_id,
+ uint8_t * const new_bin_choice,
+ struct efd_online_group_entry * const entry)
+{
+ unsigned int i;
+ int ret;
+ uint32_t new_idx;
+ void *new_k, *slot_id = NULL;
+ int status = EXIT_SUCCESS;
+ unsigned int found = 0;
+
+ efd_compute_ids(table, key, chunk_id, bin_id);
+
+ struct efd_offline_chunk_rules * const chunk =
+ &table->offline_chunks[*chunk_id];
+ struct efd_offline_group_rules *new_group;
+
+ uint8_t current_choice = efd_get_choice(table, socket_id,
+ *chunk_id, *bin_id);
+ uint32_t current_group_id = efd_bin_to_group[current_choice][*bin_id];
+ struct efd_offline_group_rules * const current_group =
+ &chunk->group_rules[current_group_id];
+ uint8_t bin_size = 0;
+ uint8_t key_changed_index = 0;
+ efd_value_t key_changed_previous_value = 0;
+ uint32_t key_idx_previous = 0;
+
+ /* Scan the current group and see if the key is already present */
+ for (i = 0; i < current_group->num_rules; i++) {
+ if (current_group->bin_id[i] == *bin_id)
+ bin_size++;
+ else
+ continue;
+
+ void *key_stored = EFD_KEY(current_group->key_idx[i], table);
+ if (found == 0 && unlikely(memcmp(key_stored, key,
+ table->key_len) == 0)) {
+ /* Key is already present */
+
+ /*
+ * If previous value is same as new value,
+ * no additional work is required
+ */
+ if (current_group->value[i] == value)
+ return RTE_EFD_UPDATE_NO_CHANGE;
+
+ key_idx_previous = current_group->key_idx[i];
+ key_changed_previous_value = current_group->value[i];
+ key_changed_index = i;
+ current_group->value[i] = value;
+ found = 1;
+ }
+ }
+
+ if (found == 0) {
+ /* Key does not exist. Insert the rule into the bin/group */
+ if (unlikely(current_group->num_rules >= EFD_MAX_GROUP_NUM_RULES)) {
+ RTE_LOG(ERR, EFD,
+ "Fatal: No room remaining for insert into "
+ "chunk %u group %u bin %u\n",
+ *chunk_id,
+ current_group_id, *bin_id);
+ return RTE_EFD_UPDATE_FAILED;
+ }
+
+ if (unlikely(current_group->num_rules ==
+ (EFD_MAX_GROUP_NUM_RULES - 1))) {
+ RTE_LOG(INFO, EFD, "Warn: Insert into last "
+ "available slot in chunk %u "
+ "group %u bin %u\n", *chunk_id,
+ current_group_id, *bin_id);
+ status = RTE_EFD_UPDATE_WARN_GROUP_FULL;
+ }
+
+ if (rte_ring_sc_dequeue(table->free_slots, &slot_id) != 0)
+ return RTE_EFD_UPDATE_FAILED;
+
+ new_k = RTE_PTR_ADD(table->keys, (uintptr_t) slot_id *
+ table->key_len);
+ rte_prefetch0(new_k);
+ new_idx = (uint32_t) ((uintptr_t) slot_id);
+
+ rte_memcpy(EFD_KEY(new_idx, table), key, table->key_len);
+ current_group->key_idx[current_group->num_rules] = new_idx;
+ current_group->value[current_group->num_rules] = value;
+ current_group->bin_id[current_group->num_rules] = *bin_id;
+ current_group->num_rules++;
+ table->num_rules++;
+ bin_size++;
+ } else {
+ uint32_t last = current_group->num_rules - 1;
+ /* Swap the key with the last key inserted*/
+ current_group->key_idx[key_changed_index] =
+ current_group->key_idx[last];
+ current_group->value[key_changed_index] =
+ current_group->value[last];
+ current_group->bin_id[key_changed_index] =
+ current_group->bin_id[last];
+
+ /*
+ * Key to be updated will always be available
+ * at the end of the group
+ */
+ current_group->key_idx[last] = key_idx_previous;
+ current_group->value[last] = value;
+ current_group->bin_id[last] = *bin_id;
+ }
+
+ *new_bin_choice = current_choice;
+ *group_id = current_group_id;
+ new_group = current_group;
+
+ /* Group need to be rebalanced when it starts to get loaded */
+ if (current_group->num_rules > EFD_MIN_BALANCED_NUM_RULES) {
+
+ /*
+ * Subtract the number of entries in the bin from
+ * the original group
+ */
+ current_group->num_rules -= bin_size;
+
+ /*
+ * Figure out which of the available groups that this bin
+ * can map to is the smallest (using the current group
+ * as baseline)
+ */
+ uint8_t smallest_choice = current_choice;
+ uint8_t smallest_size = current_group->num_rules;
+ uint32_t smallest_group_id = current_group_id;
+ unsigned char choice;
+
+ for (choice = 0; choice < EFD_CHUNK_NUM_BIN_TO_GROUP_SETS;
+ choice++) {
+ uint32_t test_group_id =
+ efd_bin_to_group[choice][*bin_id];
+ uint32_t num_rules =
+ chunk->group_rules[test_group_id].num_rules;
+ if (num_rules < smallest_size) {
+ smallest_choice = choice;
+ smallest_size = num_rules;
+ smallest_group_id = test_group_id;
+ }
+ }
+
+ *new_bin_choice = smallest_choice;
+ *group_id = smallest_group_id;
+ new_group = &chunk->group_rules[smallest_group_id];
+ current_group->num_rules += bin_size;
+
+ }
+
+ uint8_t choice = 0;
+ for (;;) {
+ if (current_group != new_group &&
+ new_group->num_rules + bin_size >
+ EFD_MAX_GROUP_NUM_RULES) {
+ RTE_LOG(DEBUG, EFD,
+ "Unable to move_groups to dest group "
+ "containing %u entries."
+ "bin_size:%u choice:%02x\n",
+ new_group->num_rules, bin_size,
+ choice - 1);
+ goto next_choice;
+ }
+ move_groups(*bin_id, bin_size, new_group, current_group);
+ /*
+ * Recompute the hash function for the modified group,
+ * and return it to the caller
+ */
+ ret = efd_search_hash(table, new_group, entry);
+
+ if (!ret)
+ return status;
+
+ RTE_LOG(DEBUG, EFD,
+ "Failed to find perfect hash for group "
+ "containing %u entries. bin_size:%u choice:%02x\n",
+ new_group->num_rules, bin_size, choice - 1);
+ /* Restore groups modified to their previous state */
+ revert_groups(current_group, new_group, bin_size);
+
+next_choice:
+ if (choice == EFD_CHUNK_NUM_BIN_TO_GROUP_SETS)
+ break;
+ *new_bin_choice = choice;
+ *group_id = efd_bin_to_group[choice][*bin_id];
+ new_group = &chunk->group_rules[*group_id];
+ choice++;
+ }
+
+ if (!found) {
+ current_group->num_rules--;
+ table->num_rules--;
+ } else
+ current_group->value[current_group->num_rules - 1] =
+ key_changed_previous_value;
+ return RTE_EFD_UPDATE_FAILED;
+}
+
+int
+rte_efd_update(struct rte_efd_table * const table, const unsigned int socket_id,
+ const void *key, const efd_value_t value)
+{
+ uint32_t chunk_id = 0, group_id = 0, bin_id = 0;
+ uint8_t new_bin_choice = 0;
+ struct efd_online_group_entry entry;
+
+ int status = efd_compute_update(table, socket_id, key, value,
+ &chunk_id, &group_id, &bin_id,
+ &new_bin_choice, &entry);
+
+ if (status == RTE_EFD_UPDATE_NO_CHANGE)
+ return EXIT_SUCCESS;
+
+ if (status == RTE_EFD_UPDATE_FAILED)
+ return status;
+
+ efd_apply_update(table, socket_id, chunk_id, group_id, bin_id,
+ new_bin_choice, &entry);
+ return status;
+}
+
+int
+rte_efd_delete(struct rte_efd_table * const table, const unsigned int socket_id,
+ const void *key, efd_value_t * const prev_value)
+{
+ unsigned int i;
+ uint32_t chunk_id, bin_id;
+ uint8_t not_found = 1;
+
+ efd_compute_ids(table, key, &chunk_id, &bin_id);
+
+ struct efd_offline_chunk_rules * const chunk =
+ &table->offline_chunks[chunk_id];
+
+ uint8_t current_choice = efd_get_choice(table, socket_id,
+ chunk_id, bin_id);
+ uint32_t current_group_id = efd_bin_to_group[current_choice][bin_id];
+ struct efd_offline_group_rules * const current_group =
+ &chunk->group_rules[current_group_id];
+
+ /*
+ * Search the current group for the specified key.
+ * If it exists, remove it and re-pack the other values
+ */
+ for (i = 0; i < current_group->num_rules; i++) {
+ if (not_found) {
+ /* Found key that needs to be removed */
+ if (memcmp(EFD_KEY(current_group->key_idx[i], table),
+ key, table->key_len) == 0) {
+ /* Store previous value if requested by caller */
+ if (prev_value != NULL)
+ *prev_value = current_group->value[i];
+
+ not_found = 0;
+ rte_ring_sp_enqueue(table->free_slots,
+ (void *)((uintptr_t)current_group->key_idx[i]));
+ }
+ } else {
+ /*
+ * If the desired key has been found,
+ * need to shift other values up one
+ */
+
+ /* Need to shift this entry back up one index */
+ current_group->key_idx[i - 1] = current_group->key_idx[i];
+ current_group->value[i - 1] = current_group->value[i];
+ current_group->bin_id[i - 1] = current_group->bin_id[i];
+ }
+ }
+
+ if (not_found == 0) {
+ table->num_rules--;
+ current_group->num_rules--;
+ }
+
+ return not_found;
+}
+
+
+#if (RTE_EFD_VALUE_NUM_BITS == 8 || RTE_EFD_VALUE_NUM_BITS == 16 || \
+ RTE_EFD_VALUE_NUM_BITS == 24 || RTE_EFD_VALUE_NUM_BITS == 32)
+#define EFD_LOAD_SI128(val) _mm_load_si128(val)
+#else
+#define EFD_LOAD_SI128(val) _mm_lddqu_si128(val)
+#endif
+
+static inline efd_value_t
+efd_lookup_internal(const struct efd_online_group_entry * const group,
+ const uint32_t hash_val_a, const uint32_t hash_val_b,
+ enum rte_efd_compare_function cmp_fn)
+{
+ efd_value_t value = 0;
+ uint32_t i;
+
+ switch (cmp_fn) {
+#ifdef RTE_MACHINE_CPUFLAG_AVX2
+ case RTE_HASH_COMPARE_AVX2:
+
+ i = 0;
+ __m256i vhash_val_a = _mm256_set1_epi32(hash_val_a);
+ __m256i vhash_val_b = _mm256_set1_epi32(hash_val_b);
+
+ for (; i < RTE_EFD_VALUE_NUM_BITS; i += 8) {
+ __m256i vhash_idx =
+ _mm256_cvtepu16_epi32(EFD_LOAD_SI128(
+ (__m128i const *) &group->hash_idx[i]));
+ __m256i vlookup_table = _mm256_cvtepu16_epi32(
+ EFD_LOAD_SI128((__m128i const *)
+ &group->lookup_table[i]));
+ __m256i vhash = _mm256_add_epi32(vhash_val_a,
+ _mm256_mullo_epi32(vhash_idx, vhash_val_b));
+ __m256i vbucket_idx = _mm256_srli_epi32(vhash,
+ EFD_LOOKUPTBL_SHIFT);
+ __m256i vresult = _mm256_srlv_epi32(vlookup_table,
+ vbucket_idx);
+
+ value |= (_mm256_movemask_ps(
+ (__m256) _mm256_slli_epi32(vresult, 31))
+ & ((1 << (RTE_EFD_VALUE_NUM_BITS - i)) - 1)) << i;
+ }
+ break;
+#endif
+ default:
+
+ i = 0;
+ for (; i < RTE_EFD_VALUE_NUM_BITS; i++) {
+ value <<= 1;
+
+ uint32_t h = hash_val_a + (hash_val_b *
+ group->hash_idx[RTE_EFD_VALUE_NUM_BITS - i - 1]);
+ uint16_t bucket_idx = h >> EFD_LOOKUPTBL_SHIFT;
+
+ value |= (group->lookup_table[
+ RTE_EFD_VALUE_NUM_BITS - i - 1] >>
+ bucket_idx) & 0x1;
+ }
+ }
+ return value;
+}
+
+efd_value_t
+rte_efd_lookup(const struct rte_efd_table * const table,
+ const unsigned int socket_id, const void *key)
+{
+ uint32_t chunk_id, group_id, bin_id;
+ uint8_t bin_choice;
+ const struct efd_online_group_entry *group;
+ const struct efd_online_chunk * const chunks = table->chunks[socket_id];
+
+ /* Determine the chunk and group location for the given key */
+ efd_compute_ids(table, key, &chunk_id, &bin_id);
+ bin_choice = efd_get_choice(table, socket_id, chunk_id, bin_id);
+ group_id = efd_bin_to_group[bin_choice][bin_id];
+ group = &chunks[chunk_id].groups[group_id];
+
+ return efd_lookup_internal(group,
+ EFD_HASHFUNCA(key, table),
+ EFD_HASHFUNCB(key, table),
+ table->cmp_fn);
+}
+
+void rte_efd_lookup_bulk(const struct rte_efd_table * const table,
+ const unsigned int socket_id, const int num_keys,
+ const void **key_list, efd_value_t * const value_list)
+{
+ int i;
+ uint32_t chunk_id_list[RTE_EFD_BURST_MAX];
+ uint32_t bin_id_list[RTE_EFD_BURST_MAX];
+ uint8_t bin_choice_list[RTE_EFD_BURST_MAX];
+ uint32_t group_id_list[RTE_EFD_BURST_MAX];
+ struct efd_online_group_entry *group;
+
+ struct efd_online_chunk *chunks = table->chunks[socket_id];
+
+ for (i = 0; i < num_keys; i++) {
+ efd_compute_ids(table, key_list[i], &chunk_id_list[i],
+ &bin_id_list[i]);
+ rte_prefetch0(&chunks[chunk_id_list[i]].bin_choice_list);
+ }
+
+ for (i = 0; i < num_keys; i++) {
+ bin_choice_list[i] = efd_get_choice(table, socket_id,
+ chunk_id_list[i], bin_id_list[i]);
+ group_id_list[i] =
+ efd_bin_to_group[bin_choice_list[i]][bin_id_list[i]];
+ group = &chunks[chunk_id_list[i]].groups[group_id_list[i]];
+ rte_prefetch0(group);
+ }
+
+ for (i = 0; i < num_keys; i++) {
+ group = &chunks[chunk_id_list[i]].groups[group_id_list[i]];
+ value_list[i] = efd_lookup_internal(group,
+ EFD_HASHFUNCA(key_list[i], table),
+ EFD_HASHFUNCB(key_list[i], table),
+ table->cmp_fn);
+ }
+}
diff --git a/lib/librte_efd/rte_efd.h b/lib/librte_efd/rte_efd.h
new file mode 100644
index 0000000..cb614c7
--- /dev/null
+++ b/lib/librte_efd/rte_efd.h
@@ -0,0 +1,294 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _RTE_EFD_H_
+#define _RTE_EFD_H_
+
+/**
+ * @file
+ *
+ * RTE EFD Table
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*************************************************************************
+ * User selectable constants
+ *************************************************************************/
+
+/*
+ * If possible, best lookup performance will be achieved by ensuring that
+ * the entire table fits in the L3 cache.
+ *
+ * Some formulas for calculating various sizes are listed below:
+ *
+ * # of chunks =
+ * 2 ^ (ceiling(log2((requested # of rules) /
+ * (EFD_CHUNK_NUM_GROUPS * EFD_TARGET_GROUP_NUM_RULES))))
+ *
+ * Target # of rules = (# of chunks) * EFD_CHUNK_NUM_GROUPS *
+ * EFD_TARGET_GROUP_NUM_RULES
+ *
+ * Group Size (in bytes) = 4 (per value bit)
+ *
+ * Table size (in bytes) = RTE_EFD_VALUE_NUM_BITS * (# of chunks) *
+ * EFD_CHUNK_NUM_GROUPS * (group size)
+ */
+
+/**
+ * !!! This parameter should be adjusted for your application !!!
+ *
+ * This parameter adjusts the number of bits of value that can be
+ * stored in the table.
+ * For example, setting the number of bits to 3 will allow storing 8 values
+ * in the table (between 0 and 7).
+ *
+ * This number directly affects the performance of both lookups and insertion.
+ * In general, performance decreases as more bits are stored in the table.
+ *
+ * This number is directly proportional to the size of the online region
+ * used for lookups.
+ *
+ * Note that due to the way the CPU operates on memory, best lookup performance
+ * will be achieved when RTE_EFD_VALUE_NUM_BITS is a multiple of 8.
+ * These values align the hash indexes on 16-byte boundaries.
+ * The greatest performance drop is moving from 8->9 bits, 16->17 bits, etc.
+ *
+ * This value must be between 1 and 32
+ */
+#ifndef RTE_EFD_VALUE_NUM_BITS
+#define RTE_EFD_VALUE_NUM_BITS (8)
+#endif
+
+/*
+ * EFD_TARGET_GROUP_NUM_RULES:
+ * Adjusts how many groups/chunks are allocated at table creation time
+ * to support the requested number of rules. Higher values pack entries
+ * more tightly in memory, resulting in a smaller memory footprint
+ * for the online table.
+ * This comes at the cost of lower insert/update performance.
+ *
+ * EFD_MAX_GROUP_NUM_RULES:
+ * This adjusts the amount of offline memory allocated to store key/value
+ * pairs for the table. The recommended numbers are upper-bounds for
+ * this parameter
+ * - any higher and it becomes very unlikely that a perfect hash function
+ * can be found for that group size. This value should be at
+ * least 40% larger than EFD_TARGET_GROUP_NUM_RULES
+ *
+ * Recommended values for various lookuptable and hashfunc sizes are:
+ *
+ * HASH_FUNC_SIZE = 16, LOOKUPTBL_SIZE = 16:
+ * EFD_TARGET_GROUP_NUM_RULES = 22
+ * EFD_MAX_GROUP_NUM_RULES = 28
+ */
+#define EFD_TARGET_GROUP_NUM_RULES (22)
+#define EFD_MAX_GROUP_NUM_RULES (28LU)
+
+#define EFD_MIN_BALANCED_NUM_RULES 5
+
+/**
+ * Maximum number of keys that can be looked up in one call to efd_lookup_bulk
+ */
+#ifndef RTE_EFD_BURST_MAX
+#define RTE_EFD_BURST_MAX (32)
+#endif
+
+/** Maximum number of characters in efd name.*/
+#define RTE_EFD_NAMESIZE 32
+
+#if (RTE_EFD_VALUE_NUM_BITS > 0 && RTE_EFD_VALUE_NUM_BITS <= 8)
+typedef uint8_t efd_value_t;
+#elif (RTE_EFD_VALUE_NUM_BITS > 8 && RTE_EFD_VALUE_NUM_BITS <= 16)
+typedef uint16_t efd_value_t;
+#elif (RTE_EFD_VALUE_NUM_BITS > 16 && RTE_EFD_VALUE_NUM_BITS <= 32)
+typedef uint32_t efd_value_t;
+#else
+#error("RTE_EFD_VALUE_NUM_BITS must be in the range [1:32]")
+#endif
+
+/**
+ * Creates an EFD table with a single offline region and multiple per-socket
+ * internally-managed copies of the online table used for lookups
+ *
+ * @param name
+ * EFD table name
+ * @param max_num_rules
+ * Minimum number of rules the table should be sized to hold.
+ * Will be rounded up to the next smallest valid table size
+ * @param online_cpu_socket_bitmask
+ * Bitmask specifying which sockets should get a copy of the online table.
+ * LSB = socket 0, etc.
+ * @param offline_cpu_socket
+ * Identifies the socket where the offline table will be allocated
+ * (and most efficiently accessed in the case of updates/insertions)
+ *
+ * @return
+ * EFD table, or NULL if table allocation failed or the bitmask is invalid
+ */
+struct rte_efd_table *
+rte_efd_create(const char *name, uint32_t max_num_rules, uint32_t key_len,
+ uint8_t online_cpu_socket_bitmask, uint8_t offline_cpu_socket);
+
+/**
+ * Releases the resources from an EFD table
+ *
+ * @param table
+ * Table to free
+ */
+void
+rte_efd_free(struct rte_efd_table *table);
+
+/**
+ * Find an existing EFD table object and return a pointer to it.
+ *
+ * @param name
+ * Name of the EFD table as passed to rte_efd_create()
+ * @return
+ * Pointer to EFD table or NULL if object not found
+ * with rte_errno set appropriately. Possible rte_errno values include:
+ * - ENOENT - value not available for return
+ */
+struct rte_efd_table*
+rte_efd_find_existing(const char *name);
+
+#define RTE_EFD_UPDATE_WARN_GROUP_FULL (1)
+#define RTE_EFD_UPDATE_NO_CHANGE (2)
+#define RTE_EFD_UPDATE_FAILED (3)
+
+/**
+ * Computes an updated table entry for the supplied key/value pair.
+ * The update is then immediately applied to the provided table and
+ * all socket-local copies of the chunks are updated.
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param key
+ * EFD table key to modify
+ * @param value
+ * Value to associate with the key
+ *
+ * @return
+ * RTE_EFD_UPDATE_WARN_GROUP_FULL
+ * Operation is insert, and the last available space in the
+ * key's group was just used
+ * Future inserts may fail as groups fill up
+ * This operation was still successful, and entry contains a valid update
+ * RTE_EFD_UPDATE_FAILED
+ * Either the EFD failed to find a suitable perfect hash or the group was full
+ * This is a fatal error, and the table is now in an indeterminite state
+ * RTE_EFD_UPDATE_NO_CHANGE
+ * Operation resulted in no change to the table (same value already exists)
+ * 0 - success
+ */
+int
+rte_efd_update(struct rte_efd_table *table, unsigned int socket_id,
+ const void *key, efd_value_t value);
+
+/**
+ * Removes any value currently associated with the specified key from the table
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param key
+ * EFD table key to delete
+ * @param prev_value
+ * If not NULL, will store the previous value here before deleting it
+ *
+ * @return
+ * 0 - successfully found and deleted the key
+ * nonzero otherwise
+ */
+int
+rte_efd_delete(struct rte_efd_table *table, unsigned int socket_id,
+ const void *key, efd_value_t *prev_value);
+
+/**
+ * Looks up the value associated with a key
+ *
+ * NOTE: Lookups will *always* succeed - this is a property of
+ * using a perfect hash table.
+ * If the specified key was never inserted, a pseudorandom answer will be returned.
+ * There is no way to know based on the lookup if the key was ever inserted
+ * originally, so this must be tracked elsewhere.
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param key
+ * EFD table key to look up
+ *
+ * @return
+ * Value associated with the key, or random junk if they key was never inserted
+ */
+efd_value_t
+rte_efd_lookup(const struct rte_efd_table *table, unsigned int socket_id,
+ const void *key);
+
+/**
+ * Looks up the value associated with several keys.
+ *
+ * NOTE: Lookups will *always* succeed - this is a property of
+ * using a perfect hash table.
+ * If the specified key was never inserted, a pseudorandom answer will be returned.
+ * There is no way to know based on the lookup if the key was ever inserted
+ * originally, so this must be tracked elsewhere.
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param num_keys
+ * Number of keys in the key_list array, must be less than RTE_EFD_BURST_MAX
+ * @param key_list
+ * Array of num_keys pointers which point to keys to look up
+ * @param value_list
+ * Array of size num_keys where lookup values will be stored
+ */
+void
+rte_efd_lookup_bulk(const struct rte_efd_table *table, unsigned int socket_id,
+ int num_keys, const void **key_list,
+ efd_value_t *value_list);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RTE_EFD_H_ */
diff --git a/lib/librte_efd/rte_efd_version.map b/lib/librte_efd/rte_efd_version.map
new file mode 100644
index 0000000..91810b3
--- /dev/null
+++ b/lib/librte_efd/rte_efd_version.map
@@ -0,0 +1,12 @@
+DPDK_17.02 {
+ global:
+
+ rte_efd_create;
+ rte_efd_delete;
+ rte_efd_free;
+ rte_efd_lookup;
+ rte_efd_lookup_bulk;
+ rte_efd_update;
+
+ local: *;
+};
diff --git a/mk/rte.app.mk b/mk/rte.app.mk
index f75f0e2..3956849 100644
--- a/mk/rte.app.mk
+++ b/mk/rte.app.mk
@@ -86,6 +86,7 @@ _LDLIBS-y += --whole-archive
_LDLIBS-$(CONFIG_RTE_LIBRTE_TIMER) += -lrte_timer
_LDLIBS-$(CONFIG_RTE_LIBRTE_HASH) += -lrte_hash
+_LDLIBS-$(CONFIG_RTE_LIBRTE_EFD) += -lrte_efd
_LDLIBS-$(CONFIG_RTE_LIBRTE_VHOST) += -lrte_vhost
_LDLIBS-$(CONFIG_RTE_LIBRTE_KVARGS) += -lrte_kvargs
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v2 2/5] app/test: add EFD functional and perf tests
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 0/5] Elastic Flow Distributor Pablo de Lara
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 1/5] efd: new Elastic Flow Distributor library Pablo de Lara
@ 2017-01-07 1:06 ` Pablo de Lara
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 3/5] examples/flow_distributor: sample app to demonstrate EFD usage Pablo de Lara
` (4 subsequent siblings)
6 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-07 1:06 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Byron Marohn, Karla Saur, Saikrishna Edupuganti
Signed-off-by: Byron Marohn <byron.marohn@intel.com>
Signed-off-by: Karla Saur <karla.saur@intel.com>
Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
---
MAINTAINERS | 1 +
app/test/Makefile | 3 +
app/test/test_efd.c | 494 +++++++++++++++++++++++++++++++++++++++++++++++
app/test/test_efd_perf.c | 407 ++++++++++++++++++++++++++++++++++++++
4 files changed, 905 insertions(+)
create mode 100644 app/test/test_efd.c
create mode 100644 app/test/test_efd_perf.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 9c60d67..d812962 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -532,6 +532,7 @@ EFD
M: Byron Marohn <byron.marohn@intel.com>
M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
F: lib/librte_efd/
+F: app/test/test_efd*
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
diff --git a/app/test/Makefile b/app/test/Makefile
index 5be023a..5adb26d 100644
--- a/app/test/Makefile
+++ b/app/test/Makefile
@@ -123,6 +123,9 @@ SRCS-y += test_logs.c
SRCS-y += test_memcpy.c
SRCS-y += test_memcpy_perf.c
+SRCS-$(CONFIG_RTE_LIBRTE_EFD) += test_efd.c
+SRCS-$(CONFIG_RTE_LIBRTE_EFD) += test_efd_perf.c
+
SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_hash.c
SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_thash.c
SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_hash_perf.c
diff --git a/app/test/test_efd.c b/app/test/test_efd.c
new file mode 100644
index 0000000..1cc8608
--- /dev/null
+++ b/app/test/test_efd.c
@@ -0,0 +1,494 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rte_memcpy.h>
+#include <rte_malloc.h>
+#include <rte_efd.h>
+#include <rte_byteorder.h>
+#include <rte_random.h>
+#include <rte_debug.h>
+#include <rte_ip.h>
+
+#include "test.h"
+
+#define EFD_TEST_KEY_LEN 8
+#define TABLE_SIZE (1 << 21)
+#define ITERATIONS 3
+static unsigned int test_socket_id;
+
+/* 5-tuple key type */
+struct flow_key {
+ uint32_t ip_src;
+ uint32_t ip_dst;
+ uint16_t port_src;
+ uint16_t port_dst;
+ uint8_t proto;
+} __attribute__((packed));
+/*
+ * Print out result of unit test efd operation.
+ */
+#if defined(UNIT_TEST_EFD_VERBOSE)
+
+static void print_key_info(const char *msg, const struct flow_key *key,
+ efd_value_t val)
+{
+ const uint8_t *p = (const uint8_t *) key;
+ unsigned int i;
+
+ printf("%s key:0x", msg);
+ for (i = 0; i < sizeof(struct flow_key); i++)
+ printf("%02X", p[i]);
+
+ printf(" @ val %d\n", val);
+}
+#else
+
+static void print_key_info(__attribute__((unused)) const char *msg,
+ __attribute__((unused)) const struct flow_key *key,
+ __attribute__((unused)) efd_value_t val)
+{
+}
+#endif
+
+/* Keys used by unit test functions */
+static struct flow_key keys[5] = {
+ {
+ .ip_src = IPv4(0x03, 0x02, 0x01, 0x00),
+ .ip_dst = IPv4(0x07, 0x06, 0x05, 0x04),
+ .port_src = 0x0908,
+ .port_dst = 0x0b0a,
+ .proto = 0x0c,
+ },
+ {
+ .ip_src = IPv4(0x13, 0x12, 0x11, 0x10),
+ .ip_dst = IPv4(0x17, 0x16, 0x15, 0x14),
+ .port_src = 0x1918,
+ .port_dst = 0x1b1a,
+ .proto = 0x1c,
+ },
+ {
+ .ip_src = IPv4(0x23, 0x22, 0x21, 0x20),
+ .ip_dst = IPv4(0x27, 0x26, 0x25, 0x24),
+ .port_src = 0x2928,
+ .port_dst = 0x2b2a,
+ .proto = 0x2c,
+ },
+ {
+ .ip_src = IPv4(0x33, 0x32, 0x31, 0x30),
+ .ip_dst = IPv4(0x37, 0x36, 0x35, 0x34),
+ .port_src = 0x3938,
+ .port_dst = 0x3b3a,
+ .proto = 0x3c,
+ },
+ {
+ .ip_src = IPv4(0x43, 0x42, 0x41, 0x40),
+ .ip_dst = IPv4(0x47, 0x46, 0x45, 0x44),
+ .port_src = 0x4948,
+ .port_dst = 0x4b4a,
+ .proto = 0x4c,
+ }
+};
+/* Array to store the data */
+efd_value_t data[5];
+
+static inline uint8_t efd_get_all_sockets_bitmask(void)
+{
+ uint8_t all_cpu_sockets_bitmask = 0;
+ unsigned int i;
+ unsigned int next_lcore = rte_get_master_lcore();
+ const int val_true = 1, val_false = 0;
+ for (i = 0; i < rte_lcore_count(); i++) {
+ all_cpu_sockets_bitmask |= 1 << rte_lcore_to_socket_id(next_lcore);
+ next_lcore = rte_get_next_lcore(next_lcore, val_false, val_true);
+ }
+
+ return all_cpu_sockets_bitmask;
+}
+
+/*
+ * Basic sequence of operations for a single key:
+ * - add
+ * - lookup (hit)
+ * - delete
+ * Note: lookup (miss) is not applicable since this is a filter
+ */
+static int test_add_delete(void)
+{
+ struct rte_efd_table *handle;
+ /* test with standard add/lookup/delete functions */
+ efd_value_t prev_value;
+ printf("Entering %s\n", __func__);
+
+ handle = rte_efd_create("test_add_delete",
+ TABLE_SIZE, sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the EFD table\n");
+
+ data[0] = mrand48() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id, &keys[0],
+ data[0]),
+ "Error inserting the key");
+ print_key_info("Add", &keys[0], data[0]);
+
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id, &keys[0]),
+ data[0],
+ "failed to find key");
+
+ TEST_ASSERT_SUCCESS(rte_efd_delete(handle, test_socket_id, &keys[0],
+ &prev_value),
+ "failed to delete key");
+ TEST_ASSERT_EQUAL(prev_value, data[0],
+ "failed to delete the expected value, got %d, "
+ "expected %d", prev_value, data[0]);
+ print_key_info("Del", &keys[0], data[0]);
+
+ rte_efd_free(handle);
+
+ return 0;
+}
+
+/*
+ * Sequence of operations for a single key:
+ * - add
+ * - lookup: hit
+ * - add: update
+ * - lookup: hit (updated data)
+ * - delete: hit
+ */
+static int test_add_update_delete(void)
+{
+ struct rte_efd_table *handle;
+ printf("Entering %s\n", __func__);
+ /* test with standard add/lookup/delete functions */
+ efd_value_t prev_value;
+ data[1] = mrand48() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+
+ handle = rte_efd_create("test_add_update_delete", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the efd table\n");
+
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id, &keys[1],
+ data[1]), "Error inserting the key");
+ print_key_info("Add", &keys[1], data[1]);
+
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id, &keys[1]),
+ data[1], "failed to find key");
+ print_key_info("Lkp", &keys[1], data[1]);
+
+ data[1] = data[1] + 1;
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id, &keys[1],
+ data[1]), "Error re-inserting the key");
+ print_key_info("Add", &keys[1], data[1]);
+
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id, &keys[1]),
+ data[1], "failed to find key");
+ print_key_info("Lkp", &keys[1], data[1]);
+
+ TEST_ASSERT_SUCCESS(rte_efd_delete(handle, test_socket_id, &keys[1],
+ &prev_value), "failed to delete key");
+ TEST_ASSERT_EQUAL(prev_value, data[1],
+ "failed to delete the expected value, got %d, "
+ "expected %d", prev_value, data[1]);
+ print_key_info("Del", &keys[1], data[1]);
+
+
+ rte_efd_free(handle);
+ return 0;
+}
+
+/*
+ * Sequence of operations for find existing EFD table
+ *
+ * - create table
+ * - find existing table: hit
+ * - find non-existing table: miss
+ *
+ */
+static int test_efd_find_existing(void)
+{
+ struct rte_efd_table *handle = NULL, *result = NULL;
+
+ printf("Entering %s\n", __func__);
+
+ /* Create EFD table. */
+ handle = rte_efd_create("efd_find_existing", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the efd table\n");
+
+ /* Try to find existing EFD table */
+ result = rte_efd_find_existing("efd_find_existing");
+ TEST_ASSERT_EQUAL(result, handle, "could not find existing efd table");
+
+ /* Try to find non-existing EFD table */
+ result = rte_efd_find_existing("efd_find_non_existing");
+ TEST_ASSERT_NULL(result, "found table that shouldn't exist");
+
+ /* Cleanup. */
+ rte_efd_free(handle);
+
+ return 0;
+}
+
+/*
+ * Sequence of operations for 5 keys
+ * - add keys
+ * - lookup keys: hit (bulk)
+ * - add keys (update)
+ * - lookup keys: hit (updated data)
+ * - delete keys : hit
+ */
+static int test_five_keys(void)
+{
+ struct rte_efd_table *handle;
+ const void *key_array[5] = {0};
+ efd_value_t result[5] = {0};
+ efd_value_t prev_value;
+ unsigned int i;
+ printf("Entering %s\n", __func__);
+
+ handle = rte_efd_create("test_five_keys", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the efd table\n");
+
+ /* Setup data */
+ for (i = 0; i < 5; i++)
+ data[i] = mrand48() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+
+ /* Add */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id,
+ &keys[i], data[i]),
+ "Error inserting the key");
+ print_key_info("Add", &keys[i], data[i]);
+ }
+
+ /* Lookup */
+ for (i = 0; i < 5; i++)
+ key_array[i] = &keys[i];
+
+ rte_efd_lookup_bulk(handle, test_socket_id, 5,
+ (const void **) (void *) &key_array, result);
+
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_EQUAL(result[i], data[i],
+ "bulk: failed to find key. Expected %d, got %d",
+ data[i], result[i]);
+ print_key_info("Lkp", &keys[i], data[i]);
+ }
+
+ /* Modify data (bulk) */
+ for (i = 0; i < 5; i++)
+ data[i] = data[i] + 1;
+
+ /* Add - update */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id,
+ &keys[i], data[i]),
+ "Error inserting the key");
+ print_key_info("Add", &keys[i], data[i]);
+ }
+
+ /* Lookup */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id,
+ &keys[i]), data[i],
+ "failed to find key");
+ print_key_info("Lkp", &keys[i], data[i]);
+ }
+
+ /* Delete */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_SUCCESS(rte_efd_delete(handle, test_socket_id,
+ &keys[i], &prev_value),
+ "failed to delete key");
+ TEST_ASSERT_EQUAL(prev_value, data[i],
+ "failed to delete the expected value, got %d, "
+ "expected %d", prev_value, data[i]);
+ print_key_info("Del", &keys[i], data[i]);
+ }
+
+
+ rte_efd_free(handle);
+
+ return 0;
+}
+
+/*
+ * Test to see the average table utilization (entries added/max entries)
+ * before hitting a random entry that cannot be added
+ */
+static int test_average_table_utilization(void)
+{
+ struct rte_efd_table *handle = NULL;
+ uint32_t num_rules_in = TABLE_SIZE;
+ uint8_t simple_key[EFD_TEST_KEY_LEN];
+ unsigned int i, j;
+ unsigned int added_keys, average_keys_added = 0;
+
+ printf("Evaluating table utilization and correctness, please wait\n");
+ fflush(stdout);
+
+ for (j = 0; j < ITERATIONS; j++) {
+ handle = rte_efd_create("test_efd", num_rules_in,
+ EFD_TEST_KEY_LEN, efd_get_all_sockets_bitmask(),
+ test_socket_id);
+ if (handle == NULL) {
+ printf("efd table creation failed\n");
+ return -1;
+ }
+
+ unsigned int succeeded = 0;
+ unsigned int lost_keys = 0;
+
+ /* Add random entries until key cannot be added */
+ for (added_keys = 0; added_keys < num_rules_in; added_keys++) {
+
+ for (i = 0; i < EFD_TEST_KEY_LEN; i++)
+ simple_key[i] = rte_rand() & 0xFF;
+
+ efd_value_t val = simple_key[0];
+
+ if (rte_efd_update(handle, test_socket_id, simple_key,
+ val))
+ break; /* continue;*/
+ if (rte_efd_lookup(handle, test_socket_id, simple_key)
+ != val)
+ lost_keys++;
+ else
+ succeeded++;
+ }
+
+ average_keys_added += succeeded;
+
+ /* Reset the table */
+ rte_efd_free(handle);
+
+ /* Print progress on operations */
+ printf("Added %10u Succeeded %10u Lost %10u\n",
+ added_keys, succeeded, lost_keys);
+ fflush(stdout);
+ }
+
+ average_keys_added /= ITERATIONS;
+
+ printf("\nAverage table utilization = %.2f%% (%u/%u)\n",
+ ((double) average_keys_added / num_rules_in * 100),
+ average_keys_added, num_rules_in);
+
+ return 0;
+}
+
+/*
+ * Do tests for EFD creation with bad parameters.
+ */
+static int test_efd_creation_with_bad_parameters(void)
+{
+ struct rte_efd_table *handle, *tmp;
+ printf("Entering %s, **Errors are expected **\n", __func__);
+
+ handle = rte_efd_create("creation_with_bad_parameters_0", TABLE_SIZE, 0,
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ if (handle != NULL) {
+ rte_efd_free(handle);
+ printf("Impossible creating EFD table successfully "
+ "if key_len in parameter is zero\n");
+ return -1;
+ }
+
+ handle = rte_efd_create("creation_with_bad_parameters_1", TABLE_SIZE,
+ sizeof(struct flow_key), 0, test_socket_id);
+ if (handle != NULL) {
+ rte_efd_free(handle);
+ printf("Impossible creating EFD table successfully "
+ "with invalid socket bitmask\n");
+ return -1;
+ }
+
+ handle = rte_efd_create("creation_with_bad_parameters_2", TABLE_SIZE,
+ sizeof(struct flow_key), efd_get_all_sockets_bitmask(),
+ 255);
+ if (handle != NULL) {
+ rte_efd_free(handle);
+ printf("Impossible creating EFD table successfully "
+ "with invalid socket\n");
+ return -1;
+ }
+
+ /* test with same name should fail */
+ handle = rte_efd_create("same_name", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), 0);
+ if (handle == NULL) {
+ printf("Cannot create first EFD table with 'same_name'\n");
+ return -1;
+ }
+ tmp = rte_efd_create("same_name", TABLE_SIZE, sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), 0);
+ if (tmp != NULL) {
+ printf("Creation of EFD table with same name should fail\n");
+ rte_efd_free(handle);
+ rte_efd_free(tmp);
+ return -1;
+ }
+ rte_efd_free(handle);
+
+ printf("# Test successful. No more errors expected\n");
+
+ return 0;
+}
+
+static int
+test_efd(void)
+{
+
+ /* Unit tests */
+ if (test_add_delete() < 0)
+ return -1;
+ if (test_efd_find_existing() < 0)
+ return -1;
+ if (test_add_update_delete() < 0)
+ return -1;
+ if (test_five_keys() < 0)
+ return -1;
+ if (test_efd_creation_with_bad_parameters() < 0)
+ return -1;
+ if (test_average_table_utilization() < 0)
+ return -1;
+
+ return 0;
+}
+
+REGISTER_TEST_COMMAND(efd_autotest, test_efd);
diff --git a/app/test/test_efd_perf.c b/app/test/test_efd_perf.c
new file mode 100644
index 0000000..79fc893
--- /dev/null
+++ b/app/test/test_efd_perf.c
@@ -0,0 +1,407 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <inttypes.h>
+
+#include <rte_lcore.h>
+#include <rte_cycles.h>
+#include <rte_malloc.h>
+#include <rte_random.h>
+#include <rte_efd.h>
+#include <rte_memcpy.h>
+#include <rte_thash.h>
+
+#include "test.h"
+#define NUM_KEYSIZES 10
+#define NUM_SHUFFLES 10
+#define MAX_KEYSIZE 64
+#define MAX_ENTRIES (1 << 19)
+#define KEYS_TO_ADD (MAX_ENTRIES * 3 / 4) /* 75% table utilization */
+#define NUM_LOOKUPS (KEYS_TO_ADD * 5) /* Loop among keys added, several times */
+static unsigned int test_socket_id;
+
+static inline uint8_t efd_get_all_sockets_bitmask(void)
+{
+ uint8_t all_cpu_sockets_bitmask = 0;
+ unsigned int i;
+ unsigned int next_lcore = rte_get_master_lcore();
+ const int val_true = 1, val_false = 0;
+ for (i = 0; i < rte_lcore_count(); i++) {
+ all_cpu_sockets_bitmask |= 1 << rte_lcore_to_socket_id(next_lcore);
+ next_lcore = rte_get_next_lcore(next_lcore, val_false, val_true);
+ }
+
+ return all_cpu_sockets_bitmask;
+}
+
+enum operations {
+ ADD = 0,
+ LOOKUP,
+ LOOKUP_MULTI,
+ DELETE,
+ NUM_OPERATIONS
+};
+
+struct efd_perf_params {
+ struct rte_efd_table *efd_table;
+ uint32_t key_size;
+ unsigned int cycle;
+};
+
+static uint32_t hashtest_key_lens[] = {
+ /* standard key sizes */
+ 4, 8, 16, 32, 48, 64,
+ /* IPv4 SRC + DST + protocol, unpadded */
+ 9,
+ /* IPv4 5-tuple, unpadded */
+ 13,
+ /* IPv6 5-tuple, unpadded */
+ 37,
+ /* IPv6 5-tuple, padded to 8-byte boundary */
+ 40
+};
+
+/* Array to store number of cycles per operation */
+uint64_t cycles[NUM_KEYSIZES][NUM_OPERATIONS];
+
+/* Array to store the data */
+efd_value_t data[KEYS_TO_ADD];
+
+/* Array to store all input keys */
+uint8_t keys[KEYS_TO_ADD][MAX_KEYSIZE];
+
+/* Shuffle the keys that have been added, so lookups will be totally random */
+static void
+shuffle_input_keys(struct efd_perf_params *params)
+{
+ efd_value_t temp_data;
+ unsigned int i;
+ uint32_t swap_idx;
+ uint8_t temp_key[MAX_KEYSIZE];
+
+ for (i = KEYS_TO_ADD - 1; i > 0; i--) {
+ swap_idx = rte_rand() % i;
+
+ memcpy(temp_key, keys[i], hashtest_key_lens[params->cycle]);
+ temp_data = data[i];
+
+ memcpy(keys[i], keys[swap_idx], hashtest_key_lens[params->cycle]);
+ data[i] = data[swap_idx];
+
+ memcpy(keys[swap_idx], temp_key, hashtest_key_lens[params->cycle]);
+ data[swap_idx] = temp_data;
+ }
+}
+
+static int key_compare(const void *key1, const void *key2)
+{
+ return memcmp(key1, key2, MAX_KEYSIZE);
+}
+
+/*
+ * TODO: we could "error proof" these as done in test_hash_perf.c ln 165:
+ *
+ * The current setup may give errors if too full in some cases which we check
+ * for. However, since EFD allows for ~99% capacity, these errors are rare for
+ * #"KEYS_TO_ADD" which is 75% capacity.
+ */
+static int
+setup_keys_and_data(struct efd_perf_params *params, unsigned int cycle)
+{
+ unsigned int i, j;
+ int num_duplicates;
+
+ params->key_size = hashtest_key_lens[cycle];
+ params->cycle = cycle;
+
+ /* Reset all arrays */
+ for (i = 0; i < params->key_size; i++)
+ keys[0][i] = 0;
+
+ /* Generate a list of keys, some of which may be duplicates */
+ for (i = 0; i < KEYS_TO_ADD; i++) {
+ for (j = 0; j < params->key_size; j++)
+ keys[i][j] = rte_rand() & 0xFF;
+
+ data[i] = rte_rand() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+ }
+
+ /* Remove duplicates from the keys array */
+ do {
+ num_duplicates = 0;
+
+ /* Sort the list of keys to make it easier to find duplicates */
+ qsort(keys, KEYS_TO_ADD, MAX_KEYSIZE, key_compare);
+
+ /* Sift through the list of keys and look for duplicates */
+ int num_duplicates = 0;
+ for (i = 0; i < KEYS_TO_ADD - 1; i++) {
+ if (memcmp(keys[i], keys[i + 1], params->key_size) == 0) {
+ /* This key already exists, try again */
+ num_duplicates++;
+ for (j = 0; j < params->key_size; j++)
+ keys[i][j] = rte_rand() & 0xFF;
+ }
+ }
+ } while (num_duplicates != 0);
+
+ /* Shuffle the random values again */
+ shuffle_input_keys(params);
+
+ params->efd_table = rte_efd_create("test_efd_perf",
+ MAX_ENTRIES, params->key_size,
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(params->efd_table, "Error creating the efd table\n");
+
+ return 0;
+}
+
+static int
+timed_adds(struct efd_perf_params *params)
+{
+ const uint64_t start_tsc = rte_rdtsc();
+ unsigned int i, a;
+ int32_t ret;
+
+ for (i = 0; i < KEYS_TO_ADD; i++) {
+ ret = rte_efd_update(params->efd_table, test_socket_id, keys[i],
+ data[i]);
+ if (ret != 0) {
+ printf("Error %d in rte_efd_update - key=0x", ret);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x", keys[i][a]);
+ printf(" value=%d\n", data[i]);
+
+ return -1;
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][ADD] = time_taken / KEYS_TO_ADD;
+ return 0;
+}
+
+static int
+timed_lookups(struct efd_perf_params *params)
+{
+ unsigned int i, j, a;
+ const uint64_t start_tsc = rte_rdtsc();
+ efd_value_t ret_data;
+
+ for (i = 0; i < NUM_LOOKUPS / KEYS_TO_ADD; i++) {
+ for (j = 0; j < KEYS_TO_ADD; j++) {
+ ret_data = rte_efd_lookup(params->efd_table,
+ test_socket_id, keys[j]);
+ if (ret_data != data[j]) {
+ printf("Value mismatch using rte_efd_lookup: "
+ "key #%d (0x", i);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x", keys[i][a]);
+ printf(")\n");
+ printf(" Expected %d, got %d\n", data[i],
+ ret_data);
+
+ return -1;
+ }
+
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][LOOKUP] = time_taken / NUM_LOOKUPS;
+
+ return 0;
+}
+
+static int
+timed_lookups_multi(struct efd_perf_params *params)
+{
+ unsigned int i, j, k, a;
+ efd_value_t result[RTE_EFD_BURST_MAX] = {0};
+ const void *keys_burst[RTE_EFD_BURST_MAX];
+ const uint64_t start_tsc = rte_rdtsc();
+
+ for (i = 0; i < NUM_LOOKUPS / KEYS_TO_ADD; i++) {
+ for (j = 0; j < KEYS_TO_ADD / RTE_EFD_BURST_MAX; j++) {
+ for (k = 0; k < RTE_EFD_BURST_MAX; k++)
+ keys_burst[k] = keys[j * RTE_EFD_BURST_MAX + k];
+
+ rte_efd_lookup_bulk(params->efd_table, test_socket_id,
+ RTE_EFD_BURST_MAX,
+ keys_burst, result);
+
+ for (k = 0; k < RTE_EFD_BURST_MAX; k++) {
+ uint32_t data_idx = j * RTE_EFD_BURST_MAX + k;
+ if (result[k] != data[data_idx]) {
+ printf("Value mismatch using "
+ "rte_efd_lookup_bulk: key #%d "
+ "(0x", i);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x",
+ keys[data_idx][a]);
+ printf(")\n");
+ printf(" Expected %d, got %d\n",
+ data[data_idx], result[k]);
+
+ return -1;
+ }
+ }
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][LOOKUP_MULTI] = time_taken / NUM_LOOKUPS;
+
+ return 0;
+}
+
+static int
+timed_deletes(struct efd_perf_params *params)
+{
+ unsigned int i, a;
+ const uint64_t start_tsc = rte_rdtsc();
+ int32_t ret;
+
+ for (i = 0; i < KEYS_TO_ADD; i++) {
+ ret = rte_efd_delete(params->efd_table, test_socket_id, keys[i],
+ NULL);
+
+ if (ret != 0) {
+ printf("Error %d in rte_efd_delete - key=0x", ret);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x", keys[i][a]);
+ printf("\n");
+
+ return -1;
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][DELETE] = time_taken / KEYS_TO_ADD;
+
+ return 0;
+}
+
+static void
+perform_frees(struct efd_perf_params *params)
+{
+ if (params->efd_table != NULL) {
+ rte_efd_free(params->efd_table);
+ params->efd_table = NULL;
+ }
+}
+
+static int
+exit_with_fail(const char *testname, struct efd_perf_params *params,
+ unsigned int i)
+{
+
+ printf("<<<<<Test %s failed at keysize %d iteration %d >>>>>\n",
+ testname, hashtest_key_lens[params->cycle], i);
+ perform_frees(params);
+ return -1;
+}
+
+static int
+run_all_tbl_perf_tests(void)
+{
+ unsigned int i, j;
+ struct efd_perf_params params;
+
+ printf("Measuring performance, please wait\n");
+ fflush(stdout);
+
+ test_socket_id = rte_socket_id();
+
+ for (i = 0; i < NUM_KEYSIZES; i++) {
+
+ if (setup_keys_and_data(¶ms, i) < 0) {
+ printf("Could not create keys/data/table\n");
+ return -1;
+ }
+
+ if (timed_adds(¶ms) < 0)
+ return exit_with_fail("timed_adds", ¶ms, i);
+
+ for (j = 0; j < NUM_SHUFFLES; j++)
+ shuffle_input_keys(¶ms);
+
+ if (timed_lookups(¶ms) < 0)
+ return exit_with_fail("timed_lookups", ¶ms, i);
+
+ if (timed_lookups_multi(¶ms) < 0)
+ return exit_with_fail("timed_lookups_multi", ¶ms, i);
+
+ if (timed_deletes(¶ms) < 0)
+ return exit_with_fail("timed_deletes", ¶ms, i);
+
+ /* Print a dot to show progress on operations */
+ printf(".");
+ fflush(stdout);
+
+ perform_frees(¶ms);
+ }
+
+ printf("\nResults (in CPU cycles/operation)\n");
+ printf("-----------------------------------\n");
+ printf("\n%-18s%-18s%-18s%-18s%-18s\n",
+ "Keysize", "Add", "Lookup", "Lookup_bulk", "Delete");
+ for (i = 0; i < NUM_KEYSIZES; i++) {
+ printf("%-18d", hashtest_key_lens[i]);
+ for (j = 0; j < NUM_OPERATIONS; j++)
+ printf("%-18"PRIu64, cycles[i][j]);
+ printf("\n");
+ }
+ return 0;
+}
+
+static int
+test_efd_perf(void)
+{
+
+ if (run_all_tbl_perf_tests() < 0)
+ return -1;
+
+ return 0;
+}
+
+REGISTER_TEST_COMMAND(efd_perf_autotest, test_efd_perf);
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v2 3/5] examples/flow_distributor: sample app to demonstrate EFD usage
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 0/5] Elastic Flow Distributor Pablo de Lara
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 1/5] efd: new Elastic Flow Distributor library Pablo de Lara
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 2/5] app/test: add EFD functional and perf tests Pablo de Lara
@ 2017-01-07 1:06 ` Pablo de Lara
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 4/5] doc: add EFD library section in Programmers guide Pablo de Lara
` (3 subsequent siblings)
6 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-07 1:06 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Saikrishna Edupuganti
This new sample app, based on the client/server sample app,
shows the user an scenario using the EFD library.
It consists of:
- A front-end server which has an EFD table that stores the
node id for each flow key, which will distribute the incoming
packets to the different nodes
- A back-end node, which has a hash table where node checks,
after reading packets coming from the server, whether the packet
is meant to be used in such node, in which case it will be TXed,
or not, in which case, packet will be dropped.
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
---
MAINTAINERS | 1 +
doc/api/examples.dox | 4 +
examples/Makefile | 1 +
examples/flow_distributor/Makefile | 44 +++
examples/flow_distributor/distributor/Makefile | 57 ++++
examples/flow_distributor/distributor/args.c | 200 ++++++++++++
examples/flow_distributor/distributor/args.h | 39 +++
examples/flow_distributor/distributor/init.c | 371 ++++++++++++++++++++++
examples/flow_distributor/distributor/init.h | 76 +++++
examples/flow_distributor/distributor/main.c | 362 +++++++++++++++++++++
examples/flow_distributor/node/Makefile | 48 +++
examples/flow_distributor/node/node.c | 417 +++++++++++++++++++++++++
examples/flow_distributor/shared/common.h | 99 ++++++
13 files changed, 1719 insertions(+)
create mode 100644 examples/flow_distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/args.c
create mode 100644 examples/flow_distributor/distributor/args.h
create mode 100644 examples/flow_distributor/distributor/init.c
create mode 100644 examples/flow_distributor/distributor/init.h
create mode 100644 examples/flow_distributor/distributor/main.c
create mode 100644 examples/flow_distributor/node/Makefile
create mode 100644 examples/flow_distributor/node/node.c
create mode 100644 examples/flow_distributor/shared/common.h
diff --git a/MAINTAINERS b/MAINTAINERS
index d812962..b124f6e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -533,6 +533,7 @@ M: Byron Marohn <byron.marohn@intel.com>
M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
F: lib/librte_efd/
F: app/test/test_efd*
+F: examples/flow_distributor/
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
diff --git a/doc/api/examples.dox b/doc/api/examples.dox
index 1626852..c13e574 100644
--- a/doc/api/examples.dox
+++ b/doc/api/examples.dox
@@ -52,6 +52,10 @@
@example load_balancer/init.c
@example load_balancer/main.c
@example load_balancer/runtime.c
+@example flow_distributor/distributor/args.c
+@example flow_distributor/distributor/init.c
+@example flow_distributor/distributor/main.c
+@example flow_distributor/node/node.c
@example multi_process/client_server_mp/mp_client/client.c
@example multi_process/client_server_mp/mp_server/args.c
@example multi_process/client_server_mp/mp_server/init.c
diff --git a/examples/Makefile b/examples/Makefile
index d49c7f2..b404982 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -45,6 +45,7 @@ DIRS-y += dpdk_qat
endif
DIRS-y += ethtool
DIRS-y += exception_path
+DIRS-$(CONFIG_RTE_LIBRTE_EFD) += flow_distributor
DIRS-y += helloworld
DIRS-$(CONFIG_RTE_LIBRTE_PIPELINE) += ip_pipeline
ifeq ($(CONFIG_RTE_LIBRTE_LPM),y)
diff --git a/examples/flow_distributor/Makefile b/examples/flow_distributor/Makefile
new file mode 100644
index 0000000..d085e49
--- /dev/null
+++ b/examples/flow_distributor/Makefile
@@ -0,0 +1,44 @@
+# BSD LICENSE
+#
+# Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overridden by command line or environment
+RTE_TARGET ?= x86_64-native-linuxapp-gcc
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+DIRS-$(CONFIG_RTE_EXEC_ENV_LINUXAPP) += distributor
+DIRS-$(CONFIG_RTE_EXEC_ENV_LINUXAPP) += node
+
+include $(RTE_SDK)/mk/rte.extsubdir.mk
diff --git a/examples/flow_distributor/distributor/Makefile b/examples/flow_distributor/distributor/Makefile
new file mode 100644
index 0000000..5a709ab
--- /dev/null
+++ b/examples/flow_distributor/distributor/Makefile
@@ -0,0 +1,57 @@
+# BSD LICENSE
+#
+# Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overridden by command line or environment
+RTE_TARGET ?= x86_64-native-linuxapp-gcc
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+ifneq ($(CONFIG_RTE_EXEC_ENV), "linuxapp")
+$(error This application can only operate in a linuxapp environment, \
+please change the definition of the RTE_TARGET environment variable)
+endif
+
+# binary name
+APP = distributor
+
+# all source are stored in SRCS-y
+SRCS-y := main.c init.c args.c
+
+INC := $(wildcard *.h)
+
+CFLAGS += $(WERROR_FLAGS) -O3
+CFLAGS += -I$(SRCDIR)/../shared
+
+include $(RTE_SDK)/mk/rte.extapp.mk
diff --git a/examples/flow_distributor/distributor/args.c b/examples/flow_distributor/distributor/args.c
new file mode 100644
index 0000000..3bd1ad6
--- /dev/null
+++ b/examples/flow_distributor/distributor/args.c
@@ -0,0 +1,200 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <getopt.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include <rte_memory.h>
+#include <rte_string_fns.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+/* 1M flows by default */
+#define DEFAULT_NUM_FLOWS 0x100000
+
+/* global var for number of nodes - extern in header */
+uint8_t num_nodes;
+/* global var for number of flows - extern in header */
+uint32_t num_flows = DEFAULT_NUM_FLOWS;
+
+static const char *progname;
+
+/**
+ * Prints out usage information to stdout
+ */
+static void
+usage(void)
+{
+ printf("%s [EAL options] -- -p PORTMASK -n NUM_NODES -f NUM_FLOWS\n"
+ " -p PORTMASK: hexadecimal bitmask of ports to use\n"
+ " -n NUM_NODES: number of node processes to use\n"
+ " -f NUM_FLOWS: number of flows to be added in the EFD table\n",
+ progname);
+}
+
+/**
+ * The ports to be used by the application are passed in
+ * the form of a bitmask. This function parses the bitmask
+ * and places the port numbers to be used into the port[]
+ * array variable
+ */
+static int
+parse_portmask(uint8_t max_ports, const char *portmask)
+{
+ char *end = NULL;
+ unsigned long pm;
+ uint8_t count = 0;
+
+ if (portmask == NULL || *portmask == '\0')
+ return -1;
+
+ /* convert parameter to a number and verify */
+ pm = strtoul(portmask, &end, 16);
+ if (end == NULL || *end != '\0' || pm == 0)
+ return -1;
+
+ /* loop through bits of the mask and mark ports */
+ while (pm != 0) {
+ if (pm & 0x01) { /* bit is set in mask, use port */
+ if (count >= max_ports)
+ printf("WARNING: requested port %u not present"
+ " - ignoring\n", (unsigned int)count);
+ else
+ info->id[info->num_ports++] = count;
+ }
+ pm = (pm >> 1);
+ count++;
+ }
+
+ return 0;
+}
+
+/**
+ * Take the number of nodes parameter passed to the app
+ * and convert to a number to store in the num_nodes variable
+ */
+static int
+parse_num_nodes(const char *nodes)
+{
+ char *end = NULL;
+ unsigned long temp;
+
+ if (nodes == NULL || *nodes == '\0')
+ return -1;
+
+ temp = strtoul(nodes, &end, 10);
+ if (end == NULL || *end != '\0' || temp == 0)
+ return -1;
+
+ num_nodes = (uint8_t)temp;
+ return 0;
+}
+
+static int
+parse_num_flows(const char *flows)
+{
+ char *end = NULL;
+
+ /* parse hexadecimal string */
+ num_flows = strtoul(flows, &end, 16);
+ if ((flows[0] == '\0') || (end == NULL) || (*end != '\0'))
+ return -1;
+
+ if (num_flows == 0)
+ return -1;
+
+ return 0;
+}
+
+/**
+ * The application specific arguments follow the DPDK-specific
+ * arguments which are stripped by the DPDK init. This function
+ * processes these application arguments, printing usage info
+ * on error.
+ */
+int
+parse_app_args(uint8_t max_ports, int argc, char *argv[])
+{
+ int option_index, opt;
+ char **argvopt = argv;
+ static struct option lgopts[] = { /* no long options */
+ {NULL, 0, 0, 0 }
+ };
+ progname = argv[0];
+
+ while ((opt = getopt_long(argc, argvopt, "n:f:p:", lgopts,
+ &option_index)) != EOF) {
+ switch (opt) {
+ case 'p':
+ if (parse_portmask(max_ports, optarg) != 0) {
+ usage();
+ return -1;
+ }
+ break;
+ case 'n':
+ if (parse_num_nodes(optarg) != 0) {
+ usage();
+ return -1;
+ }
+ break;
+ case 'f':
+ if (parse_num_flows(optarg) != 0) {
+ usage();
+ return -1;
+ }
+ break;
+ default:
+ printf("ERROR: Unknown option '%c'\n", opt);
+ usage();
+ return -1;
+ }
+ }
+
+ if (info->num_ports == 0 || num_nodes == 0) {
+ usage();
+ return -1;
+ }
+
+ if (info->num_ports % 2 != 0) {
+ printf("ERROR: application requires an even "
+ "number of ports to use\n");
+ return -1;
+ }
+ return 0;
+}
diff --git a/examples/flow_distributor/distributor/args.h b/examples/flow_distributor/distributor/args.h
new file mode 100644
index 0000000..8b36148
--- /dev/null
+++ b/examples/flow_distributor/distributor/args.h
@@ -0,0 +1,39 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _ARGS_H_
+#define _ARGS_H_
+
+int parse_app_args(uint8_t max_ports, int argc, char *argv[]);
+
+#endif /* ifndef _ARGS_H_ */
diff --git a/examples/flow_distributor/distributor/init.c b/examples/flow_distributor/distributor/init.c
new file mode 100644
index 0000000..5d3b1a6
--- /dev/null
+++ b/examples/flow_distributor/distributor/init.c
@@ -0,0 +1,371 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/queue.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <inttypes.h>
+
+#include <rte_common.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_byteorder.h>
+#include <rte_atomic.h>
+#include <rte_launch.h>
+#include <rte_per_lcore.h>
+#include <rte_lcore.h>
+#include <rte_branch_prediction.h>
+#include <rte_debug.h>
+#include <rte_ring.h>
+#include <rte_log.h>
+#include <rte_mempool.h>
+#include <rte_memcpy.h>
+#include <rte_mbuf.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ether.h>
+#include <rte_ethdev.h>
+#include <rte_malloc.h>
+#include <rte_string_fns.h>
+#include <rte_cycles.h>
+#include <rte_efd.h>
+#include <rte_hash.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+#define MBUFS_PER_NODE 1536
+#define MBUFS_PER_PORT 1536
+#define MBUF_CACHE_SIZE 512
+
+#define RTE_MP_RX_DESC_DEFAULT 512
+#define RTE_MP_TX_DESC_DEFAULT 512
+#define NODE_QUEUE_RINGSIZE 128
+
+#define NO_FLAGS 0
+
+/* The mbuf pool for packet rx */
+struct rte_mempool *pktmbuf_pool;
+
+/* array of info/queues for nodes */
+struct node *nodes;
+
+/* Flow distributor table */
+struct rte_efd_table *efd_table;
+
+/* Shared info between distributor and nodes */
+struct shared_info *info;
+
+/**
+ * Initialise the mbuf pool for packet reception for the NIC, and any other
+ * buffer pools needed by the app - currently none.
+ */
+static int
+init_mbuf_pools(void)
+{
+ const unsigned int num_mbufs = (num_nodes * MBUFS_PER_NODE) +
+ (info->num_ports * MBUFS_PER_PORT);
+
+ /*
+ * Don't pass single-producer/single-consumer flags to mbuf create as it
+ * seems faster to use a cache instead
+ */
+ printf("Creating mbuf pool '%s' [%u mbufs] ...\n",
+ PKTMBUF_POOL_NAME, num_mbufs);
+ pktmbuf_pool = rte_pktmbuf_pool_create(PKTMBUF_POOL_NAME, num_mbufs,
+ MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
+
+ return pktmbuf_pool == NULL; /* 0 on success */
+}
+
+/**
+ * Initialise an individual port:
+ * - configure number of rx and tx rings
+ * - set up each rx ring, to pull from the main mbuf pool
+ * - set up each tx ring
+ * - start the port and report its status to stdout
+ */
+static int
+init_port(uint8_t port_num)
+{
+ /* for port configuration all features are off by default */
+ const struct rte_eth_conf port_conf = {
+ .rxmode = {
+ .mq_mode = ETH_MQ_RX_RSS
+ }
+ };
+ const uint16_t rx_rings = 1, tx_rings = num_nodes;
+ const uint16_t rx_ring_size = RTE_MP_RX_DESC_DEFAULT;
+ const uint16_t tx_ring_size = RTE_MP_TX_DESC_DEFAULT;
+
+ uint16_t q;
+ int retval;
+
+ printf("Port %u init ... ", (unsigned int)port_num);
+ fflush(stdout);
+
+ /*
+ * Standard DPDK port initialisation - config port, then set up
+ * rx and tx rings.
+ */
+ retval = rte_eth_dev_configure(port_num, rx_rings, tx_rings, &port_conf);
+ if (retval != 0)
+ return retval;
+
+ for (q = 0; q < rx_rings; q++) {
+ retval = rte_eth_rx_queue_setup(port_num, q, rx_ring_size,
+ rte_eth_dev_socket_id(port_num),
+ NULL, pktmbuf_pool);
+ if (retval < 0)
+ return retval;
+ }
+
+ for (q = 0; q < tx_rings; q++) {
+ retval = rte_eth_tx_queue_setup(port_num, q, tx_ring_size,
+ rte_eth_dev_socket_id(port_num),
+ NULL);
+ if (retval < 0)
+ return retval;
+ }
+
+ rte_eth_promiscuous_enable(port_num);
+
+ retval = rte_eth_dev_start(port_num);
+ if (retval < 0)
+ return retval;
+
+ printf("done:\n");
+
+ return 0;
+}
+
+/**
+ * Set up the DPDK rings which will be used to pass packets, via
+ * pointers, between the multi-process distributor and node processes.
+ * Each node needs one RX queue.
+ */
+static int
+init_shm_rings(void)
+{
+ unsigned int i;
+ unsigned int socket_id;
+ const char *q_name;
+ const unsigned int ringsize = NODE_QUEUE_RINGSIZE;
+
+ nodes = rte_malloc("node details",
+ sizeof(*nodes) * num_nodes, 0);
+ if (nodes == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot allocate memory for "
+ "node program details\n");
+
+ for (i = 0; i < num_nodes; i++) {
+ /* Create an RX queue for each node */
+ socket_id = rte_socket_id();
+ q_name = get_rx_queue_name(i);
+ nodes[i].rx_q = rte_ring_create(q_name,
+ ringsize, socket_id,
+ RING_F_SP_ENQ | RING_F_SC_DEQ);
+ if (nodes[i].rx_q == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot create rx ring queue "
+ "for node %u\n", i);
+ }
+ return 0;
+}
+
+/*
+ * Create flow distributor table which will contain all the flows
+ * that will be distributed among the nodes
+ */
+static void
+create_flow_distributor_table(void)
+{
+ uint8_t socket_id = rte_socket_id();
+
+ /* create table */
+ efd_table = rte_efd_create("flow table", num_flows * 2, sizeof(uint32_t),
+ 1 << socket_id, socket_id);
+
+ if (efd_table == NULL)
+ rte_exit(EXIT_FAILURE, "Problem creating the flow table\n");
+}
+
+static void
+populate_flow_distributor_table(void)
+{
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint8_t socket_id = rte_socket_id();
+ uint64_t node_id;
+
+ /* Add flows in table */
+ for (i = 0; i < num_flows; i++) {
+ node_id = i % num_nodes;
+
+ ip_dst = rte_cpu_to_be_32(i);
+ ret = rte_efd_update(efd_table, socket_id,
+ (void *)&ip_dst, (efd_value_t)node_id);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u in "
+ "flow distributor table\n", i);
+ }
+
+ printf("EFD table: Adding 0x%x keys\n", num_flows);
+}
+
+/* Check the link status of all ports in up to 9s, and print them finally */
+static void
+check_all_ports_link_status(uint8_t port_num, uint32_t port_mask)
+{
+#define CHECK_INTERVAL 100 /* 100ms */
+#define MAX_CHECK_TIME 90 /* 9s (90 * 100ms) in total */
+ uint8_t portid, count, all_ports_up, print_flag = 0;
+ struct rte_eth_link link;
+
+ printf("\nChecking link status");
+ fflush(stdout);
+ for (count = 0; count <= MAX_CHECK_TIME; count++) {
+ all_ports_up = 1;
+ for (portid = 0; portid < port_num; portid++) {
+ if ((port_mask & (1 << info->id[portid])) == 0)
+ continue;
+ memset(&link, 0, sizeof(link));
+ rte_eth_link_get_nowait(info->id[portid], &link);
+ /* print link status if flag set */
+ if (print_flag == 1) {
+ if (link.link_status)
+ printf("Port %d Link Up - speed %u "
+ "Mbps - %s\n", info->id[portid],
+ (unsigned int)link.link_speed,
+ (link.link_duplex == ETH_LINK_FULL_DUPLEX) ?
+ ("full-duplex") : ("half-duplex\n"));
+ else
+ printf("Port %d Link Down\n",
+ (uint8_t)info->id[portid]);
+ continue;
+ }
+ /* clear all_ports_up flag if any link down */
+ if (link.link_status == ETH_LINK_DOWN) {
+ all_ports_up = 0;
+ break;
+ }
+ }
+ /* after finally printing all link status, get out */
+ if (print_flag == 1)
+ break;
+
+ if (all_ports_up == 0) {
+ printf(".");
+ fflush(stdout);
+ rte_delay_ms(CHECK_INTERVAL);
+ }
+
+ /* set the print_flag if all ports up or timeout */
+ if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
+ print_flag = 1;
+ printf("done\n");
+ }
+ }
+}
+
+/**
+ * Main init function for the multi-process distributor app,
+ * calls subfunctions to do each stage of the initialisation.
+ */
+int
+init(int argc, char *argv[])
+{
+ int retval;
+ const struct rte_memzone *mz;
+ uint8_t i, total_ports;
+
+ /* init EAL, parsing EAL args */
+ retval = rte_eal_init(argc, argv);
+ if (retval < 0)
+ return -1;
+ argc -= retval;
+ argv += retval;
+
+ /* get total number of ports */
+ total_ports = rte_eth_dev_count();
+
+ /* set up array for port data */
+ mz = rte_memzone_reserve(MZ_SHARED_INFO, sizeof(*info),
+ rte_socket_id(), NO_FLAGS);
+ if (mz == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot reserve memory zone "
+ "for port information\n");
+ memset(mz->addr, 0, sizeof(*info));
+ info = mz->addr;
+
+ /* parse additional, application arguments */
+ retval = parse_app_args(total_ports, argc, argv);
+ if (retval != 0)
+ return -1;
+
+ /* initialise mbuf pools */
+ retval = init_mbuf_pools();
+ if (retval != 0)
+ rte_exit(EXIT_FAILURE, "Cannot create needed mbuf pools\n");
+
+ /* now initialise the ports we will use */
+ for (i = 0; i < info->num_ports; i++) {
+ retval = init_port(info->id[i]);
+ if (retval != 0)
+ rte_exit(EXIT_FAILURE, "Cannot initialise port %u\n",
+ (unsigned int) i);
+ }
+
+ check_all_ports_link_status(info->num_ports, (~0x0));
+
+ /* initialise the node queues/rings for inter-eu comms */
+ init_shm_rings();
+
+ /* Create the flow distributor table */
+ create_flow_distributor_table();
+
+ /* Populate the flow distributor table */
+ populate_flow_distributor_table();
+
+ /* Share the total number of nodes */
+ info->num_nodes = num_nodes;
+
+ /* Share the total number of flows */
+ info->num_flows = num_flows;
+ return 0;
+}
diff --git a/examples/flow_distributor/distributor/init.h b/examples/flow_distributor/distributor/init.h
new file mode 100644
index 0000000..ea0c0d4
--- /dev/null
+++ b/examples/flow_distributor/distributor/init.h
@@ -0,0 +1,76 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _INIT_H_
+#define _INIT_H_
+
+/*
+ * #include <rte_ring.h>
+ * #include "args.h"
+ */
+
+/*
+ * Define a node structure with all needed info, including
+ * stats from the nodes.
+ */
+struct node {
+ struct rte_ring *rx_q;
+ unsigned int node_id;
+ /* these stats hold how many packets the node will actually receive,
+ * and how many packets were dropped because the node's queue was full.
+ * The port-info stats, in contrast, record how many packets were received
+ * or transmitted on an actual NIC port.
+ */
+ struct {
+ uint64_t rx;
+ uint64_t rx_drop;
+ } stats;
+};
+
+extern struct rte_efd_table *efd_table;
+extern struct node *nodes;
+
+/*
+ * shared information between distributor and nodes: number of clients,
+ * port numbers, rx and tx stats etc.
+ */
+extern struct shared_info *info;
+
+extern struct rte_mempool *pktmbuf_pool;
+extern uint8_t num_nodes;
+extern unsigned int num_sockets;
+extern uint32_t num_flows;
+
+int init(int argc, char *argv[]);
+
+#endif /* ifndef _INIT_H_ */
diff --git a/examples/flow_distributor/distributor/main.c b/examples/flow_distributor/distributor/main.c
new file mode 100644
index 0000000..678b8ff
--- /dev/null
+++ b/examples/flow_distributor/distributor/main.c
@@ -0,0 +1,362 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <inttypes.h>
+#include <sys/queue.h>
+#include <errno.h>
+#include <netinet/ip.h>
+
+#include <rte_common.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_byteorder.h>
+#include <rte_launch.h>
+#include <rte_per_lcore.h>
+#include <rte_lcore.h>
+#include <rte_branch_prediction.h>
+#include <rte_atomic.h>
+#include <rte_ring.h>
+#include <rte_log.h>
+#include <rte_debug.h>
+#include <rte_mempool.h>
+#include <rte_memcpy.h>
+#include <rte_mbuf.h>
+#include <rte_ether.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ethdev.h>
+#include <rte_byteorder.h>
+#include <rte_malloc.h>
+#include <rte_string_fns.h>
+#include <rte_efd.h>
+#include <rte_ip.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+/*
+ * When doing reads from the NIC or the node queues,
+ * use this batch size
+ */
+#define PACKET_READ_SIZE 32
+
+/*
+ * Local buffers to put packets in, used to send packets in bursts to the
+ * nodes
+ */
+struct node_rx_buf {
+ struct rte_mbuf *buffer[PACKET_READ_SIZE];
+ uint16_t count;
+};
+
+struct flow_distributor_stats {
+ uint64_t distributed;
+ uint64_t drop;
+} flow_dist_stats;
+
+/* One buffer per node rx queue - dynamically allocate array */
+static struct node_rx_buf *cl_rx_buf;
+
+static const char *
+get_printable_mac_addr(uint8_t port)
+{
+ static const char err_address[] = "00:00:00:00:00:00";
+ static char addresses[RTE_MAX_ETHPORTS][sizeof(err_address)];
+ struct ether_addr mac;
+
+ if (unlikely(port >= RTE_MAX_ETHPORTS))
+ return err_address;
+ if (unlikely(addresses[port][0] == '\0')) {
+ rte_eth_macaddr_get(port, &mac);
+ snprintf(addresses[port], sizeof(addresses[port]),
+ "%02x:%02x:%02x:%02x:%02x:%02x\n",
+ mac.addr_bytes[0], mac.addr_bytes[1],
+ mac.addr_bytes[2], mac.addr_bytes[3],
+ mac.addr_bytes[4], mac.addr_bytes[5]);
+ }
+ return addresses[port];
+}
+
+/*
+ * This function displays the recorded statistics for each port
+ * and for each node. It uses ANSI terminal codes to clear
+ * screen when called. It is called from a single non-master
+ * thread in the distributor process, when the process is run with more
+ * than one lcore enabled.
+ */
+static void
+do_stats_display(void)
+{
+ unsigned int i, j;
+ const char clr[] = {27, '[', '2', 'J', '\0'};
+ const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
+ uint64_t port_tx[RTE_MAX_ETHPORTS], port_tx_drop[RTE_MAX_ETHPORTS];
+ uint64_t node_tx[MAX_NODES], node_tx_drop[MAX_NODES];
+
+ /* to get TX stats, we need to do some summing calculations */
+ memset(port_tx, 0, sizeof(port_tx));
+ memset(port_tx_drop, 0, sizeof(port_tx_drop));
+ memset(node_tx, 0, sizeof(node_tx));
+ memset(node_tx_drop, 0, sizeof(node_tx_drop));
+
+ for (i = 0; i < num_nodes; i++) {
+ const struct tx_stats *tx = &info->tx_stats[i];
+
+ for (j = 0; j < info->num_ports; j++) {
+ const uint64_t tx_val = tx->tx[info->id[j]];
+ const uint64_t drop_val = tx->tx_drop[info->id[j]];
+
+ port_tx[j] += tx_val;
+ port_tx_drop[j] += drop_val;
+ node_tx[i] += tx_val;
+ node_tx_drop[i] += drop_val;
+ }
+ }
+
+ /* Clear screen and move to top left */
+ printf("%s%s", clr, topLeft);
+
+ printf("PORTS\n");
+ printf("-----\n");
+ for (i = 0; i < info->num_ports; i++)
+ printf("Port %u: '%s'\t", (unsigned int)info->id[i],
+ get_printable_mac_addr(info->id[i]));
+ printf("\n\n");
+ for (i = 0; i < info->num_ports; i++) {
+ printf("Port %u - rx: %9"PRIu64"\t"
+ "tx: %9"PRIu64"\n",
+ (unsigned int)info->id[i], info->rx_stats.rx[i],
+ port_tx[i]);
+ }
+
+ printf("\nFLOW DISTRIBUTOR\n");
+ printf("-----\n");
+ printf("distributed: %9"PRIu64", drop: %9"PRIu64"\n",
+ flow_dist_stats.distributed, flow_dist_stats.drop);
+
+ printf("\nNODES\n");
+ printf("-------\n");
+ for (i = 0; i < num_nodes; i++) {
+ const unsigned long long rx = nodes[i].stats.rx;
+ const unsigned long long rx_drop = nodes[i].stats.rx_drop;
+ const struct filter_stats *filter = &info->filter_stats[i];
+
+ printf("Node %2u - rx: %9llu, rx_drop: %9llu\n"
+ " tx: %9"PRIu64", tx_drop: %9"PRIu64"\n"
+ " filter_passed: %9"PRIu64", "
+ "filter_drop: %9"PRIu64"\n",
+ i, rx, rx_drop, node_tx[i], node_tx_drop[i],
+ filter->passed, filter->drop);
+ }
+
+ printf("\n");
+}
+
+/*
+ * The function called from each non-master lcore used by the process.
+ * The test_and_set function is used to randomly pick a single lcore on which
+ * the code to display the statistics will run. Otherwise, the code just
+ * repeatedly sleeps.
+ */
+static int
+sleep_lcore(__attribute__((unused)) void *dummy)
+{
+ /* Used to pick a display thread - static, so zero-initialised */
+ static rte_atomic32_t display_stats;
+
+ /* Only one core should display stats */
+ if (rte_atomic32_test_and_set(&display_stats)) {
+ const unsigned int sleeptime = 1;
+
+ printf("Core %u displaying statistics\n", rte_lcore_id());
+
+ /* Longer initial pause so above printf is seen */
+ sleep(sleeptime * 3);
+
+ /* Loop forever: sleep always returns 0 or <= param */
+ while (sleep(sleeptime) <= sleeptime)
+ do_stats_display();
+ }
+ return 0;
+}
+
+/*
+ * Function to set all the node statistic values to zero.
+ * Called at program startup.
+ */
+static void
+clear_stats(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_nodes; i++)
+ nodes[i].stats.rx = nodes[i].stats.rx_drop = 0;
+}
+
+/*
+ * send a burst of traffic to a node, assuming there are packets
+ * available to be sent to this node
+ */
+static void
+flush_rx_queue(uint16_t node)
+{
+ uint16_t j;
+ struct node *cl;
+
+ if (cl_rx_buf[node].count == 0)
+ return;
+
+ cl = &nodes[node];
+ if (rte_ring_enqueue_bulk(cl->rx_q, (void **)cl_rx_buf[node].buffer,
+ cl_rx_buf[node].count) != 0){
+ for (j = 0; j < cl_rx_buf[node].count; j++)
+ rte_pktmbuf_free(cl_rx_buf[node].buffer[j]);
+ cl->stats.rx_drop += cl_rx_buf[node].count;
+ } else
+ cl->stats.rx += cl_rx_buf[node].count;
+
+ cl_rx_buf[node].count = 0;
+}
+
+/*
+ * marks a packet down to be sent to a particular node process
+ */
+static inline void
+enqueue_rx_packet(uint8_t node, struct rte_mbuf *buf)
+{
+ cl_rx_buf[node].buffer[cl_rx_buf[node].count++] = buf;
+}
+
+/*
+ * This function takes a group of packets and routes them
+ * individually to the node process. Very simply round-robins the packets
+ * without checking any of the packet contents.
+ */
+static void
+process_packets(uint32_t port_num __rte_unused, struct rte_mbuf *pkts[],
+ uint16_t rx_count, unsigned int socket_id)
+{
+ uint16_t i;
+ uint8_t node;
+ efd_value_t data[RTE_EFD_BURST_MAX];
+ const void *key_ptrs[RTE_EFD_BURST_MAX];
+
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[RTE_EFD_BURST_MAX];
+
+ for (i = 0; i < rx_count; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(pkts[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = (void *)&ipv4_dst_ip[i];
+ }
+
+ rte_efd_lookup_bulk(efd_table, socket_id, rx_count,
+ (const void **) key_ptrs, data);
+ for (i = 0; i < rx_count; i++) {
+ node = (uint8_t) ((uintptr_t)data[i]);
+
+ if (node >= num_nodes) {
+ /*
+ * Node is out of range, which means that
+ * flow has not been inserted
+ */
+ flow_dist_stats.drop++;
+ rte_pktmbuf_free(pkts[i]);
+ } else {
+ flow_dist_stats.distributed++;
+ enqueue_rx_packet(node, pkts[i]);
+ }
+ }
+
+ for (i = 0; i < num_nodes; i++)
+ flush_rx_queue(i);
+}
+
+/*
+ * Function called by the master lcore of the DPDK process.
+ */
+static void
+do_packet_forwarding(void)
+{
+ unsigned int port_num = 0; /* indexes the port[] array */
+ unsigned int socket_id = rte_socket_id();
+
+ for (;;) {
+ struct rte_mbuf *buf[PACKET_READ_SIZE];
+ uint16_t rx_count;
+
+ /* read a port */
+ rx_count = rte_eth_rx_burst(info->id[port_num], 0,
+ buf, PACKET_READ_SIZE);
+ info->rx_stats.rx[port_num] += rx_count;
+
+ /* Now process the NIC packets read */
+ if (likely(rx_count > 0))
+ process_packets(port_num, buf, rx_count, socket_id);
+
+ /* move to next port */
+ if (++port_num == info->num_ports)
+ port_num = 0;
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ /* initialise the system */
+ if (init(argc, argv) < 0)
+ return -1;
+ RTE_LOG(INFO, APP, "Finished Process Init.\n");
+
+ cl_rx_buf = calloc(num_nodes, sizeof(cl_rx_buf[0]));
+
+ /* clear statistics */
+ clear_stats();
+
+ /* put all other cores to sleep bar master */
+ rte_eal_mp_remote_launch(sleep_lcore, NULL, SKIP_MASTER);
+
+ do_packet_forwarding();
+ return 0;
+}
diff --git a/examples/flow_distributor/node/Makefile b/examples/flow_distributor/node/Makefile
new file mode 100644
index 0000000..59aac28
--- /dev/null
+++ b/examples/flow_distributor/node/Makefile
@@ -0,0 +1,48 @@
+# BSD LICENSE
+#
+# Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overridden by command line or environment
+include $(RTE_SDK)/mk/rte.vars.mk
+
+# binary name
+APP = node
+
+# all source are stored in SRCS-y
+SRCS-y := node.c
+
+CFLAGS += $(WERROR_FLAGS) -O3
+CFLAGS += -I$(SRCDIR)/../shared
+
+include $(RTE_SDK)/mk/rte.extapp.mk
diff --git a/examples/flow_distributor/node/node.c b/examples/flow_distributor/node/node.c
new file mode 100644
index 0000000..24fb444
--- /dev/null
+++ b/examples/flow_distributor/node/node.c
@@ -0,0 +1,417 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <sys/queue.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_malloc.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_atomic.h>
+#include <rte_branch_prediction.h>
+#include <rte_log.h>
+#include <rte_per_lcore.h>
+#include <rte_launch.h>
+#include <rte_lcore.h>
+#include <rte_ring.h>
+#include <rte_launch.h>
+#include <rte_lcore.h>
+#include <rte_debug.h>
+#include <rte_mempool.h>
+#include <rte_mbuf.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ether.h>
+#include <rte_ethdev.h>
+#include <rte_string_fns.h>
+#include <rte_ip.h>
+
+#include "common.h"
+
+/* Number of packets to attempt to read from queue */
+#define PKT_READ_SIZE ((uint16_t)32)
+
+/*
+ * Our node id number - tells us which rx queue to read, and NIC TX
+ * queue to write to.
+ */
+static uint8_t node_id;
+
+#define MBQ_CAPACITY 32
+
+/* maps input ports to output ports for packets */
+static uint8_t output_ports[RTE_MAX_ETHPORTS];
+
+/* buffers up a set of packet that are ready to send */
+struct rte_eth_dev_tx_buffer *tx_buffer[RTE_MAX_ETHPORTS];
+
+/* shared data from distributor. We update statistics here */
+static struct tx_stats *tx_stats;
+
+static struct filter_stats *filter_stats;
+
+/*
+ * print a usage message
+ */
+static void
+usage(const char *progname)
+{
+ printf("Usage: %s [EAL args] -- -n <node_id>\n\n", progname);
+}
+
+/*
+ * Convert the node id number from a string to an int.
+ */
+static int
+parse_node_num(const char *node)
+{
+ char *end = NULL;
+ unsigned long temp;
+
+ if (node == NULL || *node == '\0')
+ return -1;
+
+ temp = strtoul(node, &end, 10);
+ if (end == NULL || *end != '\0')
+ return -1;
+
+ node_id = (uint8_t)temp;
+ return 0;
+}
+
+/*
+ * Parse the application arguments to the node app.
+ */
+static int
+parse_app_args(int argc, char *argv[])
+{
+ int option_index, opt;
+ char **argvopt = argv;
+ const char *progname = NULL;
+ static struct option lgopts[] = { /* no long options */
+ {NULL, 0, 0, 0 }
+ };
+ progname = argv[0];
+
+ while ((opt = getopt_long(argc, argvopt, "n:", lgopts,
+ &option_index)) != EOF) {
+ switch (opt) {
+ case 'n':
+ if (parse_node_num(optarg) != 0) {
+ usage(progname);
+ return -1;
+ }
+ break;
+ default:
+ usage(progname);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Tx buffer error callback
+ */
+static void
+flush_tx_error_callback(struct rte_mbuf **unsent, uint16_t count,
+ void *userdata) {
+ int i;
+ uint8_t port_id = (uintptr_t)userdata;
+
+ tx_stats->tx_drop[port_id] += count;
+
+ /* free the mbufs which failed from transmit */
+ for (i = 0; i < count; i++)
+ rte_pktmbuf_free(unsent[i]);
+
+}
+
+static void
+configure_tx_buffer(uint8_t port_id, uint16_t size)
+{
+ int ret;
+
+ /* Initialize TX buffers */
+ tx_buffer[port_id] = rte_zmalloc_socket("tx_buffer",
+ RTE_ETH_TX_BUFFER_SIZE(size), 0,
+ rte_eth_dev_socket_id(port_id));
+ if (tx_buffer[port_id] == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot allocate buffer for tx "
+ "on port %u\n", (unsigned int) port_id);
+
+ rte_eth_tx_buffer_init(tx_buffer[port_id], size);
+
+ ret = rte_eth_tx_buffer_set_err_callback(tx_buffer[port_id],
+ flush_tx_error_callback, (void *)(intptr_t)port_id);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Cannot set error callback for "
+ "tx buffer on port %u\n", (unsigned int) port_id);
+}
+
+/*
+ * set up output ports so that all traffic on port gets sent out
+ * its paired port. Index using actual port numbers since that is
+ * what comes in the mbuf structure.
+ */
+static void
+configure_output_ports(const struct shared_info *info)
+{
+ int i;
+
+ if (info->num_ports > RTE_MAX_ETHPORTS)
+ rte_exit(EXIT_FAILURE, "Too many ethernet ports. "
+ "RTE_MAX_ETHPORTS = %u\n",
+ (unsigned int)RTE_MAX_ETHPORTS);
+ for (i = 0; i < info->num_ports - 1; i += 2) {
+ uint8_t p1 = info->id[i];
+ uint8_t p2 = info->id[i+1];
+
+ output_ports[p1] = p2;
+ output_ports[p2] = p1;
+
+ configure_tx_buffer(p1, MBQ_CAPACITY);
+ configure_tx_buffer(p2, MBQ_CAPACITY);
+
+ }
+}
+
+/*
+ * Create the hash table that will contain the flows that
+ * the node will handle, which will be used to decide if packet
+ * is transmitted or dropped.
+ */
+static struct rte_hash *
+create_hash_table(const struct shared_info *info)
+{
+ uint32_t num_flows_node = info->num_flows / info->num_nodes;
+ char name[RTE_HASH_NAMESIZE];
+ struct rte_hash *h;
+
+ /* create table */
+ struct rte_hash_parameters hash_params = {
+ .entries = num_flows_node * 2, /* table load = 50% */
+ .key_len = sizeof(uint32_t), /* Store IPv4 dest IP address */
+ .socket_id = rte_socket_id(),
+ .hash_func_init_val = 0,
+ };
+
+ snprintf(name, sizeof(name), "hash_table_%d", node_id);
+ hash_params.name = name;
+ h = rte_hash_create(&hash_params);
+
+ if (h == NULL)
+ rte_exit(EXIT_FAILURE,
+ "Problem creating the hash table for node %d\n",
+ node_id);
+ return h;
+}
+
+static void
+populate_hash_table(const struct rte_hash *h, const struct shared_info *info)
+{
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint32_t num_flows_node = 0;
+ uint64_t target_node;
+
+ /* Add flows in table */
+ for (i = 0; i < info->num_flows; i++) {
+ target_node = i % info->num_nodes;
+ if (target_node != node_id)
+ continue;
+
+ ip_dst = rte_cpu_to_be_32(i);
+
+ ret = rte_hash_add_key(h, (void *) &ip_dst);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u "
+ "in hash table\n", i);
+ else
+ num_flows_node++;
+
+ }
+
+ printf("Hash table: Adding 0x%x keys\n", num_flows_node);
+}
+
+/*
+ * This function performs routing of packets
+ * Just sends each input packet out an output port based solely on the input
+ * port it arrived on.
+ */
+static inline void
+transmit_packet(struct rte_mbuf *buf)
+{
+ int sent;
+ const uint8_t in_port = buf->port;
+ const uint8_t out_port = output_ports[in_port];
+ struct rte_eth_dev_tx_buffer *buffer = tx_buffer[out_port];
+
+ sent = rte_eth_tx_buffer(out_port, node_id, buffer, buf);
+ if (sent)
+ tx_stats->tx[out_port] += sent;
+
+}
+
+static inline void
+handle_packets(struct rte_hash *h, struct rte_mbuf **bufs, uint16_t num_packets)
+{
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[PKT_READ_SIZE];
+ const void *key_ptrs[PKT_READ_SIZE];
+ unsigned int i;
+ int32_t positions[PKT_READ_SIZE] = {0};
+
+ for (i = 0; i < num_packets; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(bufs[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = &ipv4_dst_ip[i];
+ }
+ /* Check if packets belongs to any flows handled by this node */
+ rte_hash_lookup_bulk(h, key_ptrs, num_packets, positions);
+
+ for (i = 0; i < num_packets; i++) {
+ if (likely(positions[i] >= 0)) {
+ filter_stats->passed++;
+ transmit_packet(bufs[i]);
+ } else {
+ filter_stats->drop++;
+ /* Drop packet, as flow is not handled by this node */
+ rte_pktmbuf_free(bufs[i]);
+ }
+ }
+}
+
+/*
+ * Application main function - loops through
+ * receiving and processing packets. Never returns
+ */
+int
+main(int argc, char *argv[])
+{
+ const struct rte_memzone *mz;
+ struct rte_ring *rx_ring;
+ struct rte_hash *h;
+ struct rte_mempool *mp;
+ struct shared_info *info;
+ int need_flush = 0; /* indicates whether we have unsent packets */
+ int retval;
+ void *pkts[PKT_READ_SIZE];
+ uint16_t sent;
+
+ retval = rte_eal_init(argc, argv);
+ if (retval < 0)
+ return -1;
+ argc -= retval;
+ argv += retval;
+
+ if (parse_app_args(argc, argv) < 0)
+ rte_exit(EXIT_FAILURE, "Invalid command-line arguments\n");
+
+ if (rte_eth_dev_count() == 0)
+ rte_exit(EXIT_FAILURE, "No Ethernet ports - bye\n");
+
+ rx_ring = rte_ring_lookup(get_rx_queue_name(node_id));
+ if (rx_ring == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get RX ring - "
+ "is distributor process running?\n");
+
+ mp = rte_mempool_lookup(PKTMBUF_POOL_NAME);
+ if (mp == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get mempool for mbufs\n");
+
+ mz = rte_memzone_lookup(MZ_SHARED_INFO);
+ if (mz == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get port info structure\n");
+ info = mz->addr;
+ tx_stats = &(info->tx_stats[node_id]);
+ filter_stats = &(info->filter_stats[node_id]);
+
+ configure_output_ports(info);
+
+ h = create_hash_table(info);
+
+ populate_hash_table(h, info);
+
+ RTE_LOG(INFO, APP, "Finished Process Init.\n");
+
+ printf("\nNode process %d handling packets\n", node_id);
+ printf("[Press Ctrl-C to quit ...]\n");
+
+ for (;;) {
+ uint16_t rx_pkts = PKT_READ_SIZE;
+ uint8_t port;
+
+ /*
+ * Try dequeuing max possible packets first, if that fails,
+ * get the most we can. Loop body should only execute once,
+ * maximum
+ */
+ while (rx_pkts > 0 &&
+ unlikely(rte_ring_dequeue_bulk(rx_ring, pkts,
+ rx_pkts) != 0))
+ rx_pkts = (uint16_t)RTE_MIN(rte_ring_count(rx_ring),
+ PKT_READ_SIZE);
+
+ if (unlikely(rx_pkts == 0)) {
+ if (need_flush)
+ for (port = 0; port < info->num_ports; port++) {
+ sent = rte_eth_tx_buffer_flush(
+ info->id[port],
+ node_id,
+ tx_buffer[port]);
+ if (unlikely(sent))
+ tx_stats->tx[port] += sent;
+ }
+ need_flush = 0;
+ continue;
+ }
+
+ handle_packets(h, (struct rte_mbuf **)pkts, rx_pkts);
+
+ need_flush = 1;
+ }
+}
diff --git a/examples/flow_distributor/shared/common.h b/examples/flow_distributor/shared/common.h
new file mode 100644
index 0000000..66c5574
--- /dev/null
+++ b/examples/flow_distributor/shared/common.h
@@ -0,0 +1,99 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _COMMON_H_
+#define _COMMON_H_
+
+#include <rte_hash_crc.h>
+#include <rte_hash.h>
+
+#define MAX_NODES 16
+/*
+ * Shared port info, including statistics information for display by distributor.
+ * Structure will be put in a memzone.
+ * - All port id values share one cache line as this data will be read-only
+ * during operation.
+ * - All rx statistic values share cache lines, as this data is written only
+ * by the distributor process. (rare reads by stats display)
+ * - The tx statistics have values for all ports per cache line, but the stats
+ * themselves are written by the nodes, so we have a distinct set, on different
+ * cache lines for each node to use.
+ */
+struct rx_stats {
+ uint64_t rx[RTE_MAX_ETHPORTS];
+} __rte_cache_aligned;
+
+struct tx_stats {
+ uint64_t tx[RTE_MAX_ETHPORTS];
+ uint64_t tx_drop[RTE_MAX_ETHPORTS];
+} __rte_cache_aligned;
+
+struct filter_stats {
+ uint64_t drop;
+ uint64_t passed;
+} __rte_cache_aligned;
+
+struct shared_info {
+ uint8_t num_nodes;
+ uint8_t num_ports;
+ uint32_t num_flows;
+ uint8_t id[RTE_MAX_ETHPORTS];
+ struct rx_stats rx_stats;
+ struct tx_stats tx_stats[MAX_NODES];
+ struct filter_stats filter_stats[MAX_NODES];
+};
+
+/* define common names for structures shared between distributor and node */
+#define MP_NODE_RXQ_NAME "MProc_Node_%u_RX"
+#define PKTMBUF_POOL_NAME "MProc_pktmbuf_pool"
+#define MZ_SHARED_INFO "MProc_shared_info"
+
+/*
+ * Given the rx queue name template above, get the queue name
+ */
+static inline const char *
+get_rx_queue_name(unsigned int id)
+{
+ /*
+ * Buffer for return value. Size calculated by %u being replaced
+ * by maximum 3 digits (plus an extra byte for safety)
+ */
+ static char buffer[sizeof(MP_NODE_RXQ_NAME) + 2];
+
+ snprintf(buffer, sizeof(buffer) - 1, MP_NODE_RXQ_NAME, id);
+ return buffer;
+}
+
+#define RTE_LOGTYPE_APP RTE_LOGTYPE_USER1
+
+#endif
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v2 4/5] doc: add EFD library section in Programmers guide
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 0/5] Elastic Flow Distributor Pablo de Lara
` (2 preceding siblings ...)
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 3/5] examples/flow_distributor: sample app to demonstrate EFD usage Pablo de Lara
@ 2017-01-07 1:06 ` Pablo de Lara
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 5/5] doc: add flow distributor guide Pablo de Lara
` (2 subsequent siblings)
6 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-07 1:06 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Sameh Gobriel
Signed-off-by: Sameh Gobriel <sameh.gobriel@intel.com>
---
MAINTAINERS | 1 +
doc/guides/prog_guide/efd_lib.rst | 413 ++++++++++
doc/guides/prog_guide/img/efd_i1.svg | 78 ++
doc/guides/prog_guide/img/efd_i10.svg | 1272 +++++++++++++++++++++++++++++
doc/guides/prog_guide/img/efd_i11.svg | 413 ++++++++++
doc/guides/prog_guide/img/efd_i12.svg | 590 +++++++++++++
doc/guides/prog_guide/img/efd_i13.svg | 1407 ++++++++++++++++++++++++++++++++
doc/guides/prog_guide/img/efd_i2.svg | 209 +++++
doc/guides/prog_guide/img/efd_i3.svg | 420 ++++++++++
doc/guides/prog_guide/img/efd_i4.svg | 364 +++++++++
doc/guides/prog_guide/img/efd_i5.svg | 578 +++++++++++++
doc/guides/prog_guide/img/efd_i6.svg | 413 ++++++++++
doc/guides/prog_guide/img/efd_i8.svg | 776 ++++++++++++++++++
doc/guides/prog_guide/img/efd_i9.svg | 271 ++++++
doc/guides/prog_guide/index.rst | 1 +
doc/guides/rel_notes/release_17_02.rst | 3 +
16 files changed, 7209 insertions(+)
create mode 100644 doc/guides/prog_guide/efd_lib.rst
create mode 100644 doc/guides/prog_guide/img/efd_i1.svg
create mode 100644 doc/guides/prog_guide/img/efd_i10.svg
create mode 100644 doc/guides/prog_guide/img/efd_i11.svg
create mode 100644 doc/guides/prog_guide/img/efd_i12.svg
create mode 100644 doc/guides/prog_guide/img/efd_i13.svg
create mode 100644 doc/guides/prog_guide/img/efd_i2.svg
create mode 100644 doc/guides/prog_guide/img/efd_i3.svg
create mode 100644 doc/guides/prog_guide/img/efd_i4.svg
create mode 100644 doc/guides/prog_guide/img/efd_i5.svg
create mode 100644 doc/guides/prog_guide/img/efd_i6.svg
create mode 100644 doc/guides/prog_guide/img/efd_i8.svg
create mode 100644 doc/guides/prog_guide/img/efd_i9.svg
diff --git a/MAINTAINERS b/MAINTAINERS
index b124f6e..66e9466 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -532,6 +532,7 @@ EFD
M: Byron Marohn <byron.marohn@intel.com>
M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
F: lib/librte_efd/
+F: doc/guides/prog_guide/efd_lib.rst
F: app/test/test_efd*
F: examples/flow_distributor/
diff --git a/doc/guides/prog_guide/efd_lib.rst b/doc/guides/prog_guide/efd_lib.rst
new file mode 100644
index 0000000..7d0a2d2
--- /dev/null
+++ b/doc/guides/prog_guide/efd_lib.rst
@@ -0,0 +1,413 @@
+.. BSD LICENSE
+ Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of Intel Corporation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+.. _Efd_Library:
+
+Elastic Flow Distributor Library
+================================
+
+Introduction
+------------
+
+In Data Centers today clustering and scheduling of distributed workloads
+is a very common task. Many workloads require a deterministic
+partitioning of a flat key space among a cluster of machines. When a
+packet enters the cluster, the ingress node will direct the packet to
+its handling node. For example, datacenters with disaggregated storage
+use storage metadata tables to forward I/O requests to the correct back end
+storage cluster, stateful packet inspection will use match incoming
+flows to signatures in flow tables to send incoming packets to their
+intended deep packet inspection (DPI) devices, and so on.
+
+EFD is a distributor library that uses perfect hashing to determine a
+target/value for a given incoming flow key. It has the following
+advantages: first, because it uses perfect hashing it does not store the
+key itself and hence lookup performance is not dependent on the key
+size. Second, the target/value can be any arbitrary value hence the
+system designer and/or operator can better optimize service rates and
+inter-cluster network traffic locating. Third, since the storage
+requirement is much smaller than a hash-based flow table (i.e. better
+fit for CPU cache), EFD can scale to millions of flow keys. Finally,
+with the current optimized library implementation, performance is fully
+scalable with number of CPU cores.
+
+Flow Based Distribution
+-----------------------
+
+Computation Based Schemes
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Flow distribution and/or load balancing can be simply done using a
+stateless computation, for instance using round-robin or a simple
+computation based on the flow key as an input. For example, a hash
+function can be used to direct a certain flow to the a target based on
+the flow key (e.g. h(key) mod n) where h(key) is the hash value of the
+flow key and n is the number of possible targets.
+
+
+.. figure:: img/efd_i1.*
+
+ Load Balancing Using Front End Node
+
+In this scheme the front end server/distributor/load balancer extracts the
+flow key from the input packet and applies a computation to determine where
+this flow should be directed. Intuitively, this scheme is very simple
+and requires no state to be kept at the front end node, and hence,
+storage requirements are minimum.
+
+
+.. figure:: img/efd_i2.*
+
+ Consistent Hashing
+
+A widely used flow distributor that belongs to the same category of
+computation-based schemes is *consistent hashing*, shown in the above figure.
+Target destinations (shown in red) are hashed into the same space as the flow
+keys (shown in blue), and keys are mapped to the nearest target in a clockwise
+fashion. Dynamically adding and removing targets with consistent hashing
+requires only K/n keys to be remapped on average, where K is the number of
+keys, and n is the number of targets. In contrast, in a traditional hash-based
+scheme, a change in the number of targets causes nearly all keys to be
+remapped.
+
+Although computation-based schemes are simple and need very little
+storage requirement, they suffer from the drawback that the system
+designer/operator can’t fully control the target to assign a specific
+key, as this is dictated by the hash function.
+Deterministically co-locating of keys together (for example, to minimize
+inter-server traffic or to optimize for network traffic conditions,
+target load, etc.) is simply not possible.
+
+Flow-Table Based Schemes
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+When using a Flow-Table based scheme to handle flow distribution/load
+balancing, in contrast with computation-based schemes, the system designer
+has the flexibility of assigning a given flow to any given
+target. The flow table (e.g. DPDK RTE Hash Library) will simply store
+both the flow key and the target value.
+
+.. figure:: img/efd_i3.*
+
+ Table Based Flow Distribution
+
+As shown in the above figure when doing a lookup the flow-table is indexed with
+the hash of the flow key and the keys (more than one is possible,
+because of hash collision) stored in this index and corresponding values
+are retrieved. The retrieved key(s) is matched with the input flow key
+and if there is a match the value (target id) is returned.
+
+The drawback of using a hash table for flow distribution/load balancing
+is the storage requirement, since the flow table need to store keys,
+signatures and target values. This doesn’t allow this scheme to scale to
+millions of flow keys. Large tables will usually not fit in
+the CPU cache, and hence, the lookup performance is degraded because of
+the latency to access the main memory.
+
+EFD Based Scheme
+~~~~~~~~~~~~~~~~
+
+EFD combines the advantages of both flow-table based and computation-based
+schemes. It doesn’t require the large storage necessary for
+flow-table based schemes (because EFD doesn’t store the key as explained
+below), and it supports any arbitrary value for any given key.
+
+.. figure:: img/efd_i4.*
+
+ Searching for Perfect Hash Function
+
+The basic idea of EFD is when a given key is to be inserted, a family of
+hash functions is searched until the correct hash function that maps the
+input key to the correct value is found, as shown in the above figure. However,
+rather than explicitly storing all keys and their associated values, EFD
+stores only indices of hash functions that map keys to values, and
+thereby consumes much less space than conventional flow-based tables.
+The lookup operation is very simple, similar to a computational-based
+scheme: given an input key the lookup operation is reduced to hashing
+that key with the correct hash function.
+
+.. figure:: img/efd_i5.*
+
+ Divide and Conquer for Millions of Keys
+
+Intuitively, finding a hash function that maps each of a large number
+(millions) of input keys to the correct output value is effectively
+impossible, as a result EFD, as shown in the above figure, breaks the problem
+into smaller pieces (divide and conquer). EFD divides the entire input
+key set into many small groups. Each group consists of approximately
+20-28 keys (a configurable parameter for the library), then, for each
+small group, a brute force search to find a hash function that produces
+the correct outputs for each key in the group.
+
+It should be mentioned that since in the online lookup table for EFD
+doesn’t store the key itself, the size of the EFD table is independent
+of the key size and hence EFD lookup performance which is almost
+constant irrespective of the length of the key which is a highly
+desirable feature especially for longer keys.
+
+In summary, EFD is a set separation data structure that supports millions of
+keys. It is used to distribute a given key to an intended target. By itself
+EFD is not a FIB data structure with an exact match the input flow key.
+
+.. _Efd_example:
+
+Example of EFD Library Usage
+----------------------------
+
+EFD can be used along the data path of many network functions and middleboxes.
+As previously mentioned, it can used as an index table for
+<key,value> pairs, meta-data for objects, a flow-level load balancer, etc.
+The following figure shows an example of using EFD as a flow-level load
+balancer, where flows are received at a front end server before being forwarded
+to the target back end server for processing. The system designer would
+deterministically co-locate flows together in order to minimize cross-server
+interaction.
+(For example, flows requesting certain webpage objects are co-located
+together, to minimize forwarding of common objects across servers).
+
+.. figure:: img/efd_i6.*
+
+ EFD as a Flow-Level Load Balancer
+
+As shown in the figure the front end server will have an EFD table that
+stores for each group what is the perfect hash index that satisfies the
+correct output. Because the table size is small and fits in cache (since
+keys are not stored) it sustains a large number of flows (N\*X, where N
+is the maximum number of flows served by each back end server of the X
+possible targets).
+
+With an input flow key, the group id is computed (for example, using
+last few bits of CRC hash) and then the EFD table is indexed with the
+group id to retrieve the corresponding hash index to use. Once the index
+is retrieved the key is hashed using this hash function and the result
+will be the intended correct target where this flow is supposed to be
+processed.
+
+It should be noted that as a result of EFD not matching the exact key but
+rather distributing the flows to a target back end node based on the
+perfect hash index, a key that has not been inserted before
+will be distributed to a valid target. Hence, a local table which stores
+the flows served at each node is used and is
+exact matched with the input key to rule out new never seen before
+flows.
+
+.. _Efd_api:
+
+Library API Overview
+--------------------
+
+The EFD library API is created with a very similar semantics of a
+hash-index or a flow table, the application creates an EFD table for a
+given maximum number of flows, a function is called to insert a flow key
+with a specific target value, and another function is used to retrieve
+target values for a given individual flow key or a bulk of keys.
+
+EFD Table Create
+~~~~~~~~~~~~~~~~
+
+The function *rte\_efd\_create("flow table", num\_flows,
+online\_socket\_bitmask, offline\_socket\_bitmask)* is used to create
+and return a pointer to an EFD table that is sized to hold up to
+num\_flows key. The online version of the EFD table (the one that does
+not store the keys and is used for lookups) will be allocated and
+created in the last level cache (LLC) of the socket defined by the
+online\_socket\_bitmask, while the offline EFD table (the one that
+stores the keys and is used for key inserts and for computing the
+perfect hashing) is allocated and created in the LLC of the socket
+defined by offline\_socket\_bitmask. It should be noted, that for
+highest performance the socket id should match that where the thread is
+running, i.e. the online EFD lookup table should be created on the same
+socket as where the lookup thread is running.
+
+EFD Insert and Update
+~~~~~~~~~~~~~~~~~~~~~
+
+The EFD function to insert a key or update a key to a new value is
+*rte\_efd\_update(struct rte\_efd\_table \*table, unsigned socket\_id,
+const efd\_key\_t \*key, efd\_value\_t value).* This function will update an
+existing key to a new value (target) if the key has already been inserted
+before, or will insert the <key,value> pair if this key has not been inserted
+before. It will return *0* upon success. It will return
+*EFD\_UPDATE\_WARN\_GROUP\_FULL (1)* if the operation is insert, and the
+last available space in the key's group was just used. It will return
+*EFD\_UPDATE\_FAILED (2)* when the insertion or update has failed (either it
+failed to find a suitable perfect hash or the group was full). The function
+will return *EFD\_UPDATE\_NO\_CHANGE (3)* if there is no change to the EFD
+table (i.e, same value already exists).
+
+EFD Lookup
+~~~~~~~~~~
+
+To lookup a certain key in an EFD table, the function
+*rte\_efd\_lookup(const struct rte\_efd\_table \*table, unsigned
+socket\_id, const efd\_key\_t \*key)* is used to return the value
+associated with single key. As previously mentioned, if the key has been
+inserted, the correct value inserted is returned, if the key has not been
+inserted before, a ‘random’ value (based on hashing of the key) is
+returned. For better performance and to decrease the overhead of
+function calls per key, it is always recommended to use a bulk lookup
+function (simultaneous lookup of multiple keys) instead of a single key
+lookup function. *rte\_efd\_lookup\_bulk(const struct rte\_efd\_table
+\*table, unsigned socket\_id, int num\_keys, const efd\_key\_t \*const
+\*key\_list, efd\_value\_t \*value\_list)* is the bulk lookup function,
+that looks up num\_keys simultaneously stored in the key\_list and the
+corresponding return values will be returned in the value\_list.
+
+EFD Delete
+~~~~~~~~~~
+
+To delete a certain key in an EFD table, the function
+*rte\_efd\_delete(struct rte\_efd\_table \*table, unsigned socket\_id,
+const efd\_key\_t \*key, efd\_value\_t \*prev\_value)* can be used. The
+function returns zero upon success when the key has been found and
+deleted. Socket\_id is the parameter to use to lookup the existing value, which
+is ideally the caller's socket id. The previous value associated with
+this key will be returned in the prev\_value argument.
+
+.. _Efd_internals:
+
+Library Internals
+-----------------
+
+This section provides the brief high-level idea and an overview
+of the library internals to accompany the RFC. The intent of this
+section is to explain to readers the high-level implementation of
+insert, lookup and group rebalancing in the EFD library.
+
+Insert Function Internals
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As previously mentioned the EFD divides the whole set of keys into
+groups of a manageable size (e.g. 28 keys) and then searches for the
+perfect hash that satisfies the intended target value for each key. EFD
+stores two version of the <key,value> table:
+
+- Offline Version (in memory): Only used for the insertion/update
+ operation, which is less frequent than the lookup operation. In the
+ offline version the exact keys for each group is stored. When a new
+ key is added, the hash function is updated that will satisfy the
+ value for the new key together with the all old keys already inserted
+ in this group.
+
+- Online Version (in cache): Used for the frequent lookup operation. In
+ the online version, as previously mentioned, the keys are not stored
+ but rather only the hash index for each group.
+
+.. figure:: img/efd_i8.*
+
+ Group Assignment
+
+The above figure depicts the group assignment for 7 flow keys as an example.
+Given a flow key, a hash function (in our implementation CRC hash) is
+used to get the group id. As shown in the figure, the groups can be
+unbalanced. (We highlight group rebalancing further below).
+
+.. figure:: img/efd_i9.*
+
+ Perfect Hash Search - Assigned Keys & Target Value
+
+Focusing on one group that has four keys, the above figure depicts the search
+algorithm to find the perfect hash function. Assuming that the target
+value bit for the keys is as shown in the figure, then the online EFD
+table will store a 16 bit hash index and 16 bit lookup table per group
+per value bit.
+
+.. figure:: img/efd_i10.*
+
+ Perfect Hash Search - Satisfy Target Values
+
+For a given keyX, a hash function
+(h(keyX,seed1)+index\*h(keyX,seed2)) is used to point to certain bit
+index in the 16bit lookup\_table value, as shown in the above figure. The
+insert function will brute force search for all possible values for the
+hash index until a non conflicting lookup\_table is found.
+
+
+.. figure:: img/efd_i11.*
+
+ Finding Hash Index for Conflict Free lookup\_table
+
+For example, since both key3 and key7 have a target bit value of 1, it
+is okay if the hash function of both keys point to the same bit in the
+lookup table. A conflict will occur if a hash index is used that maps
+both Key4 and Key7 to the same index in the lookup\_table,
+as shown in the above figure, since their target value bit are not the same.
+Once a hash index is found that produces a lookup\_table with no
+contradictions, this index is stored for this group. This procedure is
+repeated for each bit of target value.
+
+Lookup Function Internals
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The design principle of EFD is that lookups are much more frequent than
+inserts, and hence, EFD's design optimizes for the lookups which are
+faster and much simpler than the slower insert procedure (inserts are
+slow, because of perfect hash search as previously discussed).
+
+.. figure:: img/efd_i12.*
+
+ EFD Lookup Operation
+
+The previous figure depicts the lookup operation for EFD. Given an input key,
+the group id is computed (using CRC hash) and then the hash index for this
+group is retrieved from the EFD table. Using the retrieved hash index,
+the hash function h(key,seed1)+index\*h(key,seed2) is used which will
+result in an index in the lookup\_table, the bit corresponding to this
+index will be the target value bit. This procedure is repeated for each
+bit of the target value.
+
+Group Rebalancing Function Internals
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When discussing EFD inserts and lookups, the discussion is simplified by
+assuming that a group id is simply a result of hash function. However,
+since hashing in general is not perfect and will not always produce a
+uniform output, this simplified assumption will lead to unbalanced
+groups, i.e., some group will have more keys than other groups.
+Typically, and to minimize insert time with an increasing number of keys,
+it is preferable that all groups will have a balanced number of keys, so
+the brute force search for the perfect hash terminates with a valid hash
+index. In order to achieve this target, groups are rebalanced during
+runtime inserts, and keys are moved around from a busy group to a less
+crowded group as the more keys are inserted.
+
+.. figure:: img/efd_i13.*
+
+ Runtime Group Rebalancing
+
+The previous figure depicts the high level idea of group rebalancing, given an
+input key the hash result is split into two parts a chunk id and 8\_bit
+bin id. A chunk contains 64 different groups and 256 bins (i.e. for any
+given bin it can map to 4 distinct groups). When a key is inserted the
+bin id is computed, for example in figure bin\_id=2, and since each bin
+can be mapped to one of four different groups (2 bit storage), the four
+possible mappings are evaluated and the one that will result in a
+balanced key distribution across these four is selected the mapping result
+is stored in these two bits.
diff --git a/doc/guides/prog_guide/img/efd_i1.svg b/doc/guides/prog_guide/img/efd_i1.svg
new file mode 100644
index 0000000..cf70b09
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i1.svg
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i1.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="4.05258in" height="2.27108in"
+ viewBox="0 0 291.786 163.518" xml:space="preserve" color-interpolation-filters="sRGB" class="st2">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="19" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape1-1" v:mID="1" v:groupContext="shape" transform="translate(18.375,-18.375)">
+ <title>Sheet.1</title>
+ <rect v:rectContext="foreign" x="0" y="36.75" width="255.036" height="126.768" class="st1"/>
+ <image x="0" y="36.75" width="255.036" height="126.768" preserveAspectRatio="none" xlink:href="data:image/png;base64,
+ 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
+ YII="/>
+ <rect v:rectContext="foreign" x="0" y="36.75" width="255.036" height="126.768" class="st1"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i10.svg b/doc/guides/prog_guide/img/efd_i10.svg
new file mode 100644
index 0000000..36fe064
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i10.svg
@@ -0,0 +1,1272 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i10.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="9.44457in" height="3.10402in"
+ viewBox="0 0 680.009 223.489" xml:space="preserve" color-interpolation-filters="sRGB" class="st2">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false"/>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="19" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape1-1" v:mID="1" v:groupContext="shape" transform="translate(0.375,-0.375)">
+ <title>Sheet.1</title>
+ <rect v:rectContext="foreign" x="0" y="0.750001" width="679.259" height="222.739" class="st1"/>
+ <image x="0" y="0.750001" width="679.259" height="222.739" preserveAspectRatio="none" xlink:href="data:image/png;base64,
+ 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
+ Oj7KyStFfuQ9tuuVp21ti0zHmsk6X1qPq/a9LzaVWp96EmW0Iz+Ko3pto1gkl51q6/m4bbcRGeWuV9kxrdKefJxH6zlUErnrOI7qaQNZ
+ 5Oct+dEYykcy5hDpRjL0+3hNRxnl9FHfYZyBDXR74b4+lsrY6jGQ13zAj0jjkNBXvdqMWqWVTPqqI1/Hif+1UfTb1n9bRrEKjb6bWn97
+ Rr7Nf+pbc534D3WlFd/VzmGPKzGgJ+q3hKvc7OhPCuIM+SPmvq/XFv6F6viXWK+VuIhVaHQeGLePTZ5t82dO3U6p69EXf5XRhrI9Y1JG
+ XmPXvvBuxxgkz11jQH9J2Lvoh3/9j222E4nHBL8TTHrpu4Z/9MU/retGF4EGzDbbbB+vBSUSt0JdgAaLz744FZkvTEEqMzu+JGAnCnbG
+ bIEvJD4u4+j/or3/ZaPEYEyQ5djbotvzn3DUFzPEMd9lobXJm46t7v55Koj/+eKTCyPoseh8/Nf/4w8qP8NyXg8xQMuCtuQsx3WpnOHD
+ vIE+j34elJc8uk7lyqvtraH5aR4kzi1410fHokSZtyOZ65SvuahO5K5TH82T/S0Z5dpX3vUao+qL3P27rpDL3ZaksoifyY61J3kux/qS
+ VB75Oq99bzXeyAc0stM4C//CM19S1wf+es15v8sKRWMrP5NFcSL7TqXP8Uc5UB7GMTtQZLcXHGsUR2Xa59jq5/1Ru+AjWSPEiuSzeMyh
+ +oossD1mzXD43YJ/8W157VkXAE+/ZU++yGHr28CArkNGx7Hi1b7NwWrnsOgOfItR+HB90eM1O21Jrh/xxe7k9ZqNfd567ZDLnvOA4jPj
+ 00/z6DqS5Rnaoc9rguT2zmt/JGds11cqstl4KlM52kvC3kN/7Y9+1mY6kXhMaHEY/2ngS8dv/El5/rNOxHv1bHjA1v72X3xRtyl/9JP1
+ jq5EInF54Jsx+KbT73zv7+sP8OK+JCUSt4AvNqMFKFoubsnXvtiD1L/otBhIHHa90O6jxff4dKdJFEd3tGiM8MWJLfNexBRqx6KFYd/B
+ 4oXQxTE0/0UOkEGnc1hkq124ogvjhMcym9cn23BeZRzYLcdqudQYjS92l8n54KPz2AvH1VZIc3CZ6kY8xrs1RvmglePv+lk/sndbldGe
+ c+l+Iz3kPpb6khayyG7gq/2eg8hH9mg1t0in181Ipu0oXtR3HWOrP8jtKBvlokRZj9n6lI9a5WfjLGxMFtlF+ki3mIMiQ58y2GhOrotk
+ kQ3bY3nG8XhbPHPYk0ckG/lXKjzlaPdiFUf6jOdy8lUvMu9H1P0Kz9bjRq3ro776RLEpqxT/tvWdvm0+V7/ViCGxqfffROj4WwVUPX0L
+ 4beOvr3wKL78/erfzNfjaDZTHq2NR0THobbIB6i/waWvvoqq1/G0nfGDuT/MIe0G6zXmbHHC9VoZc7peKPr4PDSbwvM89Dlrfot2S6Y5
+ qx3kHhMU2W/1VR7xaEfjKUW+9Lk07F0Un5fQ+y2RSNwHqBn1+tDFCsOABiztr//x3+b/SJlI3Bn44cUP8OL+JCUS1wYXmbMFKmXecmGr
+ PsL7y0Z9UaBdW+iD1xeOpx00T7EYZ/UdwkZ82Th8i09ygD7KzVrNc/EyxDGKne7ywY6Y/tKDGKscDrIeo4213oXb7IZxJFfaFQrnVfSV
+ SuzVvFqsVc7Q2ZhhzozRcg53IFMv8XT30KLQrO1Ihnhux/mjnrpbI8pBKTq2Bd9a94/4SAY/ktu4nfbdbqRTf48RySJflW3qSh8xIx9Q
+ H1P0lLmPj6H6ka3yjLuILTrK1Hdlr/rAl/aqJ4UxCtGn+gUx1V5l3nedyyJex3Z/lUW+bu/9SKZ9xHcZ+S5rree3yYsfW7UDRX7eguBL
+ onwvRuPPxlPex/V+5K992K70A1u1GcUbyYIY+tsWf/s2+P5u9w9aHgtlpc9iFwut9Nfdqod1wMFefcPYkr/GW+kqNb8i1+NY/LHU/N0u
+ +jSXo+bP/IKYK77Y7ltXDNZrEvPo9Zrp1+ehxDabSipvY1fS4wtlzS+yo4592ng740GMofIqa/0+RumfM941YO+hqBXtueYSicTlgffN
+ XiMC6f15NjRgKwwnEonHAXbwX/7GTyQ2oAvNaPGpMu87z0VuWwTry8bh5UfsaNv63BnTd5swrsRZ6BintHzZOBQnmx/0M77loDtwur/m
+ Rr9C+lK0eHkstMhB/St/iIOXvMWLKf234lis1bxCTj3bRuG8NpvFWDoO+TLWNOdmsx2nUPEJ59pt3Y99tH3cxrOvPpDdGp6D9lUe8dEx
+ eD/S9bkQ/UIuMrRb46h8i9+SuS6SsdXjoDySeQsbkupAkcxJdVu8yhbjNjnH8zknsR/lpWP0uAMb79NnZjOSqW+Ut/LdTuSqZ7/amZy6
+ qK/ySObxZn6kaD40zigm5OrrdmrrPIi+qlMb6Pei5zIZb6ab9cn3fFvfbVTPfCjz+C6rdhbPbTxG8wl/22hPvtjxN2fxh1olxqVflz+N
+ sfgNKvLV77v7OjG+jiXxug146rtsuR5YHCuo8gc//aMqCq5sD7/nHHM5d8Dmt5Ut39Xca760bbJwXdFyZpyu0xiFX6wXNC7a4r86D4Mc
+ QnmNEdhWuflFdh7T+ZmOvLfkPT770ZiUqW3tC4/2GuB7qL6LFsI7av4L80TiNuC/MF/chxevD2nQ0uKjxolE4nGARWL9xATuU96ricS1
+ oYtQLkS3+JlOeH3ZWL7IiB3GL7SyDeKsdqI0f74wrXbA0gYU5VhaffFa7K4N7HUHz2LXUZSDjt34lT9z5VgeJ8ql2KzmCvF9vJbX0LbQ
+ MGe0bcw9OfNlbxVH8y8yjM+XysXxqa3yjNPpEKfTyOfWYJ6jnI7l99hG/UpFxvnZkxf1Lp/Zb8ki30g20vMYRj5+DUZ2U1lrZ2Mor322
+ 8CW5TO32jNWpyMIYout9oVFc8iO99mdjuo3bqg906qM65V3GmCqLdKQorttrP6Lo3Ghc2qks4ke+KkN/LzguKRqTuq3rK5Ip3+OY3m3I
+ RzGiHHpc6bsNyOLO1gGRXS8O+7U68IMNfQ/F4Sc7/a17iiu+W7GVRy5O1aboK7/cEVzXHn4MzQcFUsX/8K//48G2xmsxG6+F1cXvteYw
+ yHc19/RT+9YfnieJ09dr0KFt+uW6o8Vl7NLG50FyqCQxQeQ1107NluQ2PY7FVDs5voNMbY2vbSMfa9SPxhrZYizy1wILUFqMMsL3T69N
+ /+XHP1/xr6W9Bn9se23+3BY7aqPWedo/Fz663xb34sXAoKXFX38SicTj4Q8++4crPgQSiQC66NTF6Iivi+CZ7slGXxLqAp/6IJ6+bCx2
+ 5Yqu70SBP3RNv9iJovG9db7468sUCsVdRzJfFjf7WI0WOfTc1P/wMkj/1S7c0bFQLzareYWO42i+YgvU44M+GovjmH+YM23gU/hVzhpD
+ bQuF8xfZK4+2+a9stE+bW6PnFuTedZC341jxYr+nH/FoORZpZKu8t3v4LZn3I77naDrNXe1dT5uRrffJ0151VRbYer+P1fojO7dXO8ii
+ OCpnnzaui9oZz77GdBvX65hq661SjzHQOT+TcWy2M5+I38odelKUL+2O4V3GluPsRc1J/Ek+LuNWe9ORB8305COZ88xL5ZSpHfvMLbKp
+ smZDWemHv20gxjO7xc5h2oIvxN+lEda/Vx/VHbOOxW8ViT56TBGvbcuLhH9J8+WXh52Yq53D4qd/wAae5kViyhj8jZ0WuJ2P5lT1fZyD
+ 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
+ bMN5pU2JE36PWPKe5txigHbNfyGMQTy9fBa9+nT71mostvSR2Do/7cq+HXpejTS3KMeo7zqXLfSF97mI7Dym9hc5txgjX+N5XQ2v0UqF
+ 95ikLZnH85ak8fvxlH7nC4l+mHen5qsE2cKmkfp6nN4XeYnFXfyL+2Rh34j9aIxIxzguH8zFaucw7Tymy/bwPiYpslHiXIGPYo/GC/nW
+ 1rjWj+xVNpIzzy2fSoXn2OqzF/QF+ThdJ2Ms5CNq9h5PdSTasNXYrlNyWWQ7kxXiPbradTqx099i/ZcyXS7++ju92Lnajh2/eWqv7eo3
+ fpQf5C4L9Pr72u9D2qo9qPiwqL3acStxV7/51EX2ypvvav6Qc7NdjaHXj+ijdRJoc71Q+H4eKJcY4XmgHY9Hj7HHMLnldbBrevBqu/Cz
+ /l67Pf0oD7cjjzaRSCROghaasjicSDw06r2qBeJE4pqYLT7J04YtF660g1zjVH65E4U47Hp58uOLAnDYSSKxLU5/2eBYjbTICduq92OI
+ ePaLPXfbAPVFjXZFp0VN4PBCdNAdclnufgb0OPGiwxc7YrVzuMX64I//fbOQY6FNpXhe67y0YwEN5xXU7OKcD2NMc2aMZqsvaz3nhc3a
+ rs8hSW3bMSzk2lJHoo5+t4bmwHbE+3GNeLXvx1tk7CvRnjztyFNf+8LTp9sWqnxgW+nA8/pb7NpTe4/p41W5yNxuD68y9jkux1jYBDva
+ 1C7y9b62alP7pqdvj/G0Y66P77G0dflMF/kyr0AXflai6TppLOW9332LbDRmZK86l3U70amMfbUnDxuNpzxtZ32XU+Zjr3ToNz4aE7K9
+ iHJRWTTGrFX7SgMdfZw85ky+ZTviaw4b92hg91SARLz5v3ShD4HiZPctNqp/+i08jKlrgO7H3Jo/bRf9EV9a/f3tfywVfW0RD1T6YQ5i
+ M/3NJzGu8zKnijoPzCEaA3LGqnxwbsxmuF5rdqvzAHmj6XlYHI/0vQW5D1oZp9upj/dnfI0RydBv8sifdrR1G8rZJhKJxEnQQlOhRCLx
+ uFjcr2gTiWtitBDFApZy1dHefRa+h76+JOhLW4TZy4zGqeP6mEUWob9wqa359baQF0MjHIqaxV/zqHGW//QzAooyLEKHO4dbrCgPLVgf
+ M6+Hb/tKfM258Fv+w5wZo7XDnDlmsdEXy/pS1+Q9DnPzdkXNT/XkIb81mL8fR6WWK/VRznt4+JIoU7tKpa92SqG9+ETj8FwvZMvrr8rU
+ t8Ys5GN1XRuPFNlqf8RrP7RprYy1yhs0813IJryMsdB52+OKr8YZybTvOh9TeerMRgssh3u0yGFDP41FX9pQ7rbQkdhXvdtS5jrlGS8c
+ Q2SRbySb8aAaV+SRPtQVnnmN8oF8L2iv/pfiNc+qK/LOi523ridFeZJmvgM+fLZwDPZHdjXG0x9hIqCgqDt2gfqbXmJs/X4T1Z7jSe7L
+ PEwX9DWPwzeHmz443kqF1z/sjLBYS2kMi+X8MeuKxRgaq4w3/X1oFKE+i4ru6POgY3g+kUzzoaxSEAvU++oXxHA/6NRWbeinPnt47SNG
+ IpFInAQtNGVxOJF4aNR7VQvEicQ1wcVmX+Q2XhevqqNMW1L3O8j5ksAXCd+pSvSCK+P0MZdxVruJFuOtXwoXxUmN7byMp4UTRY+FsTi+
+ 9htFx4i8GZ859kIrY0kM9IfHUnz05YvzERVnw3kl3+lwXtx/mHPLbzF3Lc4w5+Kj8wK7HmcRw3glHU99VQeC7tbQOem5NV51zNH7Ea++
+ lNe+xVGdEuXaukxtKwU6jVX5p+tvdT9q7BrPZB5vZOt8G/eJNz2JMh+n+Q/zjnxdx35tZWz6eeuk8SpJTMZT3yjeHn0UkzmjLTTdOTz0
+ b7y2lI/0bEf8XrtKYuvjkbzvsi1+pq9jikzlW/nQZi881ozn2Kqrsg1/+pLcTuNGeo9T5SKL7FymfI2x49lSKbDruoM+KjD2ncTiD+B+
+ eIo//iNv/d2CzSLn1rb8DzHEhjxtu83yX+f0+5B27k++0jJ/xWEumr3nBBrGfYp5OM6t9drBZx1rcm5g03MarBckzvw8tFicLx3D+Ujm
+ eo/lvtQrRXYui2xVB762gb/6Uq8xoEskEomToIWmQolE4nGxuF/RJhLXRF98ysITMl2Eui6Se1sJvPSrL/vSMqaPubI3n1VOait24DW2
+ 8tSjJUVxyNNGx9Z+j+1ts41km76tbWPx5asXh6s+8Gn2laqs0Gos5bVtfPU1vfqqPGrhX+y1IFV3SUV5ME/XqU2l0m9xu0xtbw3kwtyZ
+ G2mUo8tGvMpAGnOhK/xiPPPzOJXMhzlHOlLp6/VHH/AO/YOG+ne++EV/1AB6Ychzk/5u32Z/XN7FFzb0Z19Jj0uJ41rL8Rf/HLoRdVr4
+ iI6v54a4NQcZh1T6jKeoxbGiWxWHeWyM03ICjXYqrubY4i7+2TttgtyerpG13YrHeDO98irTPF2nvJ7jla6QxhnlMhoL8r3QWItzHfAg
+ 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
+ F+oLkL0MgfASxJcl9lUOvrbiSxvXRTFo5zxt6asytZ31vY34We7Kb+lcprweC/jebzTyIx8ed+PdRmXKR7JRHmhnOrTQdxvTkaK++nlM
+ kOvQf1R4rqSoz+Mm6TWnduS3Ynu7JVMa+XBM1fV8zVZb5yOZ+oziKc9x3dbtZrz23SaKrX3qI7tRS9L+Fs92NJaSymZ2JB/D5cq7TPOp
+ YwU+nSyG60e5juKypV/0fGFeWzG8VXJZ1O9jiF7t1CYxx+g60TkczbXL2Xe/rlO7Fp96EOMo763qNaZSIvGcoO+bha4NFiPLyKtvzmrh
+ cLRrVms7o6Ljsb6A7xJlAXAU7xI7h2e+tImKtseAx4F4KHgeA+SGY4y+ratF12OOYXb+9VMSo5gcd6R3nJonQPmoOHzKnJ6L3DmcSLxC
+ LO5XtInEvRG+IPHliH2TK+lLGWNQpzGP4WvciV55ta18kVM28oUetBXbecbesmXr42z5KQ8/77tM/fS43U750BfU/CMf5ZnHagzRe2zv
+ q0x1zwGab8gHMp0zlanNLJbbOr/qF17Hcn3Ej/ROp/iqbHYs3h7Lq2yr7/OzmjPzdaKdxtjDa7/LW6vj+zxpDJepbo/fVp8tY7mMpHLl
+ ZzL6qk2k9/iR3mM4ue+WLOqP+MR+cM4WVObSz6Gf8+i+GLW8JpRmcZw8HiiKk0g8J7TaEN87rw0t4nrRjcXbrR2ZLC5q0fYcX+4S5c5h
+ 5AgbkO76VdAGflHRUXfBetGROYxiA/CBzbn/6p0F11N2ufIYfXe07qo99hhm5587v2cxOaejPwAozsnTrwnFOXN6LvKbw4nEK0S9V+V+
+ bXdxInF/8MWIL0T6oqRy1bkdSfsjvvoGuiim87RRncoXNsarzxbvMUgje7TUd1uTq+0WH/moTaVmR1u3U90qF7HtskYz/2gM7VcyG+UX
+ do2eE/wYyOs8jOZMefdzGWlla7G6f+N73/SRL/muN523x/J7ZaQoh718JCPPFvGjedBxaUOZx2BL0n4Um3zU9zGUd9tItsdvltPUx+Ru
+ G+notyXTftc3HW1W/iYnub3qR3wk2+KRByhxPHDuOI9o9ZrQOfa5rnYio73ael/lURxSZK86HxuUSDwn6PtmoWtj9s3Zb37zm1W+9a+9
+ UcSDnRZdz/EFuEtUyW0Ux+wc/uCDD5rkgNGOVAWL3bDFpyhOBcZAHBQyZ7ttWSAdkfseewwotgKz88/52kN7isPAMXn6DumR7945vQZy
+ 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
+ fNPEanaQ3v8yTr9LFDOMdd0yii29F0U2dq7T1tnsocXgOxD2Ht/qImbzHaj+83thawMPOYO0fYTQXmKOdSOXv4en+0O5uqcQXB9d15hb
+ pOXl96zko2YZt5/PyX7t9yetR/46xwi9+ED/D3sc10a+b6i5uUZeJ/Qi2YljrR91sj+vlt+Rtsb2O3Kqu/3z/VQT6/Ka3NjdrIl9htCe
+ 61j8+C6e3sXc4qpQHoTyBh2xRU//94JzkUP5w/dFfNM/9zgOgjnbZzGsRy3INLfo4VffbGRZ1YE+y8c84owxxhhjjDHGEHhoigcmfWDL
+ 5myDXebxYApqA4HzJB7NrtoIaGvwCadoKGxzz02hoJ+CKrJpwrSakYPmRNDfNVvs8dA8PFTzHjO91cRa0ZQ4xVBsSNsTfGg28Im4MabZ
+ Yixr8H3s7xQl2dynltdrFeFr7k0ivo4YUbPsMW0qteuNcdbk2jSGqq/VprrnBtppbW7o1PvI+2/r4jsS9HuNPdXYppNs9gMpsXxP6ncH
+ 9XiEZPV47VYT1xFx9fpWeS1n833UtTm+5Wy+Qy1mff/P97nff47JBHvB2NbntfSzGPbAdWQP2rCEPag1I57XLbL5DjR7fH7pbxfrQmCr
+ a47fO9wP7L/e2yxPx5ASE+uCfl1YX+5ZvzdSH/vpn0/LH/JQs+Yl32PUK+NmT30/xc8S9pYTgvWGfZCf/8zpzdvmRy5/Pn0Nyb3oz6u9
+ 70irz9+Rer+wfsnH3uqeu/3k4zX5s+d7WPeLPNnzyT6ezN1+XpTH+axzbKm3+a1rfJI7fF9Qq46TP/f6ekWy/YW0/D6f7SHLVz1qYc1Z
+ HAvsWZ3Mz3FVEnvkGmOMMcYYY4wh8JClD1BVypwf5jiGY9VW4rl5G2gjocaWcWgOkw8PxoHmbh7myYfTVlG324twTqCNj6F50HLqXqp+
+ 2ivbI394EG8x2Hetg/rFx80N/lf4p2OpxSfe+nXGPihme5+KvcXxZzDkt9xhvTaiIXJu4DV/kc01t5yNnfcodYfTlS1u2tQpIz5PUP3h
+ q1JqRywEa1Je/x60mPR7gHoRE6PsPb0+jitzfL79voWv2D/88MNqf/8Hn5xiW97X/+S/Vjvo+yi+4TtE6+AeBvqbmJ4YlNy4/5l/M4ZE
+ HNtirmu1PaT3CPmtxuYe0ecy/Q6UvO/85BfNc6Kv23Lz38ApF7WHPRXBNfTfabHxb7TG47pZ2Nb0ze+UY0rd9N4gpo1YW38fuC+n73Gr
+ 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
+ asyuETpGY4wxxhhjjDEEHpbwEMUPUOxjfzpvD2BcYzUP0XpsY1/kzXz6kKwxWFfX1pqsa+5szg/jWR3Es01jMmGf1taHb6zBcZkty2Gb
+ xqx05LJo7SwXvkw4rkrRww5bVq9Li81qbWLFlumX2jJfNr9ob+SDTfO7lDlfO2SIazE6n9XkNSEck/mzPc9GxGrdISak6LV24pvp2A9s
+ WT2NYR/ms/pcI+aznCP1ZmOVokc+bLzm6jrSWjTXnNnIesTvxaiOHLVrjcyHPcZ1Ys76qt5KV5uOqzU5jnXO7X7JS/fOMRKvtbM1VIfA
+ xnms63hXPfbO64doTGZDHtsxZx/7D0nL1RxewxhjjDHGGGMMwQ9NrM/GTI+HrSav/863X4Bf+f3v5jmIr/MD9dWGOa07xPTa57gf//wf
+ znvKarIe0utSzWxebS0nq3epLfNVKfpsPY1TW6bH2GtJTrEP9wt+jtGx7W3z+cO/yo2R99Jqdal2iud5Fos4zmU79Jaz+52d1Wn5470i
+ v66ntpmv1d3YeA49i4W9xiUx2bzGtrnqbONc/t5oTcRnMovj+aom69Wf5LGN7Vm97B7BpzriNBa+Gktxkrf5rhRb+pvhfNSQWoNd49TW
+ cg79Pjmnzpsd/m5nfRE/W6PGT3yhhz+TWY7OMx11q73ZMOc1VLiG6jrXnKEOrZmJ1mKBP2SIafpqT7Oas5ijtXTPOs5snKfXFXOty/pq
+ 5NwsJiT8xhhjjDHGGGMIPEjFyA9QLGzf0b/4e3/ZWg+t+cAPa4jHCJ/6VVcb4tmW5VL80JTJ8lmnvC5q01iIzo/YVjV4D7xu6DyyXW1V
+ ig672jin6Zv7xTEQrt/09PPX2FRkL4jveW1kH+et4jh+yD3NL/rOaq0Sl363NJ9lVutSf5WiY62Qrjc7ZMgpspqzjnqwYeTaNbbYea7x
+ WT7P+XODPvhF1+vTPNiGnCI851yNXeV1XWJ0T1qj2LLf1eb7h3jN79LWwRxx2R7Zlq2FOlluy+nCPh05Louf6T2W5lmcxs/iEMP1Br3F
+ cD1dYzbOdNQ9khexqzWz+7CJaYI5arKNYzOdbbPvk+q8J/ZxLsuwhtjUt5oPvqLrta70kOzeGGOMMcYYY4wh+KEJwg9ReLCqI8VxTn84
+ PDXahgYI15LYwc71Mn3Ppj7ed5EffvLzuqdf/YO/GuP29FqHbDpHHI/X2GZ6loN52Uvc4+D7H//9iy/85vuD7xTbdAj71Ua+6f3SUfTN
+ 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
+ WlgD1HuFtbCfKshtOvxhg7At9DpSDa6nOnLYhzGrrzYeVzrmWT77eA6dc+h+97iqt3j42F/zmgxzqtVzyK97YBvWzfIgnJ/VYvvGL/U0
+ blgbccWHeYzGGGOMMcYYYwh+gOKHK7Wpnj0olnF7crT4ol4RNOr66TEI1YjGAZqLp9Odp3x+hcGluSHasEQTKeinSHEdJX56Hclap0bJ
+ yR92NEr4dCr2gXw0Y+rpZVpX46p0neKabdMMQ0yLG6+75Tbha+y5xc7XwO8WHfZUYodraOtu7hvWK7k4aVwbUHwtIW3tXr/lVF3HnnNe
+ D9cf++b68IcNOu836vD19vvU1lre3yLb3OLnvRc9rcExyXVdVbf5shPdwzpljPjNbyV8EIrF57z67dV50+/yW91ed8mhvBhn1x357BvW
+ DilxvG7/flLtmG/8JPgORUxdu9k396jV4trYW8Brb6655SzvOwvWKD6sMeyj+TCC0/097XPzmSGP6vM+h+/MpGaXkj/9zCDDmqd8NInj
+ PvSaiJ0J5W/WwLzH6pxiIDpn2zS2jagZI8fOdJ0jv0qxZ/vECF+I1mSbjjNda7FdYzmfZVaL7w/nruqpT3MhGsfxnGOMMcYYY4wxhogH
+ Jn2I4jl02GcPqU2ieTI0OhBbhE/dRTOi2of882nUgJsvd8kNyRpOaL4EbMf1Do2QqAO/rNVP0cla0Wip+0DNFsPX0hvI8PM1tX0gb/C3
+ tWLfoJ+GJRmuG/VC5BrqKchqP9VGXoz9XtK66TWUmvz5R0Oo5xWJnKHxFvZhTzQvtZC3kSHmndrsDWIfwXAStsRhP9HoDvrniRoUU/dM
+ 60zvb1K/XhN8VIMbdsO9VIl6GKmu3seTntRtfr7P9beCmsgvsvmthB1rIDbmRfge1Hph53oSz9+NIT7GmpP8VlGnxAz3U+uXMb3ulst7
+ rd9p8qHGcF+5dpN0/Zo/+82Mjd/88/rWcCJ2iCnjZk2pubnvOtZaY85w/Rh1rcgrkn5mkMidfWbhL3WzmpyPffXmcK153lPU7/VaDv58
+ rr9nsg/XoyPHZTEaxzV73iRHbZk9q4vYqMtrHF1Pa2Fc5WNeY8Te81qM6uHP8jiGYzPfbA3Y2ZbN2YYRubo3jcuEfZFvjDHGGGOMMYaI
+ B6XNA1sb9SEMsSH8sMV5xQeGxmfL4YYsNxXDjxOPAZ/GvVNuyYtmxKZ50fzcsKnNv34tY/O4NjQiT9YK+nolh9cKxsbdeLJ6c7ITQnsY
+ 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"/>
+ <rect v:rectContext="foreign" x="0" y="0.750001" width="679.259" height="222.739" class="st1"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i11.svg b/doc/guides/prog_guide/img/efd_i11.svg
new file mode 100644
index 0000000..2f245de
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i11.svg
@@ -0,0 +1,413 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i11.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="9.4379in" height="2.35729in"
+ viewBox="0 0 679.529 169.725" xml:space="preserve" color-interpolation-filters="sRGB" class="st2">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false"/>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="19" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape1-1" v:mID="1" v:groupContext="shape" transform="translate(0.375,-0.375)">
+ <title>Sheet.1</title>
+ <rect v:rectContext="foreign" x="0" y="0.750008" width="678.779" height="168.975" class="st1"/>
+ <image x="0" y="0.750008" width="678.779" height="168.975" preserveAspectRatio="none" xlink:href="data:image/png;base64,
+ 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"/>
+ <rect v:rectContext="foreign" x="0" y="0.750008" width="678.779" height="168.975" class="st1"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i12.svg b/doc/guides/prog_guide/img/efd_i12.svg
new file mode 100644
index 0000000..1b7518c
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i12.svg
@@ -0,0 +1,590 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i12.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="9.40456in" height="4.44413in"
+ viewBox="0 0 677.129 319.978" xml:space="preserve" color-interpolation-filters="sRGB" class="st2">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false"/>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="19" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape1-1" v:mID="1" v:groupContext="shape" transform="translate(0.375,-0.375)">
+ <title>Sheet.1</title>
+ <rect v:rectContext="foreign" x="0" y="0.750009" width="676.379" height="319.228" class="st1"/>
+ <image x="0" y="0.750009" width="676.379" height="319.228" preserveAspectRatio="none" xlink:href="data:image/png;base64,
+ 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
+ 8JdULZcuXSofbffiiy/WnndwcFA+0m6V5/VZz9SZrvc/+cps76vnarn0R98oH2021Pae9eu7ieMpdVav76rbOtR2NlECA+yAXDl7J+f3
+ cgXtnS9aO13QXjh6s0i+xJC76VoCx6MrF5WauXlVpV2QG2HatWSL5xO0rXfXEnbZ9W2bb5V4f6WFXt991ZRV92Fa6qcjaXPrsK5t6LNv
+ q5/n1ifeN4seX2cJvK79dqYBYEckZUof6yyB33777dkDDzxQK6PiPPbYY7Nbt26VU9elJVaVRWXWKs/rs56pM13vP/vj2QP/2b98qgCu
+ 8tiVi7Nbt39UTv2Jobb3rF/fTRxPqbN6fVfd1qG2cxElMMCGXTh8PFPOPrF3/urTe7mC9sLRC0VOF7T7R9eKP/MFg5xpupbA1XRBWk7F
+ BVnbr7O3FXyrFJhtRWFI0/L6rm/brQiqNO3XIfZVU4bch3FZmc5rndvQZ98uSlsxH29Tbn277tu2Enid++1MA8COSMqUPtZVAp+cnMwe
+ euihUyXU7du3Z0899dTdn587d27+80pTiVWlqcxa9XmrrmfqzNf7wx/PHvrP/9Ldwvfg+p3Robc/+nD21LW/fvfn517/d+c/rwy1vWf9
+ +p718lJn+fqusq1DbWdXSmCAFvtXH80WtPtXv1BcuJ8uaC9efb7483RBeyf5i3+ZXLoUaotKw0WjH6tUBV9uWasUmItGSjaVaX3XNy4T
+ c+vati1D7KumDLkP43kNvf/a0mffdkm17elz27Y3fbxtuW37Zp377UwDwI5IypQ+1lUCx7/GnhZzackV/3p7+tg999wz+/N//s/f/f+m
+ MmvV5626nqkzX+/oFhCnit6kII5vDTHU9p7163vWy0utOp9VnrfKtg61nV0pgYGtdfHokWxBe+Ho80VOF7QXvvlc8WeunA3JX5iLrJB4
+ hO8iTSMTu47MjEuwttHEXQrMtpGUVZpK4CHWt5pHbj26PK/Pspsy9D5c5/5ry6r7tks2WQKve7+dWQDYEUmZ0sc6SuCmoirWVnSFUY3V
+ r7unox/byqxln9d3PVNntt4tJW8lVxIPtb1n/fpu6nhKncXr22dbh9rOLpTAwFrsX3s4W9DuH50rLniXK2j3j06KP/MXziIjTFVMtWkr
+ vUK6zCOVllvLFphNhXScRSXmMtL1bSvq2krMIZbdlKH34Tr3X1tW3bdV4v3QZJMl8DKW2W9nFgB2RFKm9LGOEji+n2kYkfjGG2+Uj3wi
+ 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
+ AABsv729/z9i8B8owrMbcAAAAABJRU5ErkJggg=="/>
+ <rect v:rectContext="foreign" x="0" y="0.750009" width="676.379" height="319.228" class="st1"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i13.svg b/doc/guides/prog_guide/img/efd_i13.svg
new file mode 100644
index 0000000..6720042
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i13.svg
@@ -0,0 +1,1407 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i13.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="9.79126in" height="4.79083in"
+ viewBox="0 0 704.971 344.94" xml:space="preserve" color-interpolation-filters="sRGB" class="st2">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false"/>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="19" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape1-1" v:mID="1" v:groupContext="shape" transform="translate(0.375,-0.375)">
+ <title>Sheet.1</title>
+ <rect v:rectContext="foreign" x="0" y="0.749985" width="704.221" height="344.19" class="st1"/>
+ <image x="0" y="0.749985" width="704.221" height="344.19" preserveAspectRatio="none" xlink:href="data:image/png;base64,
+ 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"/>
+ <rect v:rectContext="foreign" x="0" y="0.749985" width="704.221" height="344.19" class="st1"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i2.svg b/doc/guides/prog_guide/img/efd_i2.svg
new file mode 100644
index 0000000..9728084
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i2.svg
@@ -0,0 +1,209 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i2.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="2.46396in" height="2.14393in"
+ viewBox="0 0 177.405 154.363" xml:space="preserve" color-interpolation-filters="sRGB" class="st2">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false"/>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="19" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape1-1" v:mID="1" v:groupContext="shape" transform="translate(0.375,-0.375)">
+ <title>Sheet.1</title>
+ <rect v:rectContext="foreign" x="0" y="0.749998" width="176.655" height="153.613" class="st1"/>
+ <image x="0" y="0.749998" width="176.655" height="153.613" preserveAspectRatio="none" xlink:href="data:image/png;base64,
+ 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
+ bd6by2Ty/wFz3+YoxW5tWwAAAABJRU5ErkJggg=="/>
+ <rect v:rectContext="foreign" x="0" y="0.749998" width="176.655" height="153.613" class="st1"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i3.svg b/doc/guides/prog_guide/img/efd_i3.svg
new file mode 100644
index 0000000..7e04ba6
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i3.svg
@@ -0,0 +1,420 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i3.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="6.06334in" height="4.95902in"
+ viewBox="0 0 436.561 357.05" xml:space="preserve" color-interpolation-filters="sRGB" class="st2">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false"/>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="19" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape1-1" v:mID="1" v:groupContext="shape" transform="translate(0.375,-0.375)">
+ <title>Sheet.1</title>
+ <rect v:rectContext="foreign" x="0" y="0.750013" width="435.811" height="356.3" class="st1"/>
+ <image x="0" y="0.750013" width="435.811" height="356.3" preserveAspectRatio="none" xlink:href="data:image/png;base64,
+ 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
+ VuDR+vaV0QU6ROM8+9Tzg//jp7+bXtpaXNte9we2A6SmC7UTClUqVd7FWk2vH/7LzVU0utHLoSTfS1CuF/LMjSpXKmOT99+8MfPK4Kx7
+ /DL1zy/5fnzR9++fP/fp715mPPvTp0+fePvejy76fva72Zcu+aSlXOI3rs/em5jO5fO6I8ev/Gbi2XMPpESGRj/x1n3YMfhegnK9kGeu
+ EyTcSrWWSmc/GZ/90huL0t9odtL0j1Ozz//hZyN/8sxZgNlnvnftj59+j/5Z6vpcF/r6yGwkmiCvM9a7VwV4BkM0PoFCKbo0HUSeeUjI
+ M9epVquVK+Rmws3g/vtDPikvO+06sf74qdOf/f51oNp/+NkwK/mCtoJ7/NsP5xbXthK5cq5IXpfc+PMe8gylK+SZt4Q8c50Iz6q1bJHc
+ HO9b2nj58gMpOztpMkRjnxc/94Obf/rM2T968p0vvLbwxTegZKlezU1+/vz8hG9pN1GMZctZ9iyB8zwrDA48POnnE010f4htxdpgnJeg
+ ZPmDj70evM8nLBbyzFtCnrlO5Hpj7QjGZ4lcZS+eH51+8I335sUE7bQXvwj/vwZe+H/+6S2gGivk/9eB5wo//vbC8KRv+yC7lyiS8Rl7
+ loD3dF2t84wAZkhzg0BjSQtqjWfxyInukoyE0X7wurKwqTaFPEMpQp65UZBxSxUyRDtMl7YjqY/HZr/6pms4QX4qA0M8i3/xiwllcMZL
+ 6tVc4C+9sTg0NrsWiu/ECtEM+dvWdHDGO1mUS3nWhUytjbaXedbR9yILeeYtIc/cKMi51dpRsVxLFyqRVGlj5/Da8KyUrJ02HaVJdtng
+ 7Oxt38L6TjBWiKSKqXylyP42G+9jjdzLs4HILp+wRpZkeXcJeYZShDxzqchVR/pK+2SuvJ8s+pa3fvvhnJSvHTYfqKl2F8xevvxgenF9
+ O1oIJ0vsD2dXjP+KjXU8o8MUHoOQZEnOVcoJohjPkid5oc5FRdKsughpX4vAehJv0pQQjNICn+QlrQXfbBV11ZuSN1YUa4pPaFCkv5RB
+ SORKLCtcG/QJjXDpbKnx1jUT8sxbQp65VzXlrYmQjveSxXHf0vPn3fNDmkov9sFdv5x94735kakHfvqzWZy/gov0J+/ZBrXHs4bViRlT
+ Tdb1/K7zMxjLrbyQ1NQdh0GK12Z2I57pNUXLFVZBOVu2HhWVMCnXF5J+S9Hypo75zc9gK/SX0oS061vT2zRGWR0+abe0ydY1E/LMW0Ke
+ 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
+ YXt5AAAAAElFTkSuQmCC"/>
+ <rect v:rectContext="foreign" x="0" y="0.750013" width="435.811" height="356.3" class="st1"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i4.svg b/doc/guides/prog_guide/img/efd_i4.svg
new file mode 100644
index 0000000..f69c899
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i4.svg
@@ -0,0 +1,364 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i4.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="3.79074in" height="2.71065in"
+ viewBox="0 0 272.934 195.167" xml:space="preserve" color-interpolation-filters="sRGB" class="st2">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false"/>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="19" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape1-1" v:mID="1" v:groupContext="shape" transform="translate(0.375,-0.375)">
+ <title>Sheet.1</title>
+ <rect v:rectContext="foreign" x="0" y="0.749993" width="272.184" height="194.417" class="st1"/>
+ <image x="0" y="0.749993" width="272.184" height="194.417" preserveAspectRatio="none" xlink:href="data:image/png;base64,
+ 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
+ EJSrHObmAtapLHkcvNHNjRxUDn6BnaCZh6hZqsv42XIxPHXe1y6hSTtrPPfzksT1nRtu9twGNY7+0fldz2vpl+uSqgxN2R2mU8747JHz
+ adm6DY1et++ocJmUr3ig5qe61i2UueahgjU3MzhxzUmDlm6iY9u2O1Lh0ulKrrxecdjq7DBImcJCDadb7/tr0nVz6nf/eNJzn0JRpjI0
+ 2aODGMUdhDtcJuUrHqh5qT51C2Wueahgzc30g9RsNPCj7I2NrSuvv9kzrjfGJY/8pAoHNawyXdPH3xnkuV8hKJsyNGlXmE52dRAj1+47
+ Klwm5SseqPmovnULZa55qGDNzTROXHPQ5L06ffO6nxypaLnoP1yJ3fG97uqYvs0dkzsM1PByX4dzv3oJjd9R4xm20JRNGZrg0UEM5w7C
+ HQ6C8q361i2U3eahgjU3UzlxzUH9Pb6F/ObRJ7MKJ52OO1wnj47JHQZqePn5OmST9vEpOgh3OAjKt+pbt1B2m4cK1txMPkDNQn2XbKJj
+ HKblC/wtZOz2Gs+wr76fXKmdYf/2uvfkOqd+zabJHafo/Z3Jld2WVPqhXOm99hG50/4tbmTc60rKyqjz5AVJ+03Yo9MVPzp6tEr2d4ar
+ qwavI/r2g0RfvzeuX3eKrx+3h+jqRz9dL3r0vfT7eoVJp2yuQ6rzbOzr4FYuZcidVmcaf+GaD5Hu2OnicSpVmUm3Ty7yynu7frmP7T5e
+ ffK/rtc823zLFM693T5nd/q99q2P6nLe9albTmXKE6+ylipdubRl7rDp6kU2YVMdW5TPa+VXFay5mcSJaw7q49GI/OudQZ5h0+mvWVT8
+ X3HH5NxnbIoGxi1p8MZwg+fcV+RO+6U/+NFR8UklG8KVzN4nm3TW5fyd6rY42Zz8nc2Je51Tsn38XqI7X/XeLpIwXsdyK5vzk4bPuU9T
+ XAcv5VKG3GmW+F+fNP+osuyU+7xTxeNOZzbpEnnFn40yxf/oW32TOhF3GuuS//W95tnkWzbh3NvleDf89o9Hlr3kbkdyUX3Ouy51y0uZ
+ 8sR9HDEWb7CxcMbhFc5L7rbMvU+qts5dptznlc2xRXWtE8WggjU3EzhxzUGj01R2qXSDudJ57efWw1kU9ju5UbLDpzuul7zS0svDmLnl
+ PGZH18hTOv2TK73zWLno2YnJxuSP7yQvu/WNDkQ/ecp7m60r7icattX7eE5lcx2+yQ2OHb4prkMq5VKGck23Lfd1dcfjPr9cyowo13KT
+ zTm75U5jrvmfj2ueKd+yDVfX65hNeXKrvueda91KpXR54nUtvcpUXdsyd/yp0us+V2ccDV0nikUFa27G7admowezbGD/7+9Peu7v1Euu
+ gi+VxyvczzocfevgtUkLjmzv6VHJz+FvUyO21aQN447H1sgdYWrvaFAyHU8anIFr9iXFka0e6ONtToZsiW/v73HrKVMYMUDdPko+TjoV
+ 6nXIRtmk3X09vcJ6hXHH5Q7jvu7O/PE6J3fdcedNOnmlz7n/mN06Xe4x7O9OY675n49rninfsg3nlQeiTPlQl/qZj/MWZVu3UilVnnjl
+ xT+6D8q4f6bzcOaVOy+98jFTmIasE8WkgjU37++jZqWbXRU/naSwDt9a4xnPS2OTK/4VP745YxipPANW7zsq3IjtR1f2R7sNOrL93cVH
+ N0ZiwJxx2HqwU3KFc8Zjy52uVHGl0+jdRHe4bi+JaXlvc/3CiLnpuuTT7ZlUqNchG2WTdq80ZXOO7rLrjseZB6N36XSZo5H3Kvd2mFR5
+ l07uMpkufmc497Fyyf98XfN0+eZUpnBex8k2H3IpY/k6b1E25TOdUuWJuw1uqLYs0/7u8uQ8v4auE8WkgjU3YzhxzU0vuCpEOolj7zhx
+ QcY4pGK4w7gr8R1c8dxhbP3FVRGd8b3jqoSp0jTSVSGl0vXjSucON8zV6HilPZOG7yL6gWvS8HNTM4f56+D0YcT8DGLz4wyTToV4HbJV
+ Nml3XyvR37mRdodzp8197d3xOLe7y40tadCHcoNux1FXufPfK/0id364zyGX/M/XNU+Xb05lCpftdRS58yGX65Cv8xZlUz7TyStP/tVr
+ eFZx5qMtc5cX97Hc5++8Hg1dJ4pJBWtuRnPimquGejQ4XpICPYQLtHPf5z0qvnP7CK4c33BUDrshdoZxqrurIjqP6d4mFb0vV3R3HNme
+ j1te55dJvdcm304SUzKQTUl9w9z4FDdYO5PDpFMhXodslSntIvc1TZV+dzh32jJtf8DV0HvJK32ZlEv+Z0pjtvmfz2ueKU226pv/TqVL
+ Tzrl87xF2ZTPdHKfs5dSxZnNvl5ynoM7P5x5nm6brYaqE8WmgjU3I/eyS4Us/fm11IX5b+zqnWGfG5Nc8S/nQu7cPmQbV87PJDdmfVbt
+ SwrjVLrwXRcd3ah7xeWOI1tlSpuX3lj4qSER/d9rmcPcwMZlyI70YbziSadCvA7ZKlPaRdmmP1O4TNuH7eTG/tpPG/t0+jx3IIO31BzZ
+ N51yyf9MYetaD9IdU5QufLZxZQqXS5pyTb+tXPfLFD6b8plO7vi9VFJaRi+xAavLvl7KdA52O+4uS17n1lB1othUsOZmOCcOStZgrlgn
+ uSrWrY88mRTmWVeluYwrh3O7O44WXOl6caVzhnEqXfi3XRUxVVxe6c5GmdLmpSfGJ5uSBwcdHaZD78xhsoknnQrxOmSrTGkXZZv+TOGy
+ jef+NAbfqWzPPZf8zxS2rvUgU1rThc82rkzhcklTrum3let+mcJnUz7TyR1/KmVT7rNVpny3j+Uu5w+z6bH3cSvfdaLYVLDmZhgnrtg1
+ yFXA5dvCC/xtwSusLa+Kncv299j1X+pw/ZmO+RY33G0cDbd8ExjA3wQybXPKfZ5S2XpyZXOHy4fcxuXpqcnb39tFdNsr6cOIMsWTSYV4
+ HbJVprSLsr2mmcLVpWz85E/J8zfckg7Baz+ncsn/TGnMNv/zec3daUoVlzuOTPmfLk11LWf5PG9RNuUzndznLJI0/e/9uUnpFLnLUl3K
+ ayo5y7HE0/3jnUnHzyXufNSJYlPBmpshe6joNWiHTl93VaZvcEX1Cmurg8utu8M/PTq54nvFd6OrItzyyJNHhbGV7nhdFnIjdPynjdDZ
+ F15C/TbXJO0vcp+nNCTPTVhwVLj6auAOou///VND8o0/EXVa3DBhMqkQr0O2yibtA7Ye3cj3WLkv53DZxpNK7v1F6fLRKXf+P9h1kGc4
+ d36405hL/ufrmrvPO1WdcqctU/6Lss0Hr3KRSvk6b1F90iFKd87uuN3XMp9tmftYt/3jmaRrlet52apPnSgmFay5GcyJaw76o6sii6RC
+ 9eEK5Q57g6uBEP2FK6UzzFMeFd+53SuMNHjducFzh+vnUUmcx3vDo1H3SrfIfZ6/5MrmFa4+6rqa6Ft/+dSUXH4/Uc+NDRMmkwr1OmSj
+ bNLuTlO2aXeHS7c92/xxh8u2bLnLpFe+DfD4AuJORy75n69r7pUud1sgcrcZmfJflG0+5FKH83XeomzKZzqlK3PZ5Gu+2jKvc3XK63o2
+ dJ0oJhWsuRm0m11yM1DfLekLeDpJwe62Yl9SfO5CLQ1V7001SWH6b9fpElcFlm8gz45fcCTM6wuSG2zRpdfdnBSPO4zXsVKFFf3ib08m
+ hbnhj/GG2H2cbNVxwaeGRHTbqw0XJpPc18HrnJriOmSjbMqQu9x6lcVswqXb7pU/7rR41Z8H3h50ZHs6ee3rjN/r+CL3OeSS//m65iK7
+ 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"/>
+ <rect v:rectContext="foreign" x="0" y="0.749993" width="272.184" height="194.417" class="st1"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i5.svg b/doc/guides/prog_guide/img/efd_i5.svg
new file mode 100644
index 0000000..d627da0
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i5.svg
@@ -0,0 +1,578 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i5.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="5.26421in" height="3.07735in"
+ viewBox="0 0 379.023 221.569" xml:space="preserve" color-interpolation-filters="sRGB" class="st2">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false"/>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="19" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape1-1" v:mID="1" v:groupContext="shape" transform="translate(0.375,-0.375)">
+ <title>Sheet.1</title>
+ <rect v:rectContext="foreign" x="0" y="0.749997" width="378.273" height="220.819" class="st1"/>
+ <image x="0" y="0.749997" width="378.273" height="220.819" preserveAspectRatio="none" xlink:href="data:image/png;base64,
+ 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
+ Dl5S0XC+K6klGqVxb9dz30177mwNXn6RSWX+pvZ+iJp5ZzeTOTrtNtRLOrVaXL/SXjq97iX57ew/ymYTdtdkoo90QjD0zM6y9aN7yqmv
+ XCzz0h9j4fZcL1U/Qo+3eXAaQqoWtXO8d0U0mHlfE4HF+N113XmpTOYi9ZkrXNf7SH8uxN8n1U2W7716kCYRu4gzdWfp+rz+nNBbtn9s
+ b7n53Tul0W9hBCN88/S43wpOQUhNwg5eUvFwviupFVLiXoAKOYX3R3iunzZrMDRVk4m/qk0PTbvePP25UqtxGbpiuHz55Z9oMLSzdJrS
+ V7qqnGd7yy6j/kcGf/igbpHNunX7ORoMfTM4BSFVjdr6pmrzE1Ufor0Q8R+MJg2em/ZS3jptRz5ZJ2tWX//pndLzlT3FebmPdJn4Xek8
+ 6WviPL2NfHvUz+TBxcNlrazQxiZ4mprrZTLpzC+DUxBSk7CDl1QEaoec70pqHrXZvdKet0Qr7XmooDOyDj80wZDVXoOXUfP/YFFqYfqM
+ 9y+WjV7ZTe1+Z+k24XviTNpdnCd6yf8+eZCMWv60rJdV0WBIo6HMT4NTEFL1aFtxq/rKW16D+4pGMsiqJe01fK5/W/qJ+sap7/9Hur2w
+ U3at0Us7ijNe25GRu8gvpv9R5qjrNXp16YwXjE6kvbUaDP1R971W/9krOAUhNQk7eEli0Qqa810JieClXUzJmOCl3AWSSSMjENdLfap/
+ W/t64zz51cw/izNVE+jXtpBO03bWRFqDocf6yR/eOFMWyWJJeevSGXc9YiEEQ6vcxszRuu/1qzOZrYNTEFK1aL2/nZf64jPPc2eucb3X
+ Mxig8PPytExKT5d9Zh4jzitfVd/pJd2f7SXOxO3EebKnnPDqGbI4tWyF+so03fcj339cb4H6z5H1nvcQ/p3KuGcEpyGkalFTZwcvqTzU
+ yDjflZAATQSO8Fx3XZ3nPlef8hb5Ju3P2Fgv96waITu9eoAGQ7tK5yk9pftz22ow1Eu6PLGdXPjOZbLaW7tQbX+a56b9xdoaDM1NN2R+
+ l/K8R/Fv9anTg9MQUrVo3b+j533hZ9RYOurhPxmRicufka9P+7G2H1to0LOtdJn0bXGe3U16jO4t/d+9QN1sFdoOJBS3qR5X3eW6mWNd
+ zxuNYwFX5PfBaQipOtTE2cFLKhc1Ms53JSQgk6k71TdixZ+WqsGQJ2kZNO826fnC1zWZ2Ew6vbCTBkP7iDOur/R6Yhe5Y95Af0Mv7X2s
+ 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
+ 1X8w7Q8J9gPavjyifqRty9FP/j3jeu5c9YkX9Rgv4Vij3n1ANrpxO91G25+H9OdDO0mXITtrMtFXNr6+jwx843q0QfC/lZrAjHC9hika
+ DY0IikZqm9CeQyGOKkShhCI6OjdbFfWXqPJ1doFm7VjKdddqnc8OXpIo4sx99eF8V5JgzMDGpnzJQinzW6PbNtOP99vvc7XtD8KpG/0n
+ XC3OmZtLZ7XpTvCDh7SSf0B/aqLsPLCNfGPIL+Wj+nl4Cd0crY9HIRNZuPZD+fZd31Of2Uq3U9t/eAfpcp8mFAiGBvWWHw75qXyangsf
+ WKfnQs/sf9NeQ0r9YR8tAyFtgRnQ2JSv/cjrX5lMdg0R8DKePw0jxH9Wvou/ZOStFe9J73P3lM4D+2pyrYHRvVtJ53t7S5fBGO3bWTa/
+ bQ+Z8YU/YofHLq9Y0LBYvnHnvuIM3kz9R9uQYLtOmPZ0y5byy3t/m1mf9ueUP6jt0KX68xqNgz5RHzo8KBqpbdBxGxUShkIgaTD3CR+C
+ EE0ooGjiYPqVLXEx2zZ/f3bwkqRhndeaDxiaVr6c70qSTL6Av1APU6nzW7sedNCvc47fvXt3wcvugO8WWGGRcWVp42fyrUv/T5zL+0h3
+ 9Kreu6V0und76XLPjhrcqH3ftINc9/bNWiGrRae9xXCIvz1znjgDN1bb12TiXm0M7tUE3f+5tWx+bV8Z+cEoBEJLXDeD+bN4PvjDGgzh
+ 2eOstElbE50eG6rYdI28/gVXMVFfWK4B0qQGr85vH0LOHXmZOGerDwzeQbrevbl0vau39LjtS+Lco35021ZyxnN/D7YUuWT85eLcoO3G
+ /VtIp7t7Sdc7d9btNakYvLVscsO2MvzDUehPfdVtkOfX+j2yGBjxPtTm5YKgaISUCzOhMBPvQrNEoh2+UFNCzg5ekjSs81rz4Rse57sS
+ kicQ8lYgEKr31vsvlgu5c/pQ6fTXbWWLu3aUbvdsLl3u3lZ6DOonXe7sJ87d28h3HvyxrMqup5MX506RTa7ZXZz7ttLPtpQu2OfOvtJp
+ sPrPzRvLEY//Seq8ujnqdzMaXHdKWtMXDcAWqz++oRX6l4PiEVIR4HGVbsY9IwO5/s+T1I639j/z36zrzVL79qdNvLdsrvQ9Yy/pdnVv
+ 6Xa3Bkp3bKe+oUnDENXdO8iX7/iRfNz4qbz+2cvS898aPN2kfnO3Ju53bantUU/peq8GQ9duLyfef7LmDY2S9tJztO0ap367XNuiq4PO
+ MgR3hJQTcxQibkJhJhM5MRp8IrBZdvCSDifvvNZ8cL4rIVksgRCelLEdPtNK/ma10w9Ufs/RisY18v2L9pfOF/eS7vf00UBoe+l+57aa
+ JGyjicMOstHA3eXhD0dJg9TJT2/9qTiXbi2d79Ek/K6tpevdW8hG9+q2d/WWXS/fS174dDISFzz4bxymVrmuvKbNxwVanov0VJ38whFS
+ Rai9+wkFOO2Bc8X5+6bS7Y6dpbP6UWcNipx7kHz3ka5X9JNrXh0kh99xhLY/3WXTm76s2+3ij+x1Hry5OLdtLdtd/XWZ/umL2lSlEUZp
+ e+b70iL1XX+qh/r0hcFpCSknhaaW26YEmtOcmk0nhL2yg5ckhbzzWvPB+a6ExEft1H8CBxg46U5xTt1Mut6mFfsdO0iXOzSZuHtLtXm1
+ 8Wv6yNGj/ybXTL5OnL92lU1u2EV63LabdLpHP7tXE/O7tFIfsI3846l/axhUr0l7StIp39mQXFytyfkkVZ3+s29wakIqHq3f/0cT9Yv9
+ CEfbGzBp4Yuy2WnaRqjPOHdqgHR7L3Hu2FI6DeopG93UT3a8Yk/pesom0uW6PrLFwF2l2w29xblLE477thXn8m3kuMf/pu1W0I55fuQE
+ HxrreY3jsr+nHgpOT0g5yTe9PN/oRXRqoXX2CDt4SZIodd4457sSEgNplL01GDrdc9Nan2fdYm79p7Lrud8U5z/bSKc71QcGaSWN3tVB
+ W0i3W/vI9tftKVucrkHPxVvKZjfvJptcp/Z/29b+Y/6cW7aTPlfvKa+twjKJAE0nPNdbq35wQ9pL+S/D03PyKU+kalD7nuXbOiIjxU2n
+ 61d7axsOu+kP4vxD/eL2XcS5VZPum7eUzrf1lK639pYuSDSu2EY637KTbH6dBj83IPHAFI3tZbsrvinTPnvZP5b65kf+L4qeZwlCL/93
+ Lz0kOD0h5Sbf+r5QYcdunAcgaH3PDl5SwXC+KyHFgZ3CB0LcVFoz4HTdv0ZdIs5fttREoZ8GQjto5byVdLp1K+lyq/rGjTuL899tNDjq
+ Iz1u6KvB0E7S6TZNMO7RBuCKHeTsMZjRlMHCuwUaAK3Acf1ne3ipz9F8APWHbwdFIKTiQcCiesi39bS7Rv1qvv57wciZo6XTCeo7N/bT
+ JELbjRt7SuebNFG/UduWW9Sv7thRf/aRbtfvJF0H6r/Vv5zLeslpT16IDrBleozb1Ydm6M8pGTdznv58wncgBZ8FpyekLbAlC2anbqyE
+ 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
+ ZlGd9Ti3jm3e0BXbBg3HEx8ua7bdc0uaV87/uWdY0+ePzGze0B2vDUT0GKHMxih6nFBmufIdK59s5Y1z/bb7OWlZWn5wwEF+o3uHNrrR
+ z8zrjt6/cL84xy7lvrf0Ogopjq2YZYlbZnO7cl2TaUe2Y4TfT76yluO6zWOU8l1Gbb9c96WQOuq7yVdGs64zy1OuusI8zg/2/02Ob0K2
+ Y8dRHBsql42UUs8WUjnua0vrRai1vtsevlIuMaEgpLZIVEIBmRVhIdkqdNsxbBWu2aAXapwKNQJmA5KvTGaQHbdhtZW9kIo1zKGKNXzF
+ ZJ4nur95raFaEwBCbXEdcWyl0LWWsl05rsm8t/nsrZjKcd3l8qH2+K7b47uBzHtiOwZk3v9C33G+6zTLbPsO8/liqFLrl6iSWM8WUnvU
+ wYXsrBy+Wy47bQ8xoSCktsibUDyvFUJH6llLxWkTgtTxGqRG973FaOj+VxuE6OcTLRX77VqxR7eJaphROUfPaX6GivtxrbjNY8S9HlO2
+ 6ysk8zz5rs3cLl+5bTKDBNv+5xnBgU3m9xJVe1wHVMxWoLjnKLZdOa6ptdcbqrXXXU4fKsd9KaZi+5ajDKXck0LHMT+Lq3x1hXnvQ5Vy
+ /2xKYj1bSOW+r6aK1YuFvvO4Mo/RFr5SLjGhIKS2yJtQTNYKIUkqFKBefM+wnG1vtjR00c+f0cp2O6OyHa2VbXSbqAptP9TS0NmOZR4j
+ roqVzVTca4u73QQjKMgnc/+4+0G7Bw129Lzlvo58KmYrULnKYn7eNQgGosewbRc9TmuvN1Rrr7vUcpRyrJbcl2Iqtm85ylBK+Uo5TlwV
+ Op+t/jTrzVKVxHq2kMp5X1tSL5Z6P2yKe4xynKu1YkJBSG2RN6GYtEISqfGLmzcKx513Sc42N40xGrpfHZLzuXkMVLajPliWs01UhbZ/
+ +I3mDZ3tWLZyx1GxspmKe23Ftnvus7TsGzMhgPKd5583Fx+pgMz9y3UdxVTMVqBylaUcx2nt9YZq7XWXWo5yHKs1115s33KUoZTylXKc
+ uMp3vny+vPte+8i4hXXNto+rJNazhVSO+9qaerHU+2FT3GOU41ytFRMKQmqLDk8ozIoPPYODnpth3TZUsYas2Odmo1DsnGZjFm2IC30W
+ VXtV8OVqcPIlAuH1teR6fm+ZEhDVRXcPa9q2XNdRTC0JrPPZS7GgpxzXFLcsxdTahKKcPlSO+1JMxfYtRxlKuSeFjtOa67SpUFJvdsaU
+ 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
+ EEJIK3Cc/w89P4M7NoQMtAAAAABJRU5ErkJggg=="/>
+ <rect v:rectContext="foreign" x="0" y="0.749997" width="378.273" height="220.819" class="st1"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i6.svg b/doc/guides/prog_guide/img/efd_i6.svg
new file mode 100644
index 0000000..97c9adf
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i6.svg
@@ -0,0 +1,413 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i6.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="6.5721in" height="3.93701in"
+ viewBox="0 0 473.191 283.465" xml:space="preserve" color-interpolation-filters="sRGB" class="st2">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false"/>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="19" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape1-1" v:mID="1" v:groupContext="shape" transform="translate(0.375,-0.375)">
+ <title>Sheet.1</title>
+ <rect v:rectContext="foreign" x="0" y="0.749992" width="472.441" height="282.715" class="st1"/>
+ <image x="0" y="0.749992" width="472.441" height="282.715" preserveAspectRatio="none" xlink:href="data:image/png;base64,
+ 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
+ Dg//f9NW31MZe1iBAAAAAElFTkSuQmCC"/>
+ <rect v:rectContext="foreign" x="0" y="0.749992" width="472.441" height="282.715" class="st1"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i8.svg b/doc/guides/prog_guide/img/efd_i8.svg
new file mode 100644
index 0000000..c5f736e
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i8.svg
@@ -0,0 +1,776 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i8.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="9.59791in" height="4.1041in"
+ viewBox="0 0 691.05 295.496" xml:space="preserve" color-interpolation-filters="sRGB" class="st2">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false"/>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="19" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape1-1" v:mID="1" v:groupContext="shape" transform="translate(0.375,-0.375)">
+ <title>Sheet.1</title>
+ <rect v:rectContext="foreign" x="0" y="0.749991" width="690.3" height="294.746" class="st1"/>
+ <image x="0" y="0.749991" width="690.3" height="294.746" preserveAspectRatio="none" xlink:href="data:image/png;base64,
+ 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
+ Zf0wDKpdKUo1X/Rz7H5Tv5YIPa6ydqVNtSdFKeWJ3X/s8aRKj6uszcOg2pWiVPIldJxDo8dU1t50qbakKKXrSZkh1b/0uMraBS/VUSkK
+ JaBNlJgbb3cfvnD3EfMJTZ03bhSeE6LalaLU88VyL3Kpv+HT4yprU5pUO1I0lPzgDV0CVLtSlGK+uK9nb7Ds8aZ+LRF6XGXtSptqT4pS
+ yhP777r4MDR6XGVtHgbVrhSlki8x2w2BHlNZP6RLtSVFKV1PQpYpVPeZHldZu+ClOipFvgS0yRdzMXC31893H7NvwkIJ76pzE0PhOSGq
+ XSlKPV8se5GLOea+0+Mqa1eaVDtSNIT8GFJuCD2usrYNg2pXilLMF/uey72xWuYa1Fd6XGXtSptqT4pSyhObH/Lv9jluDKFIIPS4yto2
+ DKpdKUolX3SxzRbo5N9sxBQB+06Pqaxd6VJtSVEq+VHG9z4sZXpcZW2Dl+qoFLkJ4UZM8gl74QgNfvupjPt42Sc7vueXofCcENWuFKWe
+ L8J9cxd7keszPa6ydqVJtSNFKeaHrzjA9SQBql0pSi1fQjN36t409ZkeV1m70qbak6KU8sTek5TFEK4velxl7RoG1a4UpZIv7v37D//w
+ Dz8+Tl+kfG3RYyprT7pUW1KU+n28e/xDeM8l9LjK2gYv1VEpCiWgRMyArrrhcPdvP+m3SeZLcnshsp8UVaHwnBDVrhQNJV/qbNN3elxl
+ bUuTakeKUswPX+HZxhByRI+rrF3DoNqVopTyxW7ne72q40iJHldZu9Km2pOilPLEvpaviOBea1LPFT2usjYNg2pXilLJF/d+xPe4e83x
+ 7TcVekxl7UmXakuKUrqe+FQVvlOkx1XWPnipjkqRmyA2idw3RzZpfMqS1xfuvnyJ60vWKjZhKTwnQLUrRanmi37t0AUzRXpcZe1Lk2pH
+ ilLNDy32mFOgx1XWpmFQ7UpRSvlitykrtg3huqLHVdautKn2pCilPKli95n6PYseV1mbhkG1K0Wp5ItbeA5dO8o+8EyFHlNZW9Kl2pKi
+ VPLDx31+bKE6BXpcZe2Dl+qoFPkS0P23sk8ZmySg/cqm+4mN/bc6b8goPCdEtStFKeaLfZ7sM+VZAyF6XGXtTJNqR4pSzI8Q+yYx9Td3
+ elxlbRoG1a4UpZIvVTNsfDdUqdLjKmtX2lR7UpRKnsQYymw1Pa6yNg2DaleKUskXe/9elQupvxfTYyprS7pUW1KUSn742OeXHWOK9LjK
+ +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
+ 9Kl2pYp86Rc9rsxoS49qR6rIj37R48qMtvSpdqWKfOkXPa7MaEuXak+qyJN+0ePKjLb0qXalinzpDz2mzEhLk2pLqsiPftHjyow2LFAd
+ lSoSsF/0uDKjLX2qXakiX/pFjysz2tKj2pEq8qNf9Lgyoy19ql2pIl/6RY8rM9rSpdqTKvKkX/S4MqMtfapdqSJf+kOPKTPS0qTakiry
+ o1/0uDKjDQtUR6WKBOwXPa7MaEufaleqyJd+0ePKjLb0qHakivzoFz2uzGhLn2pXqsiXftHjyoy2dKn2pIo86Rc9rsxoS59qV6rIl/7Q
+ Y8qMtDSptqSK/OgXPa7MaMMC1VGpIgH7RY8rM9rSp9qVKvKlX/S4MqMtPaodqSI/+kWPKzPa0qfalSrypV/0uDKjLV2qPakiT/pFjysz
+ 2tKn2pUq8qU/9JgyIy1Nqi2pIj/6RY8rM9qwQHVUqvQAt/8/a+Hj2NraMs9e9OGHH5595jOfmXu+xKc//emzDz74wDxr0euvvx61TdPj
+ S40eV1n7hkG1K1XkS7/ocZW1L02qHakiP/pFj6usfcOg2pUq8qVf9LjK2pc21Z5UkSf9osdV1r5hUO1KFfnSH3pMZW1Ll2pLqsiPftHj
+ KmsfvFRHpcod4H/n7/yduYHthi85fEnkxsc//vGzr33ta+bZBV8CuWETzWpyfCnS4ypr2zCodqWKfOkXPa6ytqVJtSNV5Ee/6HGVtW0Y
+ VLtSRb70ix5XWdvSptqTKvKkX/S4yto2DKpdqSJf+kOPqaxd6VJtSRX50S96XGVtg5fqqFTphNBJc/v27cePTadT86+F999//+wTn/jE
+ wr+7+9SfynzhC1/wvo6Qx/TzmxxfivS4yto1DKpdqSJf+kWPq6xdaVLtSBX50S96XGXtGgbVrlSRL/2ix1XWrrSp9qSKPOkXPa6ydg2D
+ aleqyJf+0GMqa1O6VFtSRX70ix5XWbvgpToqVe4AD316Yj/h0clRxian3qfdV2yyrOr4+kqPq6xdw6DalSrypV/0uMralSbVjlSRH/2i
+ x1XWrmFQ7UoV+dIvelxl7Uqbak+qyJN+0eMqa9cwqHalinzpDz2msjalS7UlVeRHv+hxlbULXqqjUuUO8Js3b5p/nWc/rQklgI9NQP1J
+ TdknPz6rOr6+0uMqa9cwqHalinzpFz2usnalSbUjVeRHv+hxlbVrGFS7UkW+9IseV1m70qbakyrypF/0uMraNQyqXakiX/pDj6msTelS
+ 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
+ 91lVr932GK2THz5N75Xq0OM9e82OqQNq0+uvv/64Y33RZmc3fS3foIwZhMu8bkwCSMT2j3sMVccc+9oSZa8fSjod7j7qvPaq/kjr8Z69
+ Vj1q+zaRL/7X9V3gdHzsYx87Ozw8NFv49WnMdpGzy9DjPXuNcur5bSI//K8bOz6abCsR0791xrXPqvJijNcTjfyp10Z9DG3nT51tJSg8
+ P4k2kRf+123yvqvOWOvj+6ZVHX8sPd6z14mntm0TueJ/3djxEts/qxrnMWN11fdJbY4RS4/57HXKqee3iRyp10Z9DL5t2xjjbY3RZfLD
+ p06ON6XHe/aaHVMH1Bb35OgT4Ha4fVPcRJPXOj09PfvkJz/5+DluVA2GJq9rH9fbuckRU0zzHX9Zn7r79yVGzOu7bQsluTxHP1b12sLd
+ d0z769LjPXudetT2bSFfwq9rx41vrLk3R2Wv7+6/6zHbRc4uS4/37DXKqee3hfwof137uN6uamy0Obbqjmut7bwQdXOzLj3es9epR22/
+ KuRP/Ta62/j2KZrkT8z49Vn3dm3S4z07ljhqu7aQF+HXteNlmfddMWPNfe2Yv81tXx/cx5fJzbrHX4ce79lrxFPbtoVcKX9d+7jermyc
+ +XT5Psh9Xpv3SXX7oC495rPXKaee3xZypH4b3W18+xRtjPE2xqj7GnXyQ6ub403p8Z69XsfUAbWhatq8sCew6gRVafJa7kCTsAlnE6ss
+ AVfZRndQhvZt2WOVGSr29WK/Ahfad9nrN/l6QMxrC/f1q/4I1qXHe/Ya9ajt20C+FJZtox2Toe36NmbXnbNN6PGe7b+cen4byI/Csm0s
+ Gxttjq2641pbRV6IsV1PNPKnUKeN9pohNyDf//3fH9xvk/yJHb/aurdrkx7v2bHEUdu1gbwoLNvGsvddsWOtzt/mVVwfmubmqq4terxn
+ +4+ntm0DuVJYto2hcebT1fugVd8n1emDuvSYz16jnHp+G8iRQp02tvk+S5SN8aZjtEl+aHVzvCk93rPX65g6oDbYTi0beO4AtpV+98T6
+ qv++Qb/sa1ny2Pd93/fNbRuTgE1ft0xsktnn2U9n7OuVHVPMvkPPcf+9rG9CYtslqsbCsvR4z/Zfj9q+DeRLoex1y9jtfPvu25i1+1tX
+ zjalx3u2/3Lq+W0gPwplr1umbGy0Nbbsc+qMa9cy28cclzWm64lG/hTKXtfl3nzI8+yNgW+MNcmfOuPXte7t2qTHe3YscdR2bSAvCrF5
+ odntfPuuM9b6+L6p7eOvS4/3bN/x1LZtIFcKZa9bJnY8rWqcW6Hz4e5jVfdJdY6zLj3ms9cop57fBnKkUPa6rrbfZ1kxY7zuGHX/fZn8
+ cNl9LXuvtAw93rN2dEwdUFPuCYr9lM59Xmhg+57f9LVCqhJwVa9rxX4yaAesPU739eomlkv/QbBi/6CExLy25R5Dnb6rosd7tv961PZN
+ NR1L5Ev56/dtzK47Z5vS4z3bfzn1/Kaajh3yo/x64u6zydhaZly7VpUX1liuJ1rsmAk9b2z543tO0xuiUP7EbOuz7u3apMd7dixx1HZN
+ +c6zT+h5XFfKX9/dZ9VYi/nbvO73TW0ff116vGf7jqe2bSp2fISeR67Mj5Gy8dTV+yD7uvLvq7pPCuVaG/SYz16jnHp+U03Hz9hyxPec
+ tgrPoTEes4/QGG2aH65lcrwpPd6z1+qYOqCmYv/ACjvQ3IEeGrT2Uww3MZu+VkhVAq7qdS3bVvuJSIjdt5sIdtvQ61UNcvdxvY/QH8dY
+ dRNsmb6rosd7tv961PZNkS/z6p7zqlzp25i1j68rZ5vS4z17jXLq+U2RH/Pq5ocoy5G2xtYy49q1irzQlum7Knq8Z/uvR23fNvJnXtXr
+ +q4Xdpuq/Kh6XL9m3fFrrXu7Nunxnh1LHLVdU+TFvDqvK8quKaLuWKt6ffv4ut43tX38denxnu07ntq2KXJl3jLnuipfrFWMc813/L7r
+ Xh1Ncq0Nesxnr1NOPb8pcmRe1eu2/T5Lq+rjqtfQx900P1z22Ja9V1qGHu/Za3VMHVBT7uB0O9YnNND1pwt2n/qPdhuv5VMnAdt8XeG2
+ vSzB7DHoRAj1leUmV1m4f/ysum3R6v7xaPp6Pnq8Z/uvR23fVBtjaUz5Yv9ou1F2jE3HUJtj1vbDOnO2KT3es9cpp57fVBtjh+tJ+HrS
+ xtiyx193XFvLbs/1pFobY2ss+WPbqdsVe0NUFr78id1WH2sb28Xkyyro8Z4dSxy1XVPkxbyqfdV939Xm32bbjnW+b2rz+Jehx3u273hq
+ 26baGEe8Bwu/B7PsMXTxPqhum7QmudYGPeaz1yqnnt9UG+OH91lx77OWHeNNxmidfixj+3DZe6Vl6fGetaVj6oCaamtwuo/98A//cP6/
+ 9YBLPQE1+1yJqj/Q9hMS3/NsAle9oaoKfcx2vzFt8Vn2j4dO0ib0eM/2X4/avinyZV7Vvnw3QDbKciGmHT5tjtkucrYpPd6z1yinnt8U
+ +TFvmdeV8I0r0cbYWnZcW23kBdcTP/JnXmhf7j50u+wY9I2xJvkTu+26t4vJpWXp8Z69Xhy1XVPkxbyqfdkc8MWq/zZ38b6pzeNfhh7v
+ 2b7jqW2bIlfmLfO6Er4x6uryfZDdf0ybfJrkWhv0mM9ep5x6flPkyLzQvtx96HbFvs9adow3GaNN88Nqeq+0LD3es9fpmDqgptpKCj1I
+ fM9JOQFduq1ViWWfH/p0xH6i5HsTFJPA7idv7nHX6UOfmNd2NX09Hz3es/3Xo7ZvinyZt8w5t3+wfftvOobaGrNd5WxTerxn+y+nnt8U
+ +TEv5nV1W8vGbdOx1WRciybbxxy7q845i6XHe7b/etT2bSN/5oX2Vfamv+kNUSh/6o5fq43tyqLOPuvS4z17vThqu6bIi3l1Xtcqe99V
+ d4yGXt/uZ93vm9o6/mXp8Z7tO57atilyZV7M6+q2Vo2hVY9zl+/46/SlT8wxhHKtDXrMZ69RTj2/KXJkXmhfq3yf5fK9fpMxWqcfQ+zr
+ L3uv1IQe71lbOqYOqCl3cFYNEDvQQifTfjog4Rvobb6Wq04CNn1dd6CHBqTmblMVut9iE9i+hntMtl+WTYy6fzzqnLNYerxn+69Hbd8U
+ +TJv2XNut9MXtL6M2a5ytik93rNjKKee3xT5Ma/qdeteT5qOrSbjWqwjL6w65yyWHu/Z/utR27eN/Jnne13brtA1wm7j23eT/Kk7fq11
+ b9cmPd6zY4mjtmuKvJhX53Vddjv9vqutv83ruD60kZvL9l+IHu/ZvuOpbZsiV+ZVva47ZmPfi3f9Pmhd90m+XGuDHvPZcZRTz2+KHJnn
+ e911vM+yfK/fZIw2zQ/RJMeb0uM9e42OqQNqyj25ZVPGq57nDnIJ3x+qtl5Lq0rAtl7X/QMT84fBsscXE3q/7jGVJZ/b/zYJ3MRZJjFi
+ X1vU+SNXhx7v2f7rUds31dZYGkO+lAkdQ1/GbFc525Qe79m+y6nnN9XW2BlDfixzPXH3uczYajKuxTryQpTlZhN6vGf7r0dt37a2xtZQ
+ 88f9t9hwj8Pdvm7+xG6rrXu7Nunxnh1LHLVdU25fNBmrY7iulAkdg7vPqrFW9rd5HdeHprlZdvzL0uM923c8tW1TbY2jMeRKn+/pRWis
+ rus+yZdrbdBjPtt/OfX8ptoaP0PNEfffYsM9Dnf7Zcd47D58Y7RpfogmOd6UHu/Za3RMHVAbbAeXfTpQdSLtJxYycMs+tWnjtbSqBBRN
+ X9d9LOaPgmWTwvcHyWX3r5/XJPncbZdJjDp/POwFvKqddenxnr1GPWr7NpAvhbqv6wodQx/GbJc525Qe79m+y6nnt4H8KJS9rvtYnetJ
+ k7HVdFyvKy/EmK4nGvlT8L2uO4Ziwz2OJvlTZ/y61r1dm/R4z44ljtquDeRFoe7rukLHUGes9fF9UxvH34Qe79n+46lt20CuFMpe132s
+ znuwPrwPcvdR1kchTXKtDXrMZ/svp57fBnKk4Htdd3zEhnscbY/xJteDZfKjaY43pcd71o6OqQNqgzvwQifYJpVvANuBYx9zB4LeX9PX
+ 8olJwCav6w7iOhcoYfumavCHkiw2+ULJa/9dou6xx76227fLJHkZPd6z16hHbd8G8qVQ9rpf/vKXzf9aVDWuuh6zXedsE3q8Z/svp57f
+ BvKjEHpdd3ysanz7xlbTcb2uvCjLzab0eM9eox61/SqQP4W6r2vZ7Xz7bZI/sdtq696uTXq8Z8cSR23XBvKiUPa6y77vih1r7vHpdqzr
+ +tAkN8uOvwk93rP9x1PbtoFcKYRe1x0vfb2nrxqr9jgk1vk+sg16zGevUU49vw3kSKHu61p2O99+2xjjTcdok/yw2y6b403p8Z7tv2Pq
+ gNpiB7GE7kA7wHyPucnmfkpT9gdr2dcKiUlAsezr2uRY5o9vWXJq9vjcPwAxA9tNMF8fuG0L/XGxz3HPYcxrx+y7CT3es9epR23fFvIl
+ /LplbXHbX5ZPMePKPqftMWsfj+nPVeXssvR4z/ZfTj2/LeRH+HWbXE+ajK2m43odeeH2Wyjvm9DjPXudetT2q7Ls2BpD/lQpG6dN8idm
+ W591b9cmPd6zY4mjtmsLeRF+3bK2VL3vauNvc1neabaNbb1vauP4m9DjPXuNeGrbtpAr4dft+z197FiNeZ59Tt37pFCutUGP+ew1yqnn
+ t4Ucqf+6lt3Wt10bY7yNMVr1GsI+xz2PZW3TfDnelB7v2f47pg6oTe5J8oXvJNhtfJ8o2MeqBoQvYk64FZuAYpnXdQd3VbiDt+7FzffH
+ zE2+qigb+O4fn7Jwz2Nbr92EHu/Za9Wjtm8T+eJ/3dix5uaKTxdjtk85uww93rPXKKee3ybyw/+6y15PxLJjq+m4Tj0vLD3es9eqR22/
+ SuRPvde17D592zYZg3W2lVhm3Ev4tlumH9qgx3t2LHHUdm1aZszYbXjftXhNEU3/Nnd9fWh6/E3p8Z69Tjy1bZvIFf/rDuGe3orN+zbv
+ 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
+ r7YAAAAASUVORK5CYII="/>
+ <rect v:rectContext="foreign" x="0" y="0.749991" width="690.3" height="294.746" class="st1"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i9.svg b/doc/guides/prog_guide/img/efd_i9.svg
new file mode 100644
index 0000000..219f6e3
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i9.svg
@@ -0,0 +1,271 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i9.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="4.4908in" height="1.59055in"
+ viewBox="0 0 323.338 114.52" xml:space="preserve" color-interpolation-filters="sRGB" class="st2">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false"/>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="19" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape1-1" v:mID="1" v:groupContext="shape" transform="translate(0.375,-0.375)">
+ <title>Sheet.1</title>
+ <rect v:rectContext="foreign" x="0" y="0.749996" width="322.588" height="113.77" class="st1"/>
+ <image x="0" y="0.749996" width="322.588" height="113.77" preserveAspectRatio="none" xlink:href="data:image/png;base64,
+ iVBORw0KGgoAAAANSUhEUgAAAqAAAADtCAYAAABgZcdeAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7D
+ AcdvqGQAAEnxSURBVHhe7Z0/rOXGleYVCQsoGWVOWpjQodECJnaoQAN0oMC5gZGzwXYgOOvQgbJJHAkTdrIDYTGAMYBh2ECv1pKxhoJ1
+ ImjRHqzhHawMw/rXbsu29i2/Kn58h+dVkUWyeElefj/gPN7HP8VikafOx8PivS8IIYQQd/gvv7+RyWSyVUwIIYS4A4PEu3+4+U//9dPm
+ s6aaaqppvalEqBBCiLu04jMK0TiNgYPzNU/zNE/zFs4TQggheiA4NMGCgaILGAggIZBonuZpnuYtnCeEEEL0QIAIASMGDSGEWEpPfGIq
+ hBBC9GiCA4MFpkIIsRSKz06ICiGEEBafqRBCiKWEPsU8WWm7GyGEEKKlFZ8UokIIsRT2KxwP2vY2QgghRAuCg8aACiEqYof1SIAKIYS4
+ iwkSmAohxFKC6GxvakP/IoQQQli6DIUyoEKISnTCs+1X2u5GCCGEaGnFJ4WoEEIshf2KxoAKIYRIg+CgMaBCiIrwhrZ7wiKEEEL0MEEC
+ UyGEWEoQne1NbehfiggbyWSyi5sQG9BlKCpnQP/PO+/c/OyFF+7Yz195pV3jOvnsgw+Sx2rnp6xmu/zx6dOb//byy73y/+ff/327dBlz
+ y8Y6JetPLd+WW7K+uAzoS2LfMvYI3q/YTPm/pppqut40WDtGJs6TEBUXptfnLxegOeHpDYLsGvn0yZPuGP/Hq6+2c/vzh2ypePpfDx8m
+ y6Utafc5Zae2+Z8PHrRL+0wpv+Q6++qTT9q1xaVhv5IfA4qZ3difdiX8b8YDaaqpputNGfQ78dktb0yIS4BrzfT5S/CiAILCg3U++Zd/
+ af+7PmymEwKUIsjO99nOT5p1bNYv1W4l2PbPZV/nZlqnlj0kEFMie07d8b9vK5sR/d9vv93OFZfmTmzrgRldx3M3EGqqqabrT2PQT/tg
+ MCHWxl17c/GPTiEcSrHiA9v5/wn2wfk0m2W0fPQP/9Ct4wWvrScFot9nal8lGbWSDGiuzrZe2D8pbdfc9sBmF9Eetn18fayIY9tNKRv4
+ jK/9P5UBnVp+DrQP19Wj+O2IMczEtg7X4YQVGyH62ntf3nz/V89vfvq7v7RFCCHWBL72w6d/uvnOL54FX4z+GJ22MyFWpIsBCzOgVsDl
+ HrHmsNsia8XPMAouK0JS9n8fPw7rEbs+yrcgc8ZlKQFqxZm3IQEIrADKZUDtfIutMz4TW9/c/iHaKOJS5dv9U5hZock2su3AOswpG+Az
+ z0tPgDphOLf8FLb+Y+uK9UBf0otnHV1nExd888ef33z46V/bzYQQW/CHP/+/cBN467itAIUJsRbhGls+BhTBnoF/LEvlsaLBG8SHXQ6B
+ Qqxw4brEijkrTr9+/rzbBlM8/ga+DvbxrT22MXG9JAOaOk6IMXuMPjtIeiItUcfU/lEW58HsUIBc3UvL9gyVMafuOey5siJeXBb2K5iG
+ voWdTRfc3v1DEJ9CiP2AbOitn/q7RyEq08YCXmtzyQlQiBrOt2bFgRd/XjjYsq3IBHZbu11OgIKxDCj2Z/FjNClaU1gxBbHEdb2I8lk+
+ YLf14x3HGKo/8GKd9ULbcJ41285zy7b0RKYro0b5ANtynTGhKtYlHcPwob3bhSnzKcS+QCb0Gz/6LDotnBfiQIi16AJFnM7FBn8r+Lx4
+ oNkMoxUgKeEwJD56wsZkz4YEKMvDNCVAvQAGtg5WnHly2bqpWcKpAqon4hJZRJvttMcN7LmD+Zd3ppQNS4lre2y+jKXl++WwnEgVl6GL
+ X+xfADsaGDItQoj98YOPnkc/bV8SDCbECtzGhHitzcWKmNwj+JzIGxJ/VsB64QRyWcMlGVC/PoAw4vIhAerrw/LtfIjLlEizdcA6U0SU
+ LT+VRcy1E7BtDPPHN6XsnHAeKmNJ+bbNwvYJASsuD/qS2Le0MSx2MrGjwQIEOSHE/sCTCfpqJ0KFWAMTE/B5LlYIpEQEsOvkMqA++was
+ OPLCLZdZywlQPwY0JUB9HXLbpFiSAbUvP6WysEPYLGCq/J7IcyIN54vLwvb377dLImNl985B5twPZUB9+XgKZMmV78Vn6toR29DFr2Ya
+ +pbuQ1jwez1+F2LHeH9t5YIQVYnXWHuT09hcfBYtlQW1gqE0AwqGso854ZgToMgq9uqZEKBeRNltxsZm+mxdMgPaCDyf3RzLQpZgM7t+
+ +9zXUvmvY+Jnfx6Gys61tcUef0qklpbPuqP9OA+W26/Yhtv41cuA4p84QwixX6K/Rp+Nd5BCrIC9xhbGBZ+R8iImJxZz84ldbjNwNnMG
+ s8IlJyit4LLZTF93K2iwPeePZdnmZED9cfh9lApTewxWKNt9w1KimFnJ3L5Ky/bCmvSymM2+fJazd44bgY6sM8jV3Z7HsXMiLg/7FUxj
+ /OIHCVAhdo/311YuCFGV3jVWIS54IZczK07tNl60EpsFS1kqA2bFlDWb6csJ0JRZ4ZXDC00KMiv2hiyVHZySGR1rJ7u9LTdVTxyvFYpT
+ yvb0BGjiGMGU8m1WPGe5/Yj1uRu/QgfT/NOOKRNC7Jfor9Fn4x2kECtgr7GKcWFITPgsmRV/Q9ks/5gaBpHns2kktT5g3bAsJUCxHIKW
+ /8P8uMUcXoASn8nz5rOilikCFKSO29d/KKtrl/ntSspO0ROgA+uXlm/rmDMJ0O1gv4JpjF/8EBZIgAqxZ279lXeQQtSnd43tJC7kBOWa
+ lIpgMY8tzqnYjrvxK3Qw9e90hRD1if5qfFaINVBcCJQMAxBClMF+BdPQt/TU6Ik7GiGOQOe49FkhVkBxIaIMqBD1uBu/QgejO10hjkD0
+ V+OzQqyB4kJAGVAh6sF+BdPQt/TU6Ik7mrPz9OnTmxdffBGC5uaDggHtYhs6x6XPCrECigtCiNrcjV+hg9n+Tvf1118P4uett95q59zl
+ yZMnYR3YKwVffXEpWPcadXqnuePmMea+O83z5ptvhvVRj7lAgL788suhnLMKULQB295a6Xm4BNFfjc8KsQY7iQtCiOuB/QqmoW/pqdEN
+ OxqKuEePHrVz+kAEYDns7Z2MxWGdaa8OfPVHKVYIPi78FYep66c4uwB9+PBhdx5TNnRjdEk6x6XPCrECe4kLQojr4W78Ch3MvjOgEHUU
+ R2+88UY7dxtsXbxBgPKXGpZAMVSS0bQZ0/94/nU7dzoQoC+99FIo52wC1LahvwHC9chlSwR+LaK/Gp8VYg12EheEENcD+xVMQ9/SU6Mb
+ djRDGVAKvj08drdZT9aHAgYCtMbjWghA7mNMDA612xRQ7zNmQK3wzmU5rQjd+nF857j0WSFWYC9xQQhxPdyNX6GD2fZOF1nFXAaU8yHu
+ SrDjRGkow8Ixk0OC1mY6beaLj6otVoDm4D5hYyLG7ntIWKIuLDP1SxhWLNNyx4yy8BISzAtQu59c3XPnj5Scly2w2c8cVqRunQWN/mp8
+ Vog12EFcEEJcF+xXMA19S0+NbtjRUMDY8Z1TRBtICS5rLAPTsWzf+++/H5aXZDUpYu7fv59dd2oWDUIH60IwQpCm4DrYr13HtlvOfB2G
+ 2oTLIMJydWfbpwRz6XkpYaws2lC7WVgepkM8ePCgaL216RyXPivECuwlLgghroe78St0MNvf6VIIMINmBdvHH38c5g1hhYkXUCmR4ffn
+ oYgrebQ9JQMKIVciuJhpxTbIbqbEFI/BZ+W4berYWCYElYVZTojMVAYU80sEqN8n58Ny58XXZQhb3pCVZMxtpnnsxTaev9JM/FpEfzU+
+ K8Qa7CQuCCGuB/YrmIa+padGN+xocsIil6G02Me7OYGEjJhdjnLxv88eEr/+EBSg2KZk/VLwwhXKRdt4So45BdaF6PKZXc5Heb7N7bLc
+ vnj+rGAvqeO9e/cGl68NzzPO4RA227xVXUHnuPRZIVZgL3FBCHE93I1foYPZ9k4XAT0nQHMZSguzU0NZLJZvs4U5wUVBWfrGfUkGdA5W
+ wD179qydG+ExT/1WAGY6cexWSI1lQFPbEAh4tq89X3PPy6X4qjkWXgNj+1/rHE8l+qvxWSHWYAdxQQhxXbBfwTT0LT01umFH4wUMpvgf
+ NiYOcuI1ZbYs7sOL3KmiyIqT2tmxnEBi5nAoQ2zbMGW2rvicE+R2We742GZWbM49L5eEGdCx/fMcKwMqzsBe4oIQ4nq4G79CB7P9ne6Y
+ gBkSWnOFDsc24s1vigpm+6ZkutbMjlFE4hgJM6O5/fEYhsyP58Q2uZ/iZDvBct81ynNgxfzc83Ip7BjQsbG+GgMqTsVO4oIQ4npgv4Jp
+ 6Ft6anTDjiYlYAAFAiyXeaJI89uWwLebKYAoJqeUtWYGFGIQZcMoDCmGcnVke8B8ffB/Kptp56cEaGkG1NaJ9ZjSlmOUitrS8bhD42wt
+ 3O/YemvTOS59VogV2EtcEEJcD3fjV+hg9pMB9WLFCqOcwKMAnPMrSdyWb2KzHkMZV8+aGVDg2wbtAcsJLLYXhKOH2Uy/PedjO/+tA1aA
+ ptrFniObwV5yXlLYsaZjVnouWEdYrj1x/Fxnq6ECJPqr8Vkh1mAncUEIcT2wX8E09C09NbphR0NhkXphxWYBsZ5nTCCNwXGA2BYibKpg
+ oohZa3wgy0c9+Xnoq4vs8XhygmusDYfGSmIey7Q3EEvPyyWwdcxlajGfx7fG+Z1C57j0WSFWYC9xQQhxPdyNX6GD2fZOF0HdZ/k8Vjil
+ 1uFjaVhKJKH8nAiy2+a2H4J1G8q62X1MFTFWJNGG6si29GLdtiGEtq8H95EqO1d/WybM30BY8Tb1vFwKewx+LKg97tTN0aWJ/mp8Vog1
+ 2EFcEEJcF+xXMA19S0+NbtjRjAlQUCJmuDxlyOJ9/fx5u/YtEEB2nalQwGDbnLi0dZ8qQIHdfqwMezze0EYc9wqz4s/O99lcfPYimAbh
+ TaGWOn8l5yX1XayXxLevt9RxbUHnuPRZIVZgL3FBCHE93I1foYPZ/k6XImUsy2TFTEqEprKFuXUtpftPMSUDirrNEaD2O0FL6oh24Po0
+ toEVqD77yLbLHYtvW7QbYBvkhNrc83JJcnXcE9Ffjc8KsQY7iQtCiOuB/QqmoW/pqdETdzQUoF6QCbEnOselzwqxAooLQoja3I1foYM5
+ 950us4u13tYWYi2ivxqfFWINFBeEEJVhv4Jp6Ft6avSkHQ0fu+7pcbAQKTrHpc8KsQKKC0KI2tyNX6GDOd+drn97W9lPcQSivxqfFWIN
+ ThoXhBDrwX4F09C39NToiToaK0D5Io0Qe6dzXPqsECtw1rgghFiPu/ErdDC60xXiCER/NT4rxBpcOC7ga9h++PRPN9//1fObb/3kc5lM
+ dmH77i+/vPnBR89vfv3s69Yr68N+BdPQt/TU6AU6GiHEfDrHpc8KsQKXigs//d1fbl5774tuPzKZbHv723/77Obxb75qvbQeKLsfv8IO
+ lQEV4ghEfzU+K8QarBwXkPH89pMR4fluG6Q01VTT9acJQ2b0w0//2nrtcmK5cX+hb+mp0caEEPulc1z6rBArsGZcwCM+ZFhY9h0rDZia
+ aqrpelNjeFJRA5TVj1/tDuLM36/6/F8IsQz6Kxw4+KwQa2CvseZzLRBfbq/hvn3zx5+HMaDIuNQKeEKIYeCT8DeM/3ztvS+jP64kQmNZ
+ sewYv8wM2Lu//XO7qhBiTzB4UxQEE2IF4jV2GxdqgMfudzKfTaDDPAlOIfYBbgC/84tnUYQ6Ibr0cTzK6PUt4U+3kz/cfO/DP7arCiH2
+ BN4Spp/eOrAQK9BeZzUzoHdeNmriDgKdEGJ/ICNKP6XP/s2/ftounUcsJ5YXM6DsbNqdYAe4UxVC7Iv4eKTtDNgpCLECtTOgyHCyLF67
+ Ep9C7JuQ9DACFAZhOhds38+AAnwwO1HHIMS+uM1+mkfwQqxFuNbqZUD9o/e/+9kX7RIhxJ7BU3Hru0uyoLGMqDX7GVCYEaEYDK5MqBDb
+ A/HZiU6TlYrOK0R9elmKxpaAONJds63V/GoXIcR6BP91WdC5Y7axba9vIV2AMzuC0sW34yMjOvi1GTKZrJrB1zBeDn6H72G7XXabkWrd
+ Voh1cNfbEmz2HvFF2U8hjkWXBW314dx3hWI/EMsIfYslJUJlMtk+TOJTXIp4rd3GgSXgRorlwPB0TQhxHHpjuBtDkmQO2LbXt9yBO5EI
+ lcl2YcMOK8QKhGuvTgY0ZPERT9qYcs2P3/G48uWXX4af3nzwwQft3H1xqfo9ffo07GeLttj7OTgafhjN3HGgcfvYD4S+pUdbODudToRK
+ jMpkl7fgd8ZZOV+Ilend9DS2BD9869p/7OSVV17Ztfi5d+/eRer3ySefbCYEL3WMZyL0CUYLzgHb9fqWjrZQH/Dw1S94ZIIUrF5KEmJ9
+ 4Gv4qgs8urwVnmb8J0yINXHX3BKQLVkauI7C18+f7zr79uzZs4tmQF988cWLt8Ulj/FM1PDjuK3RmB1OeGJHc990EkLUAdkivLRhs6Fh
+ KsSKxDjA661SBrQNXtfO3jOge69fDc5wjJcGP5VbQ4D2+pZAWyDF5zd+9JmynULsCP5Gb3eDCBNiLcI1pgzoVDgG9KWXXtql+GH9kJm8
+ VnF2hmPcguDH8OEFN5KxD4jbh77FdjZcoN+DF2JfoFO9FZ9tMBcvvPCf//tNMFGVeK2111ljSzh7BpSPg2mvv/56mJ8Dy+36MJSJR8s5
+ 3nzzzeQ2GIfpWVq/KbBsLwQ5n/WbU4d33nmntw3s/fffD8vGMqBYz2/r92nXefz4cTv3FtSdy99666127vWyYgb09k4XmRYhxP7AWOxb
+ B46dQHTgE0MBKhFal3B91cmA+peQrhk7BjQlkGgQSJ6UiPTmBSWzfal1aVaEsX7IDr799tvJ9WGp+s2BdcsJ0Dl1GDtmiEEsz2WhUwLf
+ mm1jew5927OcN954o51z3XQZ0AV+HLeN4jP0Ld2HdsGS3/kUQqxH+C62cAd6exfadIDnxgpQidBqxJiwLNtBzpoBpVnhAkHE+T5rhpd2
+ IJxS2TSW6bN0QwIJy1LiaG795sB9eSG4pA5WQPrMJNbnMpjfr902t8y3cWr+kydPunJ8u18ryIDa5MccsF2vb4mF3d7p6ifShNgnuPOP
+ /mpuGs8OROc//lQitDYuLiyhxtixo2DfwH711VfbuX0okh48eNDOGYfi1Jf58OHDUBYyiaWU1m9pZo9t4TORNoM5tQ5W+KUeiwMrQu1+
+ S0Qj6oXssF3Otrf75P9exF4zq2RAu0DWzsTFIYTYJ7e+yjvIk2OFpzWxiBgX2mussSWEDOjCsWNHgtm9nEBi1hLrlcZbK4KsOBrKgOYo
+ rR/E4dLs3lgGdGodKC6xfQ7sC+v4/XKIw5BYh+DFOr5e9pyVlHON4OX0W704X4D2+pZYWJ07XSHEutw6cPTbpiM8NxSczILabKiYT8W4
+ cNYMaC47RoGUy/4xq5kzn51DhtEu94+QLTXqV4rNdNp9lXxXKl8A8nWg+BvLzrJ8tA/ho/QSSwlju/3StjkiwY8X3kjGbWMZoW/pBbOZ
+ hQohLkPPeeG3Z4diM2diFvH6WhZsyFkzoCUCz4pJfMb8MUtlJXPiKlWH0vphvdS+ppDb11gdrAC1dWCGEpnQIVLlLxWgbJfc8munhh9j
+ u17fEgtTBlSIIxD91fjs2YHItFlPPx4UJqZTMS6cKQNq3zLPiSuORcxl92AePoKHjYlCL7R8PebWbypsi9Tb6HPrwGMrzYDa8pc+OmeZ
+ tFzdr5UaLxOG7VvxGfoWdjCcKYTYL53jtv7adITnxotNa3ocP5veNbYwLigD2gfzsfz+/fs9MTm0HdajACrNSlKs+Wxhaf2w3tB3j5aQ
+ 29eUOtjjXTIGlOM40S5T4bAIbMs6+PN37WAMKH14rh9ju378CoUpAyrEEYj+anz27HixmcqAcp4op2Jc6DInsEaIXjO5N78tqewehMyQ
+ KLMvG5VCofTo0aN2zm39LpEBHdoXxbQdo2nJ1YHzYbn62wywXce+yJXbNoXdJwUny7Fte+3Qj5f0CbEfiOIzlMPCOFMIsV86x239tekE
+ z40Xm9b0OH42vWtsYVxQBrQP5mM5xJXNoHF8o8/QWfEJs9vwsbIfk8h9pJbNrd8ccvuaUgefhcXXV2FZansrPlPLKcphqXGcOAd+G4pN
+ uz7rl9rHtaIMqBAnJ/qr8dmz44XmUAaUn8U4FePCGd+Cn5NhtJk2bxBWqcwexVHOvJgFc+s3B9bv448/bueUvQU/VIfUm//WIDJRPixV
+ vhep3iCOydBb9xSzqOMZvr5SGVCxGnBqdEpwKP6ertgfneO2/tqcr3Njhaa3lBiliUF619jCuKAMaB/Mx3IIF59htI+Jacy8cTuYLRtl
+ +G1gqQwfKK0f1ts6AzpUB5vNpLE8ZpNz5afaGWbbzJafqwPLqNFWe+eqM6C8K8FJz2HvEHHCtwIXL+thLXW3ORX7uKX0guZd2pL927vK
+ nNOeCV6PW15nKaK/Gp89OxSUFJtjGVBrIk/FuHCmDKgQ18pVZ0AZ8HODeiHGsBy21S8Q2CzhkC3JINo7s9wdrGfq+inQviznzAKU1yFt
+ b49XOsdt/bWp47lJCUtaiRgVSXrX2MK4cLYMqBDXyNVmQBHgGfhTGVD7iwpj3/+1NqynFyVWOOLrGZaIFvuVD2PYjOl/PP+6nTudM2dA
+ 7fXlDQIUY5b2QvRX47Nnx4vK0gyonSfuUjEuKAMqxPE5bQaU4mBvj0M9EG6oJ2zJeBBbzpgYHGq3KZw5A8o2hPEao7BPjdPaks5xW39t
+ 6nhurKj0VixGf652dPSusYVxQRnQ45O7Qc/Z0FA6cUxOOQaU8yEESki9SYgyLBwzOSRobVas9NE2sohYH9t50cJ9wsYEjd33kLDk/mAp
+ 0WiFFS13zCwr9f11dj+5uufOHyk5L1uB40N7W6wA3RPRX43Pnp1ORLbCslh0JuaJWyrGBWZOgjVCVBwLvtnfXBXFJgF6fZwiA2rHd04R
+ bSAluKyxDEzpULlsH+ZjOQRb6eN0CNXcNnBIX48hhsoiOZFk2y1nvg5DbWKX5eo+JEBLz0sJY2XRlv5KBdt2b7920Tlu669NHc+NF5HW
+ SoSn/18EetfYwrjQE6ALyxJCbMOpxoBawZYTiRYrTPz6XIYvsCV+fx6KuNIXnmyGDxk1D8tDhrFE0DArh20+a44nJUJ5DD5Dy21Tx8Yy
+ bVsAZjlzGVDMh6XGmdrz5zO2EMeYD8udF0xLsPsZMwjHJSgDehC8iCwRnSmz6wiNARVC9DhFBtRbifi04i8n7u7du9dbjnLxf+6FIWQe
+ h8pLZRlzj7fnwl98SAk0vGnP/U7J0GFdiFCf2eN8lOfb3C7L7Yvnz4petjG+OSC33Vg7bwUFKOq3p7p1jtv6a1PHc2NFpDcvPFNCNSdO
+ T07vGlsYFzQGVIjjc7UZULxlnBOgJRnIkmwly2e2EKIzJ7goPobeuLcZWm8pwTgHK6z9z5HxmKd+K4DNdFphhfn8iinfHtwG7TUmQFND
+ KKacl72gDOhBoGAcEpQ5kTlmZ6ZiXFAGVIjjc4oMKDNoVuCNiRNuW2K2LO7DZu3AXFE0pc6lUCT78pjR9WLRYuuTMism8bl2BnTuedkD
+ VoDmjnkLOsdt/bWp47lJCUfamBhNffbbnJTeNbYwLigDKsTxOcVb8DZbZgXMkNCaK3Ts2EaKDGb75ma+KPpQpxqkymNmNFdHHsOQ+Wwm
+ 2wLL7O/2gikZ0GsUoHsi+qvx2RpYwXU0SwlHLyL9/yXGbc5KxbigDKgQx+dUGVDCzBssJ34o0vy2JXCcJQUQhcecsgC3rzUeFMIb5cEo
+ wvloO1dHtgfMtxn+T2UzITI53/+SU24bC8+ffQlpyXnJUSpql47dPFUG1Iuva7Gc8OR8P/WfaSekd40tjAvKgApxfK42A4oAnxOgVvzk
+ slEUC3N+JYnbYv+A9RjKuA5hhUstfNugPWA5YcT2gqj0MNOZGgPKDKg/dizLjQ8F9hzZDPaS85KjVIAubf81zmMNor8an62BFVs5MXbE
+ zzTOSy3Lmd/mbFSMC8ycBGuE6Jb4vhR9G/6Hze3zxTGx32869dxf6ropecH6UpwiA5p6YQUnmCcB63lwMcy9kADfxMa2KGdIMGFfQ/gO
+ rgYUQ6gnP/uvUbLY4/Fwe5i9oK2ITG3HMlOPym2Z9riXnpct4TGd4ntArdC6Jhs7JrvcC86UnYjeNbYwLvQE6MKylsBgjr6MHLmPuiYY
+ N3FupvS3PHfWppQxFCuHuOR1g5iK/dTUFHM45S8hkZzIIfZrkVIiCeXnLhS7bW57YO9GUmVZZ/DL7T6mChp7sdOGxkyyLTG12Db0GVD7
+ rQCpsvn79DC7nS0T5s8NnQc29bxsCY/rtBnQo0+t2Xmp5SnLbXMWKsaFvYwBZb9o+6G1hERK7Iq78JzQSvtbH7O9+fiWAsvnnvuh66b2
+ ud9LFvSUY0AtJWKGy1OWuyBwAY2tA7zYylmqbrbucy4iu/1YGfZ4vKGNOO4VZp0HmV/ORzvY70dNiWAaOg12CKkMdsl5SX3B/ZbwXKNu
+ Wzq9p3Pc1l+bOi6H4urabExscnlObHrjshPQu8YWxoU9jAG9dBBnH4y+cU/9x14YiiVj7WXFZ0ovIP4O6QgL+neU40XkEtY494zNpce1
+ Bqd7Cz6FFTMpoZcTSql1LaX7BzmBN/Tonk6Dus25KO0Xz5fUEe3A9WlsA1t/73hsu9ydqG9btBugYMs5yNzzshWnzIDmhNgRP1vjfD8d
+ srF1r52KcWEPGVD2vzXHow9Bwbu3/mMP2BjO9intb+2NRA3RiJiEbGmNssga534P8eiqM6BbQ6eoeSEKUZvOcVt/ba7Z5aQE1pGtVGja
+ 5WPb+HX/8ed12n6n9K6xhXFhDxlQ3vymbni5LHczjptn+z+NN98WCoWcpZIPVlTRUmVbfF1ShnI9U/bFfaDOPqGRKrsUJiNsW7Ddxsbc
+ M06XJGFK8BnQVLvm6sN1ue3Uc58aRpB74oZ9cJ0lbb+Eq86Abgmd8lJ3x0LMJfqr8dkaWIF19GnK7LKh9azl1vNlXSsV48LWGVD79CWV
+ YEgt4xvS+PYPiB0sTxkEg2WqCKGgyplfPyUgc+aPdcq+8Jntkjr+VDsuoSTDZ0Vwjf3bt+DHzlvJjcuUc89tc+aPz56PrZ4aKgO6Eluf
+ WCFK6Ry39dfmul2OFVhHtylik8uHtkmtY+1KM6G9a2xhXNg6A4pg3hxSMC/oQG4cIOentrVlpoYdcbkXqBYrCP2+uQxTC2OVnw+4LFWf
+ OfsaOv7aULxhn7n9+DbFerZ+tCn1HDtGKxT9Mm7r23Ps3Fuh6suEBsklwrYeB6oMaEX83Yqyn+IIRH81PluDTkxlhNhRPueM65SsO2ap
+ svD52qgYF5g5CdYI0UvDvj6XXaPI4KN2wvm57SAEsDwlBpmpzG1rM5k5wcT9c/lYmVgP2/jlc/YFxo6/JmPnCNh1Uo+vrZWINPvNL7Ac
+ XMf+yIrNkOcEaO5YWPepwwh4vW2lVZQBrYgVoKkORIg90jlu66/N9bscCqmjmxeHViRaSy1Pfc4tT9mVZUJ719jCuNAToAvLmgMDNzJS
+ EA4eZrIg1Cycn3syxhiSynRZEZISfSUihBkv7n+sTDvUwC6fsy/A458qlOZgxWXqHAEbs2EpIXbv3r1uuReGKXiMQ4I1d6PBbYcEaOo8
+ oY2xHJZanoPncSu9ogyoECcn+qvx2RoEAeVE19GmQ1ayTqnZsu7U5YpEaMW4sPUYUAbulGCx4wCtkIAwSM23WKHhGctWQkRgeYlZUcg6
+ pY4lt2zpvuy8tbACNIcVoEOCkfW2GcsUXz9/3q3rs9+WXN1y246de6zPXxaklYjKkjZaE2VAhTg5neO2/tp0SMuhoDqqpUSh/5ya1wnH
+ zDQ3L2V2+RXQu8YWxoWtx4DmMlgkl8nKzSdWgPpM1tAyMFcUstwh8/ubu6+x468JxRX2mcsKWgGaWweMnW9LyTHm6pbbduzcE2adveXq
+ gnOD5UNttCbKgApxcqK/Gp+tQUpEHeVziU1df8hY1liZR6diXNhLBjSXOcplsjAflhMEQ5musSwY6zSUyUvBuqYs96i85BG8J5cZXouS
+ 7B7bFPUaEmAsa0yAlh5jqm7Yf27bsXOfwt8kpOozlMm/BMqACnFyOsdt/bXpkJaTElBHMi8KcyJxaHnqc265taHtDkzvGlsYF7bOgFoB
+ kRIuuUzWWHYM87E8lZGyIiS1T9ZpipjgNlNFa6kg84wdf03GzhEpqdOcDOjQMINcebm68LoYO5YUFKKpczzluNbgIhnQDz/9a7uqEGJv
+ RH81PluDlHg6yrTEpqw7ZlPLOiouLixh6wwoBQHMC4KhLNhYdqwkA5raJ7AvDOXK98wRrWDOvgC2Sb3lvQZWgA4xlgXEsWI5bGzsqj33
+ uf0OlYdtYb59xs79EBSZqWw1xeklXgpLcZEM6A+f/qldVQixJ3Bz2Dlu669Nh7SclHA6kllRmPucmsfPuWluXmr+0DYHpHeNNbaErTOg
+ YwIsl8kay7ZhPpanMl12n6lsFqDYgKXEEkSW3bctM2eoc4qxfUHcTD1+ikHYVKHlKc2AYhnbwGcCbfvk2sHDY+Q2dt9j5eXaZ+zcs938
+ eeD1BEN7eFhmatkluEgG9Hsf/rFdVQixJ37w0fPotK3PRgeugBdOR/2cs5J11raj4eLCEpg5CdYI0S1g8E6JLy7zQoLzIShSjI31s6KP
+ 5sUVs1o588KHQm3MUqJxbF9e/I0dvz2+IdFYQmkGFNgMY8pQb3zH5xg2A2rFtDesk4Lbptp66Nxzu5ylsrs4B/jNeixP7e8SrJIBDZiZ
+ 6CBKTp4Q4rL83c++6Dsv/LUGKcF0RKPQ9FNvdn7tz0PzDkS8vtqY0NgSegJ0YVlzoSDwWTOQy2Tl5hPMx/KhrJ0XIqn1bMbMmhfLFEko
+ MwfLSR0nKN0XGDv+NQQo9llaVkpQD7VNCuwPwwywT5hvm1w7gintA7PHldoXLHUewBSBvhbrZEBBNzMWjEAnhNgPNvtZVXyClFg62nTI
+ pqy7pmH/RyFca9cxBhRQLMJKBc6esMJxCCuKLwGFkVgXiu2pArsmK2dA+yL0O794pkyoEDsgiM9uDN1tVio6bwVSYumoZkXmmOD0wnTp
+ dGwe7QDEWHB7rS3BjwHdKq7g0WZzaNks056xGbMhAU1BOJS5qwkE0VAGWCxnyUtNNbE+PLdPwHa9vqWj62yaabsj3LnipSS9GS/EZYHP
+ wfe+9ZPPW7+M1t0k1iQllI722c7zltv20mb3vXfC9VYnA9pdw21c2SqejI3Z3Dv2kXNKiFB8wi4lsiGKt3or+yxw6MXW7Rz7hDYGNb48
+ h1hG7AdCOT04s92RTCbbiYXgHR239dZ6WJF0DTYmSnOCtMbnsXnWdkyMA+311tgS8DStu44b2/KbVvbwKHMuufGb3i51bHzcnxsDKZaz
+ l6Ej/AYWGp5qzAHb9vqWO3Qr3O5MJpNtbHz8sQYQQ14sHW3qP1tLrbuF+Xrs+bfjw3VXJwP6/V+145fbaxiCVMzHZjqtTf1+UCFKoQ+z
+ P5jrw6EfyGZAG27FJzuf27tgmUx2ebv1ycbWwIqka7IxwXlHEC6cjs3ztmN837+ELnvCm6jGfv3s63apEGLv+Dfg5z7FwLa9vqVHt4Pb
+ O1//+XYdmUy2ltHner63VhaUgignmo7w2U+t5ba9tHV13HHmk7TXHK+/pYQ34Xn9NlNkVIQQ+8d/Awts7ouE7FdYVtvbNJgddEKz6She
+ e+/L0Fn89Hd/aYsQQqwJfA1Ojx+FoACw2aNgNfFC6VosJzZzgrTG59F5BxCfDb040NhSkDHx1/Dj33zVLhVC7BE8qfB+u+THirB9r2/p
+ 6BZgJ3+4+eaPP9fb70JsDO40cRPY98/WapESS0eblny287Yw7P8otHGA11wN/PeBwt797Z/bpUKIPQH91/lsY+gLYEu+Ri2WFf0/9C3s
+ bGJHExdAfAoh9gPfJKYgCFaLlFi6dvOitNY0t+wgmU9i4wGsBghooVyXUfnuL7/Ud04LsSPwxCJ1w4gnc0tAGb2+JRAKb2Y0O8JCZT6F
+ 2BcI0LcDwaOvRuetgBdOR/w8NvWfL21Ho73OeMNTi/AoPpTdmAlsCHYY6qXYI8Q24HE7BKbNetLQDyx59E5iedHvQ98C2MmgQ9BXZAix
+ T7rB4CZwBwdeSkowXbvlhGmNz37eAYkx4fY6qwlEaCjfXscJwxfY07795IvV/t/689rTOZ+P/v8RPs+dDn2e8z9/YnPIaohPgLJ6fcvt
+ TpoZTYcw9/V6IcS6IDtkfTU6cAVS4ulo05LPdt6l7Ki01xmTE7XBi3bhGqaF/clksj0Z/H/pY3dLLDf6e+hbug/tDvUIRIj94v21lQvL
+ SAmnM5gXprWm/Hxg4jV2KwzXImT1cyKU89aaymSytDU+gqfhtcdmo+xe3xJ3iH/iDCHEfon+Gn023kFWwIqmo34em+bmrWEo/+jYa2zl
+ uIAgh7fhEfDC+DMIxJxorDmVlbeVpvOmRzBTV3zjyj//+1er/VBE2FerNWP84gcJUCF2j/fXVi4sIyWizmQpkbrk8xXQu8YUF4QQFbgb
+ v0IH0/zTKmEhxH6J/hp9Nt5BVsCKp6NOSz7nlte0a8FeY4oLQogKsF/BNMYvfggL1NEIsWdu/ZV3kBVICakzmReoc6ewK6F3jSkuCCEq
+ cDd+hQ5Gd7pCHIHor8Zna0DxZIXU0T6PTceW1bBrQnFBCFEZ9iuYhr6lp0bV0QixazrHpc/WICWmzmgpsVr6+cpQXBBC1OZu/AodjO50
+ hTgC0V+Nz9bAiqmjTks+l6w7x64RxQUhRGXYr2Aa+paeGlVHc1qePn168+KLLyKY3nzwwQftXLE3Oselz9YgJarOal6sjk2vFMUFIURt
+ 7sav0MFsf6f7+uuvB/Hz1ltvtXPu8uTJk7AO7JVXXmnn7gMIONZt6BjGeOedd7pyPvnkk3buMG+++WZYH204F9T/pZdeCuWcVYDac2it
+ 9DxcguivxmdrACFlRdURP49NS9eZYtfMTuKCEOJ6YL+Caehbemp0w46GAvTRo0ftnD4QAVgOe/vtt9u5+4H1HzqGEiCCXn755VDO48eP
+ 27nDTF0/BdqX5ZxRgD58+LA7fylbclNRk85x6bM1SImrM1tKtNrP//jzOu2+Y/YSF4QQ18Pd+BU6mG3vdPFLGEMZUCynOHrjjTfaufuB
+ GUjaEgEKKIZKMpo2Y/ofz+f/esGZM6C2Df25w/XIZUsEfi2ivxqfrUFPXB10WvJ5yvKcnUB8BnYQF4QQ1wX7FUxD39JToxt2NEMZUIrP
+ vT12BxBrqBvs1VdfDdOl2TJb5pgYHMscl3LWDKgV3rnzZkXo1o/jO8elz9YgJbTObl6UYnoW8dmwl7gghLge7sav0MFsf6eby4ByPsRd
+ CXacKM1nEpmxHBK0Nuuay3z5dShUUkMEbJZ0TMTYcoeEpR2zmBKNbDtruWNmWRBjviy7n1zdc+ePlJyXLbDZzxxWpG6dBY3+any2Bl50
+ HfHz2HTqOt5OJD4DO4kLQojrgf0KpqFv6anRDTsaChgr3qaINpASXNZYBqZj2T7Mx3IIttyjbe6PwwIoQFMijMtgJccCocP9Q5CmoHjy
+ 4twPCUiZr8NQm3AZRFiu7myLlGAuPS8ljJVFG2o3C8vDdIgHDx4Urbc2nePSZ2uQElwyI1BPJj4b9hIXhBDXw934FTqY/WVArWD7+OOP
+ w7whrDDxAiolMvz+PBRxuReeUm+rc+xmSoRx2ZCIsyDrRkH4WXM8KTHFY/BZOW6bOjaWCUFlGcuAYn6JAPX75HxY7rz4ugxhyxuykoy5
+ zTSPvdjG66E0E78W0V+Nz9agJ7YOOi35PGV5t+x84jOwk7gghLge2K9gGvqWnhrdsKPJCQsvWlLYx7s5gYSMmF2OcvH//fv3k+LOr2+x
+ 4tDWj6I5J2qngswqykPbeN5///2wLFfHHFgXdcdx2+043x8TsMty++L5s+K75Lzcu3dvcPna8DzjhmIIZqR9u12aznHps2cnJxyXmBWk
+ J2UvcUEIcT3cjV+hg9n2ThcBPSdAS8TcWLYSsHxmC232ywsuZjdTb9x//fx5t53fX0k9pmAF3LNnz9q5Ee5r6rcC2EynFVKYn/siem6D
+ 406JL7Ql29eerznn5ZLgWHgux/afG+5waaK/Gp89OznhmJqWLqOdmR3EBSHEdcF+BdPQt/TU6IYdjRcwzCbCxsRBTrymzJbFfXiROySK
+ hr4iKVfeEnICiZlDLxYttg1TZsWkFWNLMqBWbM49L5eEGdCx/VOAKgO6M6xgrGkn59JxATexj3/z1c33f/X85ls/+Vwmk13YvvvLL29+
+ 8NHzm18/m/91jmPcjV+hg9n+TndMwAwJrblCh2MbkfmjqGC2L5XpGnukXDsDCigicYyE9chl43gMQ+azmWwLLPNjbrkMNvZClhXfc8/L
+ JaGwHjtnPLfKgO4MCsZcJnPKZ0zPOubTc6G48NPf/eXm20++MNe1TCbb2iBIcUNYm1j+ATKghAIBlss8UaT5bUvg280UQMx0pcqaIqhg
+ NbJlEN4sjyKcYih3vGwPmN8//k9lMyEyOR/jSy12We54UueP9cjVcw6l5wCZzZK2L327nfsdW29tOselz54dishaJgJrxwVkPF97r0B4
+ vtvWQVNNNV1vmjEI0Q8//WvrtctBmf34FXa0nwyoFytWMCH7lBIVQ2M2x+C2fBOb9UhlXLms1Gply3zboD1gOYHF9oJw9Nhspt2e87Gd
+ P/ahZcCeI5tJXHJeUtixpmNW2vasIyzXnjh+rrNVppZEfzU+e3YgGm0Gc+5Umc8+K8YFPOL7m3+N5SYNQZHG/zXVVNPLTBOGJxU1iOXF
+ /YS+padGG9sKCovUo1CbBcR6HggECqCUQBqD4wCxLYTWXMG0RsYPUCShnl4wp7DH48kJLisiU9sNjZXEPJZpj33pebkEto6588bzCsuJ
+ 1EvROS599uxAQNYw0WOtuADxmQx0zby//bfPwhhQBLuaWRchRB74GgzjP19778usEK3xSB7l9ONXKHzbDCiCus/yeaxwSq3Dx9KwlEhC
+ +TkRZLfNbV8CX1BKiWi7j6kixook2lAd2ZZerNs29BlQ+60AqbJz9bdlwvyxW/E29bxciqFryx732DjRSxD91fjs2aGAZDZzzmdxlxXi
+ AvoYiMzuGm4DXe3HfEKI+cAXe0K0neKpxdIXlKLvx/JC39JTo41txZgABSVihstThixeCgigsXVKYP1Sx2DrPieLZrcfK8Mejze0Ecc9
+ wqz4Q+aX89EOCBgE+/MimIbH3RRqqWMvOS92X1vg29db6ri2oHNc+uzZoYicayLJGnEhNebzO7/of72cEGIf/PDpn+5kQyFCl8RqlNGP
+ X6HgbTOggCJlLMtkxUxKhKayhbl1LaX7H2JIhHGZzzyWYt/AL6kj2oHr09gGVqD67CPbLjeG0rct2g0wi5gTanPPyyXJ1XFPRH81Pnt2
+ ICKZzZw6FXkqxwU8Vr+9dhtrApvEpxD7JojQ1l/pu3hUP5dYRiwr9C09NdrYWaEA9YJMiD3ROS599uxASM4xMUjtuIDH7CwLwQyP4oUQ
+ ++d7H/6xJ0CXZEGxfT9+hUK3z4BuCbOLtd7WFmItor8anz07FJTMapZ8FuNUjAvhxSNet20g05hPIY4BxKb13SX+G7eP5YS+padGGzsj
+ fOy6p8fBQqToHJc+e3YoLktNFFEzLvjHeMiGCiGOg8+Czh0+g2378SsUeL4MqH97W9lPcQSivxqfPTsQlcxsDk1hopyKcQHBqrtumyC2
+ ZAyZEOLy+DHcc28i4/ZRfIa+padGGzsLVoDyRRoh9k7nuPTZswOBWWJiEjXjQnj73WRPan2p9SXBi6NNswTb03sCfHqndxfKmNteez3/
+ l8I/hsc40Dlg2378wp/mnzjzPAJUiCMS/dX47NmhwGS2M/VZTKdiXOh+9agNXku/S3AL8A0ZL7744u4EiAToNOa2117P/yXpbiLb6RzC
+ 9q34DH1LT43OLFQIcRk6x6XPnh0KzZRJfM6mZlwIXz5vMqBHxH4P8p4ECH+h7tpE0fvvvx+OC8c352sLc8xtr72e/0tSw4+xXT9+hcKU
+ ARXiCER/NT57dig07VTiczkV44LPgB6RPWbAKIpQr2sTRfxmmtz3Uc9hSXvx/F9jW5dSw4/D9q34DH1LT43OLFQIcRk6x6XPnh0KTm9i
+ ETXjgjKg63HtGdD79+/fPHtW7wcL5raX/ZGSswrQFTOgnHnM8TlCnAX6Kxw4+OzZoeC02U+xHHuNNZ+XcE0Z0LEMGNZrWu+OlTxGnrPt
+ UEaPv74H43K7j1y5/FEW+6t2EIEUYNyO/1srOc4h/LfTePPls67WIDJz+PYqPYZSAWp/sZB2LS85r5IBtTNg7/72z+2qQog9wS/0pigI
+ dnYoOiU+qxKvsdu4sIRryICWCJCUGLKW+5liMHfbXEbPCjkrqPCZx5ETiykBCrgv/BQ0y07Zku/TLhWgVlznLHV8pceAelhqnP9cex+F
+ 9TKgXaF/CF84KoTYH90XegfnpQOfHIhOZj9FPdrrTBnQyJgAsYLICzArqlJCcsm2qJPPgNptfF1xHFgf240JUIg08vXz593x0/z2dvlS
+ sYV6o5zcGFCej1ybYFschyV1DJhnyR3D2Pm34tMv5zJfn6MRBGjoF6LNIW4b+4GYAQWY2XYO6Czm/s6nEGI9Xnvvy855u5vGs6PM5yoo
+ A9pnSIDYZbnsnxWFXti89NJLs7YFPgNK4ZYrz9Y1JxIpmHIZUNQ3ty3XSQnDKfA4UN5UUDccI7b140dZP4jwsWN49OhRO2f4/NvH7mNl
+ 5pYfgXUyoAAfTMFzf2ZJCLEOt9lP8wheiLUI11qdDGgvc9LEmSMyJEAoEMfe2E6J1CXbclwmM6CoI9aB2eylhceRE5HICKYEKJJSrEOu
+ bIBtsM7SXxZc8hY8jxFmj3HqMdiM5dD5ZwZ7qEy2ae4m4wisMwYUhJm3BcO+/6vnyoQKsQMgPjvRabJS0XmFqE8vS9HYEmo8utuaIQHy
+ 8OHDMH/sEStEGdazwm7JtoCZNWt+HQsEGY8jl42jWLIZQMB9+eO3UFBj3SVvr2MfKAcCdChrSLGYM7/t1GMgtt38tmyvEjuyAF0vA9rQ
+ BTizAyheZEPxU2q+E5HJZOsYfO3bT74Ivoff3L1ddpuRat1WiHVw19sSrn0MKAXIkPADXM9mypZsC1gna0PZRxwH1sF2YwLU1yl3/JbS
+ jO4YYxlQHseQITNsRbAdAzr1GLA/lIf5+Gxhe5XY4QVo6BeizSFuG/uB0LdYUiJUJpPtwyQ+xaWI19ptHFhCjczJ1gwJUGYxxx47p4Qd
+ H9/O2RbYjJ7N0uUErV1nTIAuyYCOZS7HsAI0VQ6OD8thfvnQMU7NgFLADp1/1iXX5tfCqhnQDszkTiREZbIdmDKf4sK4624J154BLcn6
+ cbwm1ps6BjS3rZ3POmGK//26hJk8jhv1WPFms602eziUxaMYe/DgQTtnHmMZUNbFZyMBjxHreAE65RjsTUHJ+R+7iTg6640BJWHh7Q4k
+ RGWyLc36n/FBIVZGGdA+QwLELoMYSUGRArOiaMm2IJXRs9nBlMjkNikRhnnc1mf0uB2mKVA3bjsk8EpAvVmWP2aQOm5S0l5Tj6H0/Kfq
+ cy2smwFtC+UdL6cYh4YXkj789K9tEUKINfnp7/5y84OPnofv5A1+2Do9fTKYEGsSrjNlQC0UGSlxZUWfX24FUepN6SXbok6pbKYdl+hF
+ mP3eUbvM7gvmBSiPH+YzkxBhuWVzsOX5egAeH6YWewypN/2XHMPQ+R/6LleAeh5dnK6bAQ2F0n5/880ffy7RKcTG4Jso8P2ft+Iz+mcw
+ IVYiXm+319oSriEDCvCItWmaYKkMmhV9KUsJKTJ326FMoM322ZdxIMqsELMG4UUx5ffJ8qzYSpkXfXPB/nNl43j9MhraEkMA+L9tmyXH
+ sPT8p7Y5EutlQNsCGeQgPoUQ+wFPIqyPBgcWYi3aa4zX2xJC4ArlNdYEsCND4ZbL8tksmrUS7KNcazlBxzGgufGctryhjB4NAgowi5gb
+ A4pyU8e5xhhIL0JtW6TqwOyjFai2bXAMMJSTau+xY+D6Q+fflwlLZUWPRs+PZ/YJcdsoPkPfAqwixWdlPoXYF8iEfuNHsQPoRKgQK2Fj
+ AmwJNQKX2B5mD1NiV1w/K2dA492ufgVJiH2CcaG9u8fOgYWojIkJ+LyEaxkDembsrwhJgJ6TdcaA8kNY8PsQ5IQQ+wNPJqKvtnePMCFW
+ wF9nS6iRORHbowzouVknA9oWxrtdPX4XYp8gC0F/7W4ahVgDExPweQnKgB4fjgHFm+WlAtR+T2mpDb2oJbZlnTGg/NAWqt9/F2K/ROel
+ NT4rxArEmNBeY40tQRnQ62BOBlQC9HpYMQOKf+IMIcR+uXXg1m+FWIP2+uK1tgRlQIU4PiuNATX/zCxUCHEZvL+2ckGIqsTrK15rS+OC
+ MqBCHJ/VMqBdMGtmCiH2C/2081sh1qC9zhgbltAbO9YEMCHE8VglA9pTozMLFUJchs5x6bNCrEDNuNAToAvLEkJsw6pjQONMdQ5C7Jno
+ r8ZnhViDinFBY0CFOD41biTjtrEfCH1LT43OLFQIcRk6x6XPCrECNeNCjcyJEGJblAEVq2F/V1dfNLxfor8anxViDSrGBWVAhTg+Vz0G
+ 9PXXXw/iZ+h7wN5///1OJOE7yS79naUPHz7s9l9in3zySbtlOe+8887k7VkvtOFcsC/91NotvB63uM6G6ByXPivECtSMC8qALqMkNm7N
+ 3NiBuIPt5mw7hSdPnnT7mROXxRVnQBHg6WSPHj1q5/axGbrcOmvz5ptvdnUosTnCBceJX5vA9o8fP27n5sE+6Pwl6+ew+z2zAOV1SHv1
+ 1VfbJfsg+qvxWSHWoGJcUAZ0PhROJf2QvWkuxSYerE0pw/7i0dTYccknb4zf+rL7eVz1GNCxuzxe4A8ePGjn7BPUH/XE8cxlShlzMqYp
+ zp4BTXXCMHT8e7pj7hyXPivECtSMC8qAzodxEf18Dq5DK+2zSp7olfZ9c34lCQzFHfyP+Si7Rh/M8qYcl7jlqseA0ol8dtNm+PaWjfLU
+ upuzQw3GymG7vf322+2cebDuU37r91qwHTivMQr7vV1z0V+NzwqxBhXjQi9z0gQwUcbQY2Mr3LyV9Fn2aV4qdqD/mxJT1kheTMn+llIr
+ Xp6RU44B5fzSRwJWvNFQhoV3figTjw9y0KlKH21jPayfchjr8CV3X/fu3QvrDj0uGHtsboUV7f79+8n92w7Nl2X3k6t77vyRkvOyFTg+
+ HLs9NgrQWnfftegclz4rxArUjAs1Ht2dEcaMN954o51zC+ZhGYyx0d40D/VZ6N+5bS3BiDrULA+wnqWxv4S99utH4BQZUHtnQgeEOBgS
+ iiQluKzxghsTbmDO3deQYOVjdVuPIUoycLl1rNjNma/DUAZ0TIDaMbxegNplOZvSEYyVRVvaaZW0/xZEfzU+K8QaVIwLGgM6HTuuMhVP
+ eNNsKe2z2IfWepcC/bcXoKy7tVw/z3W5LY8jZ76cVLzL9f8UtTC0oSjnVBlQK9hyItFihYlfn8swJRhLink5J+RFncvoeazTpBxtqgBl
+ B4P1kT1MwePyHRS3TdWdmVXbFgB18h0B4TKI0FzdWRffnpyfKpfLfF2GsOUNGTrhknbOYTvzkpufS9E5Ln1WiBWoGRc0BnQ6Q/1xDvZZ
+ uadcALEB60wptwQKUCSQWH7KUEePF69TBCjbKGepY+T+UnURea42A4oAnxMWJU5S8hULXkDxkTAERupt9THB5WH9a44tGRJoJcecAh0Q
+ jssfNzsmLPNtbpfl9sW6WtFr6/jVwHnB8inHcAmsAN0T0V+NzwqxBhXjgjKg07F9ZyklfZZ/smcFqbWp/bEXgh673JaNz1zm4w7+x/zc
+ 8Vih6uuLZamhCyAVq8Q4NYbSxG1jPxD6lp4anVloDXhReCu5SJitHBJ/zHjabGHuwueFnbuAPXPF4Bh0wFS5PObSOhLr8LZMO9+3R24b
+ C8+fPQccazv0qIdjmVKPmbaE1wDulnPHvAWd49JnhViBmnFBGdDpWDFZ2v+U9Fl2nbGhWlMEGrOKsNy+uY4vl/NzAhTLU6AcLJ+a9Jkb
+ O8/OKcaA8uLkxQUbEyc58ZoyWxb34R2C5ZWKIq5f+4K23wDg65ITi5axDsZ2FGMZUMyH5R5H+/MH5p6XPWADwJ6I/mp8Vog1qBgXlAGd
+ DvvuKf1PSZ/FdWipmMXYAhuKLwRxhCJySAwy3vp9Yn8wvy+frfXYYykV6YD1QHwS5ZxiDKi9gK2AGXKEuUIHworOxguY83J3XR77CKPE
+ WaeSchbsB/NyjmmPa8is0+Izt/HHYZflHJ3n4BoFaO6Yt6BzXPqsECtQMy4oAzqdOSKppM+yos321Z5ctjJHLotp4b59bM1ta+Nc6nhS
+ ca6kvXL1EMOcYgyov+DtBZZzKt4tljqLhfulAOLFWZrW52PmtdL5qe8EHTtem/n0GUs6Lcy2J+a/+OKLYZuPP/64nRuhyPbbEPumu223
+ JeclRckb9bSlnYvtzPdE9Ffjs0KsQcW40Bs71gQwMQ77zhJBRUr6LK4DGwJ9NtYpiWuIMYzTJQLU1g/xJCdAxzKgJBcTcnXZa9++d3p+
+ PLNPiNtG8Rn6lp4anVloDXgRebGCC5QXNy6YlADiBTVHBHJbOjrrMeRIxN6BrZnB821Dh021Bcg5NLDtabe38/1b93ZZrl243J6/Jecl
+ R66z8YZrJfVyWSm2k8q18xZ0jkufFWIFasaFGoHrbFAATul/Svos9N9YBzZUro+LYwzFHJKrX25b1hXLS9vAx4dUfdi2pccmIlebAQW8
+ cFKZR+s0KTFjheCQA+TAtsj+YVtMSwWTdag1sfv5p3/6p2w7ELYF2sXDsmCpt+Bhvg3h/Pz6ppTQtmXa87f0vGzJpc7tVKK/Gp8VYg0q
+ xgWNAZ3OnP6ndJuSPnlqBpZlDiViWKaPXbl4VZoBTUE94RNaYE52WZzse0A9VuSk1uFdDSzlBLjocw5nt81tn4KOk6uzhRc9rPRujlgh
+ RxuqI44V63gHs20I8/XgnWiqbNtGdjtfpm+LsfOCbyfYozjlcU25+74EnePSZ4VYgZpxoUbm5GygT2xOQ7DS/qe0z2KfnBNgNt6UxkLG
+ DkxTDJXJbX0cmNMGhMfo4xEY0xoizdVmQHFxlVwUvKhgKcdgGTnLOYe90EvvtqzwKnEOW/epzgSsgB0rw34tlDe0kW0n6/R2Pr7M2GdI
+ 8RY8l1tDm7F+Qw6fM7+vPcDzO+fue02ivxqfFWINKsYFZUCng/62OQvBvDDLUdpnWTGIvtli91va9yEWUUSmthsrk3Xxx2m3Sz0ZZczx
+ WsDGv5ROmCquReR0b8GnsGImdQFZ57I2drGx3NK7Iu5jrL5kqQC1Irlkn6l2YBvYsrzT81E7OpTUl8dzOY0dGDu/XN3mnpet4PFAHOe+
+ emoLOselzwqxAjXjgjKg86CoK+0jrQAdizE2BuRsSpxiXX2ixFuqTG7rYxGwcdOXkYon1lLD1GwcSu1P5LnqMaBbQwGqi1LsmeivxmeF
+ WIOKcUEZ0HlQzA2N97fMeWpjEzq00qQK4VvwMIjDVLIhdwxYf0iAAi9CrYjFZ7uMlhPtc9pIRIIADf1CtDnEbWM/EPqWnhqdWejRYcqe
+ 2Twh9krnuPRZIVagZlxQBnQeNktpRZeYz9QnneIWZUBXAGMPebe218fBQpDor8ZnhViDinGhlzlpAtjexnvvGQomxabl2LGhEvTToQ8v
+ 6RNiGVF8hnJiYee7O2Uqnoa3sYXYO53j0meFWIGaceHvfvZFVw7sp7/7S7tEjMEsaMm4TjEMhzRMHWIgIjb7ic9zwLb9+BUKPF8G1ApQ
+ iU9xFKK/Gp8VYg0qxoXv/OKZuW5/f/PP//5Vu0SUoMfGy9FwhmV8+OlfO/9Fn/DNH3/eLplGLCOKz9C3xA6mzp2uEGJdOselzwqxAjXj
+ wvd/9TyW02ZQIEiFEMeh8+HWXnvvy3bJNLBtP36FAs+XARXiiER/NT4rxBpUjAs2exJM40CFOBTdN1m0wvGHT//ULpmGLSP0LT012pgQ
+ Yr90jkufFWIFaseFb/zos07Mwr734R/bJUKIPfODj26zn/ThuTeQt2UwfoWClQEV4ghEfzU+K8QaVI4LNojR3v3tn9ulQog9gqcXt9nP
+ aEtuHmMZJgNqZ8B+/ezrdlUhxN7oHJc+K8QK9K6xxmrgs6Cwx7/RC0lC7BFowZ74xBjuhcNnUE4/frWFs2PQXakQ+wQdAv1VAlSsSnuN
+ MS7UAF+/hLK8CMULDhoTKsR+uPPEon2BcO7YTxLLi2WFfqD7EBZobI4QewU3h53wbDuEVi4IUZUYE9prrLFa4CuYYpm3ZcOQafnuL78M
+ j/yEEJcHCQ4IT//InbGmhjZEOb2+JRB21MxodoSd625UiP3BL/TuZZCEWIM2JvBaqwmyKL1rOGPf+snnp7ZvP/niYp9r/5/6vLep/3zW
+ //1vvHcG4VlRfIJYdiwz9AFdZ8OMSmP6njYh9gWCtnXe4K9CrEQvS9FYbfA4HvsoEaIymezCRj3YTJc+dregzF7fEgg7bWYYEapxOULs
+ A3QA3WMR46PReYVYgXCNrfvtKIgv4Quu7TUtk8m2t8YnkfWsrQFj+dHfQ99CujtR0xkgNYu3FDUuR4jLAsfHmE/84gT9sfNNTIVYkV6W
+ orE14bWOJ2/hRste55oee3oUKz2ea5zSmv8Rb5DwWOvbkLCfuxlQ4rKgMplsR0bfFGJtwjW3bgZUCHEu2K9g2suAdmAFBDoJUZlsPxb8
+ sTEhLsAlM6BCiHOAviSfASVtpyMRKpNtb8xCtd4pxPqEa08ZUCFEPdivdHFtkLCyCYAymeyCBkdtTIgL08tSNCaEEEtBX9LrW4QQQoge
+ 7Q2QMqBCiFqwX+mEqBBCCGFRBlQIUZtOeCoDKoQQIkkQnsqACiHqwX6lE6JCCCGERRlQIURtOuGpDKgQQogkQXgqAyqEqAf7lU6ICiGE
+ EBZlQIUQtemEpzKgQgghkgThqQyoEKIe7Fc6ISqEEEJYlAEVQtSmE57KgAohhEgShOdtBlQmk8mWm8l+4n8hhBCihw0SIWjQbDCRyWSy
+ MeuLTmVAhRBC5DHBox84bgOKTCaT5S3etN69kVUGVAghxBAMJO/eDRxxejtfJpPJot3tL+7+39gLL7zw/wE+tyL1tSrk0QAAAABJRU5E
+ rkJggg=="/>
+ <rect v:rectContext="foreign" x="0" y="0.749996" width="322.588" height="113.77" class="st1"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/index.rst b/doc/guides/prog_guide/index.rst
index ed7f770..72d22ab 100644
--- a/doc/guides/prog_guide/index.rst
+++ b/doc/guides/prog_guide/index.rst
@@ -47,6 +47,7 @@ Programmer's Guide
link_bonding_poll_mode_drv_lib
timer_lib
hash_lib
+ efd_lib
lpm_lib
lpm6_lib
packet_distrib_lib
diff --git a/doc/guides/rel_notes/release_17_02.rst b/doc/guides/rel_notes/release_17_02.rst
index 5023038..4c94513 100644
--- a/doc/guides/rel_notes/release_17_02.rst
+++ b/doc/guides/rel_notes/release_17_02.rst
@@ -64,6 +64,9 @@ New Features
is much smaller than a hash-based flow table and therefore, it can better fit for
CPU cache, being able to scale to millions of flow keys.
+ See the :ref:`Elastic Flow Distributor Library <Efd_Library>` documentation in
+ the Programmers Guide document, for more information.
+
Resolved Issues
---------------
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v2 5/5] doc: add flow distributor guide
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 0/5] Elastic Flow Distributor Pablo de Lara
` (3 preceding siblings ...)
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 4/5] doc: add EFD library section in Programmers guide Pablo de Lara
@ 2017-01-07 1:06 ` Pablo de Lara
2017-01-09 18:19 ` [dpdk-dev] [PATCH v2 0/5] Elastic Flow Distributor Maciocco, Christian
2017-01-12 22:15 ` [dpdk-dev] [PATCH v3 " Pablo de Lara
6 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-07 1:06 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Sameh Gobriel
Signed-off-by: Sameh Gobriel <sameh.gobriel@intel.com>
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
---
MAINTAINERS | 1 +
doc/guides/sample_app_ug/flow_distributor.rst | 492 ++++++++++++++++++++++
doc/guides/sample_app_ug/img/flow_distributor.svg | 417 ++++++++++++++++++
doc/guides/sample_app_ug/index.rst | 1 +
4 files changed, 911 insertions(+)
create mode 100644 doc/guides/sample_app_ug/flow_distributor.rst
create mode 100644 doc/guides/sample_app_ug/img/flow_distributor.svg
diff --git a/MAINTAINERS b/MAINTAINERS
index 66e9466..0d3b247 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -535,6 +535,7 @@ F: lib/librte_efd/
F: doc/guides/prog_guide/efd_lib.rst
F: app/test/test_efd*
F: examples/flow_distributor/
+F: doc/guides/sample_app_ug/flow_distributor.rst
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
diff --git a/doc/guides/sample_app_ug/flow_distributor.rst b/doc/guides/sample_app_ug/flow_distributor.rst
new file mode 100644
index 0000000..39820f0
--- /dev/null
+++ b/doc/guides/sample_app_ug/flow_distributor.rst
@@ -0,0 +1,492 @@
+.. BSD LICENSE
+ Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of Intel Corporation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Flow Distributor Sample Application
+===================================
+
+This sample application demonstrates the use of EFD library as a flow-level
+load balancer, for more information about the EFD Library please refer to the
+DPDK programmer's guide.
+
+This sample application is a variant of the :ref:`client-server sample application <multi_process_app>`
+where a specific target node is specified for every and each flow
+(not in a round-robin fashion as the original load balancing sample application).
+
+Overview
+--------
+
+The architecture of the EFD flow-based load balancer sample application is
+presented in the following figure.
+
+.. _figure_efd_sample_app_overview:
+
+.. figure:: img/flow_distributor.*
+
+ Using EFD as a Flow-Level Load Balancer
+
+As shown in figure, the sample application consists of a front end node
+(distributor) using the EFD library to create a load-balancing table for flows,
+for each flow a target backend worker node is specified. The EFD table does not
+store the flow key (unlike a regular hash table), and hence, it can
+individually load-balance millions of flows (number of targets * maximum number
+of flows fit in a flow table per target) while still fitting in CPU cache.
+
+It should be noted that although they are referred to as nodes, the frontend
+distributor and worker nodes are processes running on the same platform.
+
+Front-end Distributor
+~~~~~~~~~~~~~~~~~~~~~
+
+Upon initializing, the frontend distributor node (process) creates a flow
+distributor table (based on the EFD library) which is populated with flow
+information and its intended target node.
+
+The sample application assigns a specific target node_id (process) for each of
+the IP destination addresses as follows:
+
+.. code-block:: c
+
+ node_id = i % num_nodes; /* Target node id is generated */
+ ip_dst = rte_cpu_to_be_32(i); /* Specific ip destination address is
+ assigned to this target node */
+
+then the pair of <key,target> is inserted into the flow distribution table.
+
+The main loop of the the distributor node receives a burst of packets, then for
+each packet a flow key (IP destination address) is extracted. The flow
+distributor table is looked up and the target node id is returned. Packets are
+then enqueued to the specified target node id.
+
+It should be noted that flow distributor table is not a membership test table.
+I.e. if the key has already been inserted the target node id will be correct,
+but for new keys the flow distributor table will return a value (which can be
+valid).
+
+Backend Worker Nodes
+~~~~~~~~~~~~~~~~~~~~
+
+Upon initializing, the worker node (process) creates a flow table (a regular
+hash table that stores the key default size 1M flows) which is populated with
+only the flow information that are serviced at this node. This flow key is
+essential to point out new keys that have not been inserted before.
+
+The worker node's main loop is simply receiving packets then doing a hash table
+lookup. If a match occurs then statistics are updated for flows serviced by
+this node. If no match is found in the local hash table then this indicates
+that this is a new flows which is dropped.
+
+
+Compiling the Application
+-------------------------
+
+The sequence of steps used to build the application is:
+
+#. Export the required environment variables:
+
+ .. code-block:: console
+
+ export RTE_SDK=/path/to/rte_sdk
+ export RTE_TARGET=x86_64-native-linuxapp-gcc
+
+#. Build the application executable file:
+
+ .. code-block:: console
+
+ cd ${RTE_SDK}/examples/flow_distributor/
+ make
+
+ For more details on how to build the DPDK libraries and sample
+ applications,
+ please refer to the *DPDK Getting Started Guide.*
+
+
+Running the Application
+-----------------------
+
+The application has two binaries to be run: the front-end distributor
+and the back-end node.
+
+The frontend distributor (distributor) has the following command line options::
+
+ ./distributor [EAL options] -- -p PORTMASK -n NUM_NODES -f NUM_FLOWS
+
+Where,
+
+* ``-p PORTMASK:`` Hexadecimal bitmask of ports to configure
+* ``-n NUM_NODES:`` Number of back-end nodes that will be used
+* ``-f NUM_FLOWS:`` Number of flows to be added in the EFD table (1 million, by default)
+
+The back-end node (node) has the following command line options::
+
+ ./node [EAL options] -- -n NODE_ID
+
+Where,
+
+* ``-n NODE_ID:`` Node ID, which cannot be equal or higher than NUM_MODES
+
+
+First, the distributor app must be run, with the number of nodes that will be run.
+Once it has been started, the node instances can be run, with different NODE_ID.
+These instances have to be run as secondary processes, with `--proc-type=secondary`
+in the EAL options, which will attach to the primary process memory, and therefore,
+they can access the queues created by the primary process to distribute packets.
+
+To successfully run the application, the command line used to start the
+application has to be in sync with the traffic flows configured on the traffic
+generator side.
+
+For examples of application command lines and traffic generator flows, please
+refer to the DPDK Test Report. For more details on how to set up and run the
+sample applications provided with DPDK package, please refer to the
+:ref:`DPDK Getting Started Guide for Linux <linux_gsg>` and
+:ref:`DPDK Getting Started Guide for FreeBSD <freebsd_gsg>`.
+
+
+Explanation
+-----------
+
+As described in previous sections, there are two processes in this example.
+
+The first process, the front-end distributor, creates and populates the EFD table,
+which is used to distribute packets to nodes, which the number of flows
+specified in the command line (1 million, by default).
+
+
+.. code-block:: c
+
+ static void
+ create_flow_distributor_table(void)
+ {
+ uint8_t socket_id = rte_socket_id();
+
+ /* create table */
+ efd_table = rte_efd_create("flow table", num_flows * 2, sizeof(uint32_t),
+ 1 << socket_id, socket_id);
+
+ if (efd_table == NULL)
+ rte_exit(EXIT_FAILURE, "Problem creating the flow table\n");
+ }
+
+ static void
+ populate_flow_distributor_table(void)
+ {
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint8_t socket_id = rte_socket_id();
+ uint64_t node_id;
+
+ /* Add flows in table */
+ for (i = 0; i < num_flows; i++) {
+ node_id = i % num_nodes;
+
+ ip_dst = rte_cpu_to_be_32(i);
+ ret = rte_efd_update(efd_table, socket_id,
+ (void *)&ip_dst, (efd_value_t)node_id);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u in "
+ "flow distributor table\n", i);
+ }
+
+ printf("EFD table: Adding 0x%x keys\n", num_flows);
+ }
+
+After initialization, packets are received from the enabled ports, and the IPv4
+address from the packets is used as a key to look up in the EFD table,
+which tells the node where the packet has to be distributed.
+
+.. code-block:: c
+
+ static void
+ process_packets(uint32_t port_num __rte_unused, struct rte_mbuf *pkts[],
+ uint16_t rx_count, unsigned int socket_id)
+ {
+ uint16_t i;
+ uint8_t node;
+ efd_value_t data[EFD_BURST_MAX];
+ const void *key_ptrs[EFD_BURST_MAX];
+
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[EFD_BURST_MAX];
+
+ for (i = 0; i < rx_count; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(pkts[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = (void *)&ipv4_dst_ip[i];
+ }
+
+ rte_efd_lookup_bulk(efd_table, socket_id, rx_count,
+ (const void **) key_ptrs, data);
+ for (i = 0; i < rx_count; i++) {
+ node = (uint8_t) ((uintptr_t)data[i]);
+
+ if (node >= num_nodes) {
+ /*
+ * Node is out of range, which means that
+ * flow has not been inserted
+ */
+ flow_dist_stats.drop++;
+ rte_pktmbuf_free(pkts[i]);
+ } else {
+ flow_dist_stats.distributed++;
+ enqueue_rx_packet(node, pkts[i]);
+ }
+ }
+
+ for (i = 0; i < num_nodes; i++)
+ flush_rx_queue(i);
+ }
+
+The burst of packets received is enqueued in temporary buffers (per node),
+and enqueued in the shared ring between the distributor and the node.
+After this, a new burst of packets is received and this process is
+repeated infinitely.
+
+.. code-block:: c
+
+ static void
+ flush_rx_queue(uint16_t node)
+ {
+ uint16_t j;
+ struct node *cl;
+
+ if (cl_rx_buf[node].count == 0)
+ return;
+
+ cl = &nodes[node];
+ if (rte_ring_enqueue_bulk(cl->rx_q, (void **)cl_rx_buf[node].buffer,
+ cl_rx_buf[node].count) != 0){
+ for (j = 0; j < cl_rx_buf[node].count; j++)
+ rte_pktmbuf_free(cl_rx_buf[node].buffer[j]);
+ cl->stats.rx_drop += cl_rx_buf[node].count;
+ } else
+ cl->stats.rx += cl_rx_buf[node].count;
+
+ cl_rx_buf[node].count = 0;
+ }
+
+The second process, the back-end node, receives the packets from the shared
+ring with the distributor and send them out, if they belong to the node.
+
+At initialization, it attaches to the distributor process memory, to have
+access to the shared ring, parameters and statistics.
+
+.. code-block:: c
+
+ rx_ring = rte_ring_lookup(get_rx_queue_name(node_id));
+ if (rx_ring == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get RX ring - "
+ "is distributor process running?\n");
+
+ mp = rte_mempool_lookup(PKTMBUF_POOL_NAME);
+ if (mp == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get mempool for mbufs\n");
+
+ mz = rte_memzone_lookup(MZ_SHARED_INFO);
+ if (mz == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get port info structure\n");
+ info = mz->addr;
+ tx_stats = &(info->tx_stats[node_id]);
+ filter_stats = &(info->filter_stats[node_id]);
+
+Then, the hash table that contains the flows that will be handled
+by the node is created and populated.
+
+.. code-block:: c
+
+ static struct rte_hash *
+ create_hash_table(const struct shared_info *info)
+ {
+ uint32_t num_flows_node = info->num_flows / info->num_nodes;
+ char name[RTE_HASH_NAMESIZE];
+ struct rte_hash *h;
+
+ /* create table */
+ struct rte_hash_parameters hash_params = {
+ .entries = num_flows_node * 2, /* table load = 50% */
+ .key_len = sizeof(uint32_t), /* Store IPv4 dest IP address */
+ .socket_id = rte_socket_id(),
+ .hash_func_init_val = 0,
+ };
+
+ snprintf(name, sizeof(name), "hash_table_%d", node_id);
+ hash_params.name = name;
+ h = rte_hash_create(&hash_params);
+
+ if (h == NULL)
+ rte_exit(EXIT_FAILURE,
+ "Problem creating the hash table for node %d\n",
+ node_id);
+ return h;
+ }
+
+ static void
+ populate_hash_table(const struct rte_hash *h, const struct shared_info *info)
+ {
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint32_t num_flows_node = 0;
+ uint64_t target_node;
+
+ /* Add flows in table */
+ for (i = 0; i < info->num_flows; i++) {
+ target_node = i % info->num_nodes;
+ if (target_node != node_id)
+ continue;
+
+ ip_dst = rte_cpu_to_be_32(i);
+
+ ret = rte_hash_add_key(h, (void *) &ip_dst);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u "
+ "in hash table\n", i);
+ else
+ num_flows_node++;
+
+ }
+
+ printf("Hash table: Adding 0x%x keys\n", num_flows_node);
+ }
+
+After initialization, packets are dequeued from the shared ring (from the distributor) and,
+like in the distributor process, the IPv4 address from the packets is used as
+a key to look up in the hash table.
+If there is a hit, packet is stored in a buffer, to be eventually transmitted
+in one of the enabled ports. If key is not there, packet is dropped, since the
+flow is not handled by the node.
+
+.. code-block:: c
+
+ static inline void
+ handle_packets(struct rte_hash *h, struct rte_mbuf **bufs, uint16_t num_packets)
+ {
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[PKT_READ_SIZE];
+ const void *key_ptrs[PKT_READ_SIZE];
+ unsigned int i;
+ int32_t positions[PKT_READ_SIZE] = {0};
+
+ for (i = 0; i < num_packets; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(bufs[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = &ipv4_dst_ip[i];
+ }
+ /* Check if packets belongs to any flows handled by this node */
+ rte_hash_lookup_bulk(h, key_ptrs, num_packets, positions);
+
+ for (i = 0; i < num_packets; i++) {
+ if (likely(positions[i] >= 0)) {
+ filter_stats->passed++;
+ transmit_packet(bufs[i]);
+ } else {
+ filter_stats->drop++;
+ /* Drop packet, as flow is not handled by this node */
+ rte_pktmbuf_free(bufs[i]);
+ }
+ }
+ }
+
+Finally, note that both processes updates statistics, such as transmitted, received
+and dropped packets, which are shown and refreshed by the distributor app.
+
+.. code-block:: c
+
+ static void
+ do_stats_display(void)
+ {
+ unsigned int i, j;
+ const char clr[] = {27, '[', '2', 'J', '\0'};
+ const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
+ uint64_t port_tx[RTE_MAX_ETHPORTS], port_tx_drop[RTE_MAX_ETHPORTS];
+ uint64_t node_tx[MAX_NODES], node_tx_drop[MAX_NODES];
+
+ /* to get TX stats, we need to do some summing calculations */
+ memset(port_tx, 0, sizeof(port_tx));
+ memset(port_tx_drop, 0, sizeof(port_tx_drop));
+ memset(node_tx, 0, sizeof(node_tx));
+ memset(node_tx_drop, 0, sizeof(node_tx_drop));
+
+ for (i = 0; i < num_nodes; i++) {
+ const struct tx_stats *tx = &info->tx_stats[i];
+
+ for (j = 0; j < info->num_ports; j++) {
+ const uint64_t tx_val = tx->tx[info->id[j]];
+ const uint64_t drop_val = tx->tx_drop[info->id[j]];
+
+ port_tx[j] += tx_val;
+ port_tx_drop[j] += drop_val;
+ node_tx[i] += tx_val;
+ node_tx_drop[i] += drop_val;
+ }
+ }
+
+ /* Clear screen and move to top left */
+ printf("%s%s", clr, topLeft);
+
+ printf("PORTS\n");
+ printf("-----\n");
+ for (i = 0; i < info->num_ports; i++)
+ printf("Port %u: '%s'\t", (unsigned int)info->id[i],
+ get_printable_mac_addr(info->id[i]));
+ printf("\n\n");
+ for (i = 0; i < info->num_ports; i++) {
+ printf("Port %u - rx: %9"PRIu64"\t"
+ "tx: %9"PRIu64"\n",
+ (unsigned int)info->id[i], info->rx_stats.rx[i],
+ port_tx[i]);
+ }
+
+ printf("\nFLOW DISTRIBUTOR\n");
+ printf("-----\n");
+ printf("distributed: %9"PRIu64", drop: %9"PRIu64"\n",
+ flow_dist_stats.distributed, flow_dist_stats.drop);
+
+ printf("\nNODES\n");
+ printf("-------\n");
+ for (i = 0; i < num_nodes; i++) {
+ const unsigned long long rx = nodes[i].stats.rx;
+ const unsigned long long rx_drop = nodes[i].stats.rx_drop;
+ const struct filter_stats *filter = &info->filter_stats[i];
+
+ printf("Node %2u - rx: %9llu, rx_drop: %9llu\n"
+ " tx: %9"PRIu64", tx_drop: %9"PRIu64"\n"
+ " filter_passed: %9"PRIu64", "
+ "filter_drop: %9"PRIu64"\n",
+ i, rx, rx_drop, node_tx[i], node_tx_drop[i],
+ filter->passed, filter->drop);
+ }
+
+ printf("\n");
+ }
diff --git a/doc/guides/sample_app_ug/img/flow_distributor.svg b/doc/guides/sample_app_ug/img/flow_distributor.svg
new file mode 100644
index 0000000..b35faad
--- /dev/null
+++ b/doc/guides/sample_app_ug/img/flow_distributor.svg
@@ -0,0 +1,417 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export Drawing1.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="7.0721in" height="4.43701in"
+ viewBox="0 0 509.191 319.465" xml:space="preserve" color-interpolation-filters="sRGB" class="st2">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="19" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape1-1" v:mID="1" v:groupContext="shape" transform="translate(18.375,-18.375)">
+ <title>Sheet.1</title>
+ <rect v:rectContext="foreign" x="0" y="36.75" width="472.441" height="282.715" class="st1"/>
+ <image x="0" y="36.75" width="472.441" height="282.715" preserveAspectRatio="none" xlink:href="data:image/png;base64,
+ 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
+ Dg//f9NW31MZe1iBAAAAAElFTkSuQmCC"/>
+ <rect v:rectContext="foreign" x="0" y="36.75" width="472.441" height="282.715" class="st1"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/sample_app_ug/index.rst b/doc/guides/sample_app_ug/index.rst
index 775e2f7..260f6a5 100644
--- a/doc/guides/sample_app_ug/index.rst
+++ b/doc/guides/sample_app_ug/index.rst
@@ -57,6 +57,7 @@ Sample Applications User Guides
l3_forward_virtual
link_status_intr
load_balancer
+ flow_distributor
multi_process
qos_metering
qos_scheduler
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [dpdk-dev] [PATCH v2 0/5] Elastic Flow Distributor
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 0/5] Elastic Flow Distributor Pablo de Lara
` (4 preceding siblings ...)
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 5/5] doc: add flow distributor guide Pablo de Lara
@ 2017-01-09 18:19 ` Maciocco, Christian
2017-01-12 22:15 ` [dpdk-dev] [PATCH v3 " Pablo de Lara
6 siblings, 0 replies; 63+ messages in thread
From: Maciocco, Christian @ 2017-01-09 18:19 UTC (permalink / raw)
To: De Lara Guarch, Pablo, dev; +Cc: De Lara Guarch, Pablo
> -----Original Message-----
> From: dev [mailto:dev-bounces@dpdk.org] On Behalf Of Pablo de Lara
> Sent: Friday, January 06, 2017 5:06 PM
> To: dev@dpdk.org
> Cc: De Lara Guarch, Pablo <pablo.de.lara.guarch@intel.com>
> Subject: [dpdk-dev] [PATCH v2 0/5] Elastic Flow Distributor
>
> EFD is a distributor library that uses perfect hashing to determine a target/value
> for a given incoming flow key. It has the following advantages:
> first, because it uses perfect hashing it does not store the key itself and hence
> lookup performance is not dependent on the key size. Second, the target/value
> can be any arbitrary value hence the system designer and/or operator can better
> optimize service rates and inter-cluster network traffic locating. Third, since the
> storage requirement is much smaller than a hash-based flow table (i.e. better fit
> for CPU cache), EFD can scale to millions of flow keys. Finally, with current
> optimized library implementation performance is fully scalable with number of
> CPU cores.
>
> The basic idea of EFD is when a given key is to be inserted, a family of hash
> functions is searched until the correct hash function that maps the input key to
> the correct value is found. However, rather than explicitly storing all keys and
> their associated values, EFD stores only indices of hash functions that map keys
> to values, and thereby consumes much less space than conventional flow-based
> tables. The lookup operation is very simple, similar to computational-based
> scheme, given an input key the lookup operation is reduced to hashing that key
> with the correct hash function.
>
> Intuitively, finding a hash function that maps each of a large number (millions) of
> input keys to the correct output value is effectively impossible, as a result EFD,
> breaks the problem into smaller pieces (divide and conquer). EFD divides the
> entire input key set into many small groups. Each group consists of
> approximately 20-28 keys (a configurable parameter for the library), then, for
> each small group, a brute force search to find a hash function that produces the
> correct outputs for each key in the group.
> It should be mentioned that since in the online lookup table for EFD doesn’t
> store the key itself, the size of the EFD table is independent of the key size and
> hence EFD lookup performance which is almost constant irrespective of the
> length of the key which is a highly desirable feature especially for longer keys.
>
> Library code is included in the patch, plus an sample application that shows how
> the library can be used.
>
> RFC for this library was already sent:
> http://dpdk.org/ml/archives/dev/2016-October/049238.html
>
> For more information on the library, check out the following document:
> https://github.com/pablodelara/perfect_hash_flow_distributor/blob/master/EF
> D_description.pdf
>
> Changes in v2:
>
> - Added documentation for library and sample app
> - Fixed checkpatch errors/warnings
> - Added functional and performance tests
> - Made key size variable at runtime
> - Made code multi-architecture compatible at runtime
>
>
> Pablo de Lara (5):
> efd: new Elastic Flow Distributor library
> app/test: add EFD functional and perf tests
> examples/flow_distributor: sample app to demonstrate EFD usage
> doc: add EFD library section in Programmers guide
> doc: add flow distributor guide
>
> MAINTAINERS | 9 +
> app/test/Makefile | 3 +
> app/test/test_efd.c | 494 ++++++++
> app/test/test_efd_perf.c | 407 ++++++
> config/common_base | 5 +
> doc/api/doxy-api-index.md | 3 +-
> doc/api/doxy-api.conf | 1 +
> doc/api/examples.dox | 4 +
> doc/guides/prog_guide/efd_lib.rst | 413 ++++++
> doc/guides/prog_guide/img/efd_i1.svg | 78 ++
> doc/guides/prog_guide/img/efd_i10.svg | 1272 +++++++++++++++++++
> doc/guides/prog_guide/img/efd_i11.svg | 413 ++++++
> doc/guides/prog_guide/img/efd_i12.svg | 590 +++++++++
> doc/guides/prog_guide/img/efd_i13.svg | 1407
> +++++++++++++++++++++
> doc/guides/prog_guide/img/efd_i2.svg | 209 +++
> doc/guides/prog_guide/img/efd_i3.svg | 420 ++++++
> doc/guides/prog_guide/img/efd_i4.svg | 364 ++++++
> doc/guides/prog_guide/img/efd_i5.svg | 578 +++++++++
> doc/guides/prog_guide/img/efd_i6.svg | 413 ++++++
> doc/guides/prog_guide/img/efd_i8.svg | 776 ++++++++++++
> doc/guides/prog_guide/img/efd_i9.svg | 271 ++++
> doc/guides/prog_guide/index.rst | 1 +
> doc/guides/rel_notes/release_17_02.rst | 15 +
> doc/guides/sample_app_ug/flow_distributor.rst | 492 +++++++
> doc/guides/sample_app_ug/img/flow_distributor.svg | 417 ++++++
> doc/guides/sample_app_ug/index.rst | 1 +
> examples/Makefile | 1 +
> examples/flow_distributor/Makefile | 44 +
> examples/flow_distributor/distributor/Makefile | 57 +
> examples/flow_distributor/distributor/args.c | 200 +++
> examples/flow_distributor/distributor/args.h | 39 +
> examples/flow_distributor/distributor/init.c | 371 ++++++
> examples/flow_distributor/distributor/init.h | 76 ++
> examples/flow_distributor/distributor/main.c | 362 ++++++
> examples/flow_distributor/node/Makefile | 48 +
> examples/flow_distributor/node/node.c | 417 ++++++
> examples/flow_distributor/shared/common.h | 99 ++
> lib/Makefile | 1 +
> lib/librte_eal/common/include/rte_log.h | 1 +
> lib/librte_efd/Makefile | 56 +
> lib/librte_efd/rte_efd.c | 1354 ++++++++++++++++++++
> lib/librte_efd/rte_efd.h | 294 +++++
> lib/librte_efd/rte_efd_version.map | 12 +
> mk/rte.app.mk | 1 +
> 44 files changed, 12488 insertions(+), 1 deletion(-) create mode 100644
> app/test/test_efd.c create mode 100644 app/test/test_efd_perf.c create
> mode 100644 doc/guides/prog_guide/efd_lib.rst create mode 100644
> doc/guides/prog_guide/img/efd_i1.svg
> create mode 100644 doc/guides/prog_guide/img/efd_i10.svg
> create mode 100644 doc/guides/prog_guide/img/efd_i11.svg
> create mode 100644 doc/guides/prog_guide/img/efd_i12.svg
> create mode 100644 doc/guides/prog_guide/img/efd_i13.svg
> create mode 100644 doc/guides/prog_guide/img/efd_i2.svg
> create mode 100644 doc/guides/prog_guide/img/efd_i3.svg
> create mode 100644 doc/guides/prog_guide/img/efd_i4.svg
> create mode 100644 doc/guides/prog_guide/img/efd_i5.svg
> create mode 100644 doc/guides/prog_guide/img/efd_i6.svg
> create mode 100644 doc/guides/prog_guide/img/efd_i8.svg
> create mode 100644 doc/guides/prog_guide/img/efd_i9.svg
> create mode 100644 doc/guides/sample_app_ug/flow_distributor.rst
> create mode 100644 doc/guides/sample_app_ug/img/flow_distributor.svg
> create mode 100644 examples/flow_distributor/Makefile
> create mode 100644 examples/flow_distributor/distributor/Makefile
> create mode 100644 examples/flow_distributor/distributor/args.c
> create mode 100644 examples/flow_distributor/distributor/args.h
> create mode 100644 examples/flow_distributor/distributor/init.c
> create mode 100644 examples/flow_distributor/distributor/init.h
> create mode 100644 examples/flow_distributor/distributor/main.c
> create mode 100644 examples/flow_distributor/node/Makefile
> create mode 100644 examples/flow_distributor/node/node.c
> create mode 100644 examples/flow_distributor/shared/common.h
> create mode 100644 lib/librte_efd/Makefile create mode 100644
> lib/librte_efd/rte_efd.c create mode 100644 lib/librte_efd/rte_efd.h create
> mode 100644 lib/librte_efd/rte_efd_version.map
>
> --
> 2.7.4
Series-acked-by: Christian Maciocco <christian.maciocco@intel.com>
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v3 0/5] Elastic Flow Distributor
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 0/5] Elastic Flow Distributor Pablo de Lara
` (5 preceding siblings ...)
2017-01-09 18:19 ` [dpdk-dev] [PATCH v2 0/5] Elastic Flow Distributor Maciocco, Christian
@ 2017-01-12 22:15 ` Pablo de Lara
2017-01-12 22:15 ` [dpdk-dev] [PATCH v3 1/5] efd: new Elastic Flow Distributor library Pablo de Lara
` (5 more replies)
6 siblings, 6 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-12 22:15 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara
EFD is a distributor library that uses perfect hashing to determine a
target/value for a given incoming flow key. It has the following advantages:
first, because it uses perfect hashing it does not store the key itself and
hence lookup performance is not dependent on the key size. Second, the
target/value can be any arbitrary value hence the system designer and/or
operator can better optimize service rates and inter-cluster network traffic
locating. Third, since the storage requirement is much smaller than a
hash-based flow table (i.e. better fit for CPU cache), EFD can scale to millions
of flow keys. Finally, with current optimized library implementation performance
is fully scalable with number of CPU cores.
The basic idea of EFD is when a given key is to be inserted, a family of hash
functions is searched until the correct hash function that maps the input key to
the correct value is found. However, rather than explicitly storing all keys and
their associated values, EFD stores only indices of hash functions that map keys
to values, and thereby consumes much less space than conventional flow-based
tables. The lookup operation is very simple, similar to computational-based
scheme, given an input key the lookup operation is reduced to hashing that key
with the correct hash function.
Intuitively, finding a hash function that maps each of a large number (millions)
of input keys to the correct output value is effectively impossible, as a result
EFD, breaks the problem into smaller pieces (divide and conquer). EFD divides
the entire input key set into many small groups. Each group consists of
approximately 20-28 keys (a configurable parameter for the library), then, for
each small group, a brute force search to find a hash function that produces the
correct outputs for each key in the group.
It should be mentioned that since in the online lookup table for EFD doesn’t
store the key itself, the size of the EFD table is independent of the key size
and hence EFD lookup performance which is almost constant irrespective of the
length of the key which is a highly desirable feature especially for longer
keys.
Library code is included in the patch, plus an sample application that shows
how the library can be used.
RFC for this library was already sent:
http://dpdk.org/ml/archives/dev/2016-October/049238.html
For more information on the library, check out the following document:
https://github.com/pablodelara/perfect_hash_flow_distributor/blob/master/EFD_description.pdf
Changes in v3:
- Fixed SVG files
- Fixed copyright dates
- Reformatted parts of documentation
Changes in v2:
- Added documentation for library and sample app
- Fixed checkpatch errors/warnings
- Added functional and performance tests
- Made key size variable at runtime
- Made code multi-architecture compatible at runtime
Pablo de Lara (5):
efd: new Elastic Flow Distributor library
app/test: add EFD functional and perf tests
examples/flow_distributor: sample app to demonstrate EFD usage
doc: add EFD library section in Programmers guide
doc: add flow distributor guide
MAINTAINERS | 9 +
app/test/Makefile | 5 +-
app/test/test_efd.c | 494 ++++++++
app/test/test_efd_perf.c | 407 +++++++
config/common_base | 5 +
doc/api/doxy-api-index.md | 3 +-
doc/api/doxy-api.conf | 1 +
doc/api/examples.dox | 4 +
doc/guides/prog_guide/efd_lib.rst | 428 +++++++
doc/guides/prog_guide/img/efd_i1.svg | 130 ++
doc/guides/prog_guide/img/efd_i10.svg | 384 ++++++
doc/guides/prog_guide/img/efd_i11.svg | 319 +++++
doc/guides/prog_guide/img/efd_i12.svg | 1008 +++++++++++++++
doc/guides/prog_guide/img/efd_i2.svg | 280 +++++
doc/guides/prog_guide/img/efd_i3.svg | 634 ++++++++++
doc/guides/prog_guide/img/efd_i4.svg | 203 +++
doc/guides/prog_guide/img/efd_i5.svg | 183 +++
doc/guides/prog_guide/img/efd_i6.svg | 1254 +++++++++++++++++++
doc/guides/prog_guide/img/efd_i7.svg | 790 ++++++++++++
doc/guides/prog_guide/img/efd_i8.svg | 182 +++
doc/guides/prog_guide/img/efd_i9.svg | 390 ++++++
doc/guides/prog_guide/index.rst | 23 +
doc/guides/rel_notes/release_17_02.rst | 15 +
doc/guides/sample_app_ug/flow_distributor.rst | 494 ++++++++
doc/guides/sample_app_ug/img/flow_distributor.svg | 1254 +++++++++++++++++++
doc/guides/sample_app_ug/index.rst | 1 +
examples/Makefile | 1 +
examples/flow_distributor/Makefile | 44 +
examples/flow_distributor/distributor/Makefile | 57 +
examples/flow_distributor/distributor/args.c | 200 +++
examples/flow_distributor/distributor/args.h | 39 +
examples/flow_distributor/distributor/init.c | 371 ++++++
examples/flow_distributor/distributor/init.h | 76 ++
examples/flow_distributor/distributor/main.c | 362 ++++++
examples/flow_distributor/node/Makefile | 48 +
examples/flow_distributor/node/node.c | 417 +++++++
examples/flow_distributor/shared/common.h | 99 ++
lib/Makefile | 3 +-
lib/librte_eal/common/include/rte_log.h | 3 +-
lib/librte_efd/Makefile | 56 +
lib/librte_efd/rte_efd.c | 1354 +++++++++++++++++++++
lib/librte_efd/rte_efd.h | 294 +++++
lib/librte_efd/rte_efd_version.map | 12 +
mk/rte.app.mk | 3 +-
44 files changed, 12334 insertions(+), 5 deletions(-)
create mode 100644 app/test/test_efd.c
create mode 100644 app/test/test_efd_perf.c
create mode 100644 doc/guides/prog_guide/efd_lib.rst
create mode 100644 doc/guides/prog_guide/img/efd_i1.svg
create mode 100644 doc/guides/prog_guide/img/efd_i10.svg
create mode 100644 doc/guides/prog_guide/img/efd_i11.svg
create mode 100644 doc/guides/prog_guide/img/efd_i12.svg
create mode 100644 doc/guides/prog_guide/img/efd_i2.svg
create mode 100644 doc/guides/prog_guide/img/efd_i3.svg
create mode 100644 doc/guides/prog_guide/img/efd_i4.svg
create mode 100644 doc/guides/prog_guide/img/efd_i5.svg
create mode 100644 doc/guides/prog_guide/img/efd_i6.svg
create mode 100644 doc/guides/prog_guide/img/efd_i7.svg
create mode 100644 doc/guides/prog_guide/img/efd_i8.svg
create mode 100644 doc/guides/prog_guide/img/efd_i9.svg
create mode 100644 doc/guides/sample_app_ug/flow_distributor.rst
create mode 100644 doc/guides/sample_app_ug/img/flow_distributor.svg
create mode 100644 examples/flow_distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/args.c
create mode 100644 examples/flow_distributor/distributor/args.h
create mode 100644 examples/flow_distributor/distributor/init.c
create mode 100644 examples/flow_distributor/distributor/init.h
create mode 100644 examples/flow_distributor/distributor/main.c
create mode 100644 examples/flow_distributor/node/Makefile
create mode 100644 examples/flow_distributor/node/node.c
create mode 100644 examples/flow_distributor/shared/common.h
create mode 100644 lib/librte_efd/Makefile
create mode 100644 lib/librte_efd/rte_efd.c
create mode 100644 lib/librte_efd/rte_efd.h
create mode 100644 lib/librte_efd/rte_efd_version.map
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v3 1/5] efd: new Elastic Flow Distributor library
2017-01-12 22:15 ` [dpdk-dev] [PATCH v3 " Pablo de Lara
@ 2017-01-12 22:15 ` Pablo de Lara
2017-01-12 22:15 ` [dpdk-dev] [PATCH v3 2/5] app/test: add EFD functional and perf tests Pablo de Lara
` (4 subsequent siblings)
5 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-12 22:15 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Byron Marohn, Saikrishna Edupuganti
Elastic Flow Distributor (EFD) is a distributor library that uses
perfect hashing to determine a target/value for a given incoming flow key.
It has the following advantages:
- First, because it uses perfect hashing, it does not store
the key itself and hence lookup performance is not dependent
on the key size.
- Second, the target/value can be any arbitrary value hence
the system designer and/or operator can better optimize service rates
and inter-cluster network traffic locating.
- Third, since the storage requirement is much smaller than a hash-based
flow table (i.e. better fit for CPU cache), EFD can scale to
millions of flow keys.
Finally, with current optimized library implementation performance
is fully scalable with number of CPU cores.
Signed-off-by: Byron Marohn <byron.marohn@intel.com>
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 5 +
config/common_base | 5 +
doc/api/doxy-api-index.md | 3 +-
doc/api/doxy-api.conf | 1 +
doc/guides/rel_notes/release_17_02.rst | 12 +
lib/Makefile | 3 +-
lib/librte_eal/common/include/rte_log.h | 3 +-
lib/librte_efd/Makefile | 56 ++
lib/librte_efd/rte_efd.c | 1354 +++++++++++++++++++++++++++++++
lib/librte_efd/rte_efd.h | 294 +++++++
lib/librte_efd/rte_efd_version.map | 12 +
mk/rte.app.mk | 3 +-
12 files changed, 1747 insertions(+), 4 deletions(-)
create mode 100644 lib/librte_efd/Makefile
create mode 100644 lib/librte_efd/rte_efd.c
create mode 100644 lib/librte_efd/rte_efd.h
create mode 100644 lib/librte_efd/rte_efd_version.map
diff --git a/MAINTAINERS b/MAINTAINERS
index 9645c9b..9c60d67 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -528,6 +528,11 @@ F: app/test/test_acl.*
F: examples/l3fwd-acl/
F: doc/guides/sample_app_ug/l3_forward_access_ctrl.rst
+EFD
+M: Byron Marohn <byron.marohn@intel.com>
+M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
+F: lib/librte_efd/
+
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
M: Pablo de Lara <pablo.de.lara.guarch@intel.com>
diff --git a/config/common_base b/config/common_base
index 8e9dcfa..869d8fb 100644
--- a/config/common_base
+++ b/config/common_base
@@ -467,6 +467,11 @@ CONFIG_RTE_LIBRTE_HASH=y
CONFIG_RTE_LIBRTE_HASH_DEBUG=n
#
+# Compile librte_efd
+#
+CONFIG_RTE_LIBRTE_EFD=y
+
+#
# Compile librte_jobstats
#
CONFIG_RTE_LIBRTE_JOBSTATS=y
diff --git a/doc/api/doxy-api-index.md b/doc/api/doxy-api-index.md
index 72d59b2..0d34e2f 100644
--- a/doc/api/doxy-api-index.md
+++ b/doc/api/doxy-api-index.md
@@ -90,7 +90,8 @@ There are many libraries, so their headers may be grouped by topics:
[frag/reass] (@ref rte_ip_frag.h),
[LPM IPv4 route] (@ref rte_lpm.h),
[LPM IPv6 route] (@ref rte_lpm6.h),
- [ACL] (@ref rte_acl.h)
+ [ACL] (@ref rte_acl.h),
+ [EFD] (@ref rte_efd.h)
- **QoS**:
[metering] (@ref rte_meter.h),
diff --git a/doc/api/doxy-api.conf b/doc/api/doxy-api.conf
index b340fcf..6892315 100644
--- a/doc/api/doxy-api.conf
+++ b/doc/api/doxy-api.conf
@@ -40,6 +40,7 @@ INPUT = doc/api/doxy-api-index.md \
lib/librte_compat \
lib/librte_cryptodev \
lib/librte_distributor \
+ lib/librte_efd \
lib/librte_ether \
lib/librte_hash \
lib/librte_ip_frag \
diff --git a/doc/guides/rel_notes/release_17_02.rst b/doc/guides/rel_notes/release_17_02.rst
index 180af82..5023038 100644
--- a/doc/guides/rel_notes/release_17_02.rst
+++ b/doc/guides/rel_notes/release_17_02.rst
@@ -53,6 +53,18 @@ New Features
information.
+* **Added Elastic Flow Distributor library (rte_efd).**
+
+ This new library uses perfect hashing to determine a target/value for a
+ given incoming flow key.
+
+ It does not store the key itself for lookup operations, and therefore,
+ lookup performance is not dependent on the key size. Also, the target/value
+ can be any arbitrary value (8 bits by default). Finally, the storage requirement
+ is much smaller than a hash-based flow table and therefore, it can better fit for
+ CPU cache, being able to scale to millions of flow keys.
+
+
Resolved Issues
---------------
diff --git a/lib/Makefile b/lib/Makefile
index 990f23a..4178325 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -1,6 +1,6 @@
# BSD LICENSE
#
-# Copyright(c) 2010-2015 Intel Corporation. All rights reserved.
+# Copyright(c) 2010-2017 Intel Corporation. All rights reserved.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -43,6 +43,7 @@ DIRS-$(CONFIG_RTE_LIBRTE_ETHER) += librte_ether
DIRS-$(CONFIG_RTE_LIBRTE_CRYPTODEV) += librte_cryptodev
DIRS-$(CONFIG_RTE_LIBRTE_VHOST) += librte_vhost
DIRS-$(CONFIG_RTE_LIBRTE_HASH) += librte_hash
+DIRS-$(CONFIG_RTE_LIBRTE_EFD) += librte_efd
DIRS-$(CONFIG_RTE_LIBRTE_LPM) += librte_lpm
DIRS-$(CONFIG_RTE_LIBRTE_ACL) += librte_acl
DIRS-$(CONFIG_RTE_LIBRTE_NET) += librte_net
diff --git a/lib/librte_eal/common/include/rte_log.h b/lib/librte_eal/common/include/rte_log.h
index 671e274..954b96c 100644
--- a/lib/librte_eal/common/include/rte_log.h
+++ b/lib/librte_eal/common/include/rte_log.h
@@ -1,7 +1,7 @@
/*-
* BSD LICENSE
*
- * Copyright(c) 2010-2014 Intel Corporation. All rights reserved.
+ * Copyright(c) 2010-2017 Intel Corporation. All rights reserved.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -79,6 +79,7 @@ extern struct rte_logs rte_logs;
#define RTE_LOGTYPE_PIPELINE 0x00008000 /**< Log related to pipeline. */
#define RTE_LOGTYPE_MBUF 0x00010000 /**< Log related to mbuf. */
#define RTE_LOGTYPE_CRYPTODEV 0x00020000 /**< Log related to cryptodev. */
+#define RTE_LOGTYPE_EFD 0x00040000 /**< Log related to EFD. */
/* these log types can be used in an application */
#define RTE_LOGTYPE_USER1 0x01000000 /**< User-defined log type 1. */
diff --git a/lib/librte_efd/Makefile b/lib/librte_efd/Makefile
new file mode 100644
index 0000000..58d34af
--- /dev/null
+++ b/lib/librte_efd/Makefile
@@ -0,0 +1,56 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+# library name
+LIB = librte_efd.a
+
+CFLAGS += -O3
+CFLAGS += $(WERROR_FLAGS) -I$(SRCDIR)
+
+EXPORT_MAP := rte_efd_version.map
+
+LIBABIVER := 1
+
+# all source are stored in SRCS-y
+SRCS-$(CONFIG_RTE_LIBRTE_EFD) := rte_efd.c
+
+# install this header file
+SYMLINK-$(CONFIG_RTE_LIBRTE_EFD)-include := rte_efd.h
+
+# this lib depends upon:
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_mbuf
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_mempool
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_eal
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_ether
+
+include $(RTE_SDK)/mk/rte.lib.mk
diff --git a/lib/librte_efd/rte_efd.c b/lib/librte_efd/rte_efd.c
new file mode 100644
index 0000000..917e076
--- /dev/null
+++ b/lib/librte_efd/rte_efd.c
@@ -0,0 +1,1354 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <immintrin.h>
+#include <math.h>
+#include <sys/queue.h>
+
+#include <rte_log.h>
+#include <rte_eal_memconfig.h>
+#include <rte_errno.h>
+#include <rte_malloc.h>
+#include <rte_memzone.h>
+#include <rte_prefetch.h>
+#include <rte_branch_prediction.h>
+#include <rte_memcpy.h>
+#include <rte_ring.h>
+#include <rte_jhash.h>
+#include <rte_hash_crc.h>
+
+#include "rte_efd.h"
+
+#define EFD_KEY(key_idx, table) (table->keys + ((key_idx) * table->key_len))
+/** Hash function used to determine chunk_id and bin_id for a group */
+#define EFD_HASH(key, table) \
+ (uint32_t)(rte_jhash(key, table->key_len, 0xbc9f1d34))
+/** Hash function used as constant component of perfect hash search */
+#define EFD_HASHFUNCA(key, table) \
+ (uint32_t)(rte_hash_crc(key, table->key_len, 0xbc9f1d35))
+/** Hash function used as multiplicative component of perfect hash search */
+#define EFD_HASHFUNCB(key, table) \
+ (uint32_t)(rte_hash_crc(key, table->key_len, 0xbc9f1d36))
+
+/*************************************************************************
+ * Fixed constants
+ *************************************************************************/
+
+/* These parameters are fixed by the efd_bin_to_group balancing table */
+#define EFD_CHUNK_NUM_GROUPS (64)
+#define EFD_CHUNK_NUM_BINS (256)
+#define EFD_CHUNK_NUM_BIN_TO_GROUP_SETS \
+ (EFD_CHUNK_NUM_BINS / EFD_CHUNK_NUM_GROUPS)
+
+/*
+ * Target number of rules that each chunk is created to handle.
+ * Used when initially allocating the table
+ */
+#define EFD_TARGET_CHUNK_NUM_RULES \
+ (EFD_CHUNK_NUM_GROUPS * EFD_TARGET_GROUP_NUM_RULES)
+/*
+ * Max number of rules that each chunk is created to handle.
+ * Used when initially allocating the table
+ */
+#define EFD_TARGET_CHUNK_MAX_NUM_RULES \
+ (EFD_CHUNK_NUM_GROUPS * EFD_MAX_GROUP_NUM_RULES)
+
+/** This is fixed based on the bin_to_group permutation array */
+#define EFD_MAX_GROUP_NUM_BINS (16)
+
+/**
+ * The end of the chunks array needs some extra padding to ensure
+ * that vectorization over-reads on the last online chunk stay within
+allocated memory
+ */
+#define EFD_NUM_CHUNK_PADDING_BYTES (256)
+
+#define EFD_LOOKUPTBL_SHIFT (32 - 4)
+typedef uint16_t efd_lookuptbl_t;
+typedef uint16_t efd_hashfunc_t;
+
+/* All different signature compare functions */
+enum rte_efd_compare_function {
+ RTE_HASH_COMPARE_SCALAR = 0,
+ RTE_HASH_COMPARE_AVX2,
+ RTE_HASH_COMPARE_NUM
+};
+
+TAILQ_HEAD(rte_efd_list, rte_tailq_entry);
+
+static struct rte_tailq_elem rte_efd_tailq = {
+ .name = "RTE_EFD",
+};
+EAL_REGISTER_TAILQ(rte_efd_tailq);
+
+/** Internal permutation array used to shuffle bins into pseudorandom groups */
+const uint32_t efd_bin_to_group[EFD_CHUNK_NUM_BIN_TO_GROUP_SETS][EFD_CHUNK_NUM_BINS] = {
+ {
+ 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3,
+ 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
+ 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11,
+ 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15,
+ 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19,
+ 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23,
+ 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27,
+ 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31,
+ 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35,
+ 36, 36, 36, 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39,
+ 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, 42, 43, 43, 43, 43,
+ 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47,
+ 48, 48, 48, 48, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51,
+ 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, 54, 55, 55, 55, 55,
+ 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59,
+ 60, 60, 60, 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63
+ },
+ {
+ 34, 33, 48, 59, 0, 21, 36, 18, 9, 49, 54, 38, 51, 23, 31, 5,
+ 44, 23, 37, 52, 11, 4, 58, 20, 38, 40, 38, 22, 26, 28, 42, 6,
+ 46, 16, 31, 28, 46, 14, 60, 0, 35, 53, 16, 58, 16, 29, 39, 7,
+ 1, 54, 15, 11, 48, 3, 62, 9, 58, 5, 30, 43, 17, 7, 36, 34,
+ 6, 36, 2, 14, 10, 1, 47, 47, 20, 45, 62, 56, 34, 25, 39, 18,
+ 51, 41, 61, 25, 56, 40, 41, 37, 52, 35, 30, 57, 11, 42, 37, 27,
+ 54, 19, 26, 13, 48, 31, 46, 15, 12, 10, 16, 20, 43, 17, 12, 55,
+ 45, 18, 8, 41, 7, 31, 42, 63, 12, 14, 21, 57, 24, 40, 5, 41,
+ 13, 44, 23, 59, 25, 57, 52, 50, 62, 1, 2, 49, 32, 57, 26, 43,
+ 56, 60, 55, 5, 49, 6, 3, 50, 46, 39, 27, 33, 17, 4, 53, 13,
+ 2, 19, 36, 51, 63, 0, 22, 33, 59, 28, 29, 23, 45, 33, 53, 27,
+ 22, 21, 40, 56, 4, 18, 44, 47, 28, 17, 4, 50, 21, 62, 8, 39,
+ 0, 8, 15, 24, 29, 24, 9, 11, 48, 61, 35, 55, 43, 1, 54, 42,
+ 53, 60, 22, 3, 32, 52, 25, 8, 15, 60, 7, 55, 27, 63, 19, 10,
+ 63, 24, 61, 19, 12, 38, 6, 29, 13, 37, 10, 3, 45, 32, 32, 30,
+ 49, 61, 44, 14, 20, 58, 35, 30, 2, 26, 34, 51, 9, 59, 47, 50
+ },
+ {
+ 32, 35, 32, 34, 55, 5, 6, 23, 49, 11, 6, 23, 52, 37, 29, 54,
+ 55, 40, 63, 50, 29, 52, 61, 25, 12, 56, 39, 38, 29, 11, 46, 1,
+ 40, 11, 19, 56, 7, 28, 51, 16, 15, 48, 21, 51, 60, 31, 14, 22,
+ 41, 47, 59, 56, 53, 28, 58, 26, 43, 27, 41, 33, 24, 52, 44, 38,
+ 13, 59, 48, 51, 60, 15, 3, 30, 15, 0, 10, 62, 44, 14, 28, 51,
+ 38, 2, 41, 26, 25, 49, 10, 12, 55, 57, 27, 35, 19, 33, 0, 30,
+ 5, 36, 47, 53, 5, 53, 20, 43, 34, 37, 52, 41, 21, 63, 59, 9,
+ 24, 1, 45, 24, 39, 44, 45, 16, 9, 17, 7, 50, 57, 22, 18, 28,
+ 25, 45, 2, 40, 58, 15, 17, 3, 1, 27, 61, 39, 19, 0, 19, 21,
+ 57, 62, 54, 60, 54, 40, 48, 33, 36, 37, 4, 42, 1, 43, 58, 8,
+ 13, 42, 10, 56, 35, 22, 48, 61, 63, 10, 49, 9, 24, 9, 25, 57,
+ 33, 18, 13, 31, 42, 36, 36, 55, 30, 37, 53, 34, 59, 4, 4, 23,
+ 8, 16, 58, 14, 30, 11, 12, 63, 49, 62, 2, 39, 47, 22, 2, 60,
+ 18, 8, 46, 31, 6, 20, 32, 29, 46, 42, 20, 31, 32, 61, 34, 4,
+ 47, 26, 20, 43, 26, 21, 7, 3, 16, 35, 18, 44, 27, 62, 13, 23,
+ 6, 50, 12, 8, 45, 17, 3, 46, 50, 7, 14, 5, 17, 54, 38, 0
+ },
+ {
+ 29, 56, 5, 7, 54, 48, 23, 37, 35, 44, 52, 40, 33, 49, 60, 0,
+ 59, 51, 28, 12, 41, 26, 2, 23, 34, 5, 59, 40, 3, 19, 6, 26,
+ 35, 53, 45, 49, 29, 57, 28, 62, 58, 59, 19, 53, 59, 62, 6, 54,
+ 13, 15, 48, 50, 45, 21, 41, 12, 34, 40, 24, 56, 19, 21, 35, 18,
+ 55, 45, 9, 61, 47, 61, 19, 15, 16, 39, 17, 31, 3, 51, 21, 50,
+ 17, 25, 25, 11, 44, 16, 18, 28, 14, 2, 37, 61, 58, 27, 62, 4,
+ 14, 17, 1, 9, 46, 28, 37, 0, 53, 43, 57, 7, 57, 46, 21, 41,
+ 39, 14, 52, 60, 44, 53, 49, 60, 49, 63, 13, 11, 29, 1, 55, 47,
+ 55, 12, 60, 43, 54, 37, 13, 6, 42, 10, 36, 13, 9, 8, 34, 51,
+ 31, 32, 12, 7, 57, 2, 26, 14, 3, 30, 63, 3, 32, 1, 5, 11,
+ 27, 24, 26, 44, 31, 23, 56, 38, 62, 0, 40, 30, 6, 23, 38, 2,
+ 47, 5, 15, 27, 16, 10, 31, 25, 22, 63, 30, 25, 20, 33, 32, 50,
+ 29, 43, 55, 10, 50, 45, 56, 20, 4, 7, 27, 46, 11, 16, 22, 52,
+ 35, 20, 41, 54, 46, 33, 42, 18, 63, 8, 22, 58, 36, 4, 51, 42,
+ 38, 32, 38, 22, 17, 0, 47, 8, 48, 8, 48, 1, 61, 36, 33, 20,
+ 24, 39, 39, 18, 30, 36, 9, 43, 42, 24, 10, 58, 4, 15, 34, 52
+ },
+};
+
+/*************************************************************************
+ * Offline region structures
+ *************************************************************************/
+
+/** Online group containing number of rules, values, keys and their bins
+ * for EFD_MAX_GROUP_NUM_RULES rules.
+ */
+struct efd_offline_group_rules {
+ uint32_t num_rules;
+ /**< Sum of the number of rules in all bins assigned to this group. */
+
+ uint32_t key_idx[EFD_MAX_GROUP_NUM_RULES];
+ /**< Array with all keys of the group. */
+ efd_value_t value[EFD_MAX_GROUP_NUM_RULES];
+ /**< Array with all values of the keys of the group. */
+
+ uint8_t bin_id[EFD_MAX_GROUP_NUM_RULES];
+ /**< Stores the bin for each correspending key to
+ * avoid having to recompute it
+ */
+};
+
+/** Offline chunk record, containing EFD_TARGET_CHUNK_NUM_RULES rules.
+ * Those rules are split into EFD_CHUNK_NUM_GROUPS groups per chunk.
+ */
+struct efd_offline_chunk_rules {
+ uint16_t num_rules;
+ /**< Number of rules in the entire chunk;
+ * used to detect unbalanced groups
+ */
+
+ struct efd_offline_group_rules group_rules[EFD_CHUNK_NUM_GROUPS];
+ /**< Array of all groups in the chunk. */
+};
+
+/*************************************************************************
+ * Online region structures
+ *************************************************************************/
+
+/** Online group containing values for EFD_MAX_GROUP_NUM_RULES rules. */
+struct efd_online_group_entry {
+ efd_hashfunc_t hash_idx[RTE_EFD_VALUE_NUM_BITS];
+ efd_lookuptbl_t lookup_table[RTE_EFD_VALUE_NUM_BITS];
+} __attribute__((__packed__));
+
+/**
+ * A single chunk record, containing EFD_TARGET_CHUNK_NUM_RULES rules.
+ * Those rules are split into EFD_CHUNK_NUM_GROUPS groups per chunk.
+ */
+struct efd_online_chunk {
+ uint8_t bin_choice_list[(EFD_CHUNK_NUM_BINS * 2 + 7) / 8];
+ /**< This is a packed indirection index into the 'groups' array.
+ * Each byte contains four two-bit values which index into
+ * the efd_bin_to_group array.
+ * The efd_bin_to_group array returns the index into the groups array
+ */
+
+ struct efd_online_group_entry groups[EFD_CHUNK_NUM_GROUPS];
+ /**< Array of all the groups in the chunk. */
+} __attribute__((__packed__));
+
+/**
+ * EFD table structure
+ */
+struct rte_efd_table {
+ char name[RTE_EFD_NAMESIZE]; /**< Name of the efd table. */
+
+ uint32_t key_len; /**< Length of the key stored offline */
+
+ uint32_t max_num_rules;
+ /**< Static maximum number of entries the table was constructed to hold. */
+
+ uint32_t num_rules;
+ /**< Number of entries currently in the table . */
+
+ uint32_t num_chunks;
+ /**< Number of chunks in the table needed to support num_rules. */
+
+ uint32_t num_chunks_shift;
+ /**< Bits to shift to get chunk id, instead of dividing by num_chunk. */
+
+ enum rte_efd_compare_function cmp_fn;
+ /**< Indicates which compare function to use. */
+
+ struct efd_online_chunk *chunks[RTE_MAX_NUMA_NODES];
+ /**< Dynamic array of size num_chunks of chunk records. */
+
+ struct efd_offline_chunk_rules *offline_chunks;
+ /**< Dynamic array of size num_chunks of key-value pairs. */
+
+ struct rte_ring *free_slots;
+ /**< Ring that stores all indexes of the free slots in the key table */
+
+ uint8_t *keys; /**< Dynamic array of size max_num_rules of keys */
+};
+
+/**
+ * Computes the chunk ID for a given key hash
+ *
+ * @param table
+ * EFD table to reference
+ * @param hashed_key
+ * 32-bit key hash returned by EFD_HASH
+ *
+ * @return
+ * chunk ID containing this key hash
+ */
+static inline uint32_t
+efd_get_chunk_id(const struct rte_efd_table * const table,
+ const uint32_t hashed_key)
+{
+ return hashed_key & (table->num_chunks - 1);
+}
+
+/**
+ * Computes the bin ID for a given key hash
+ *
+ * @param table
+ * EFD table to reference
+ * @param hashed_key
+ * 32-bit key hash returned by EFD_HASH
+ *
+ * @return bin ID containing this key hash
+ */
+static inline uint32_t
+efd_get_bin_id(const struct rte_efd_table * const table,
+ const uint32_t hashed_key)
+{
+ return (hashed_key >> table->num_chunks_shift) & (EFD_CHUNK_NUM_BINS - 1);
+}
+
+/**
+ * Looks up the current permutation choice for a particular bin in the online table
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to look up existing values (ideally caller's socket id)
+ * @param chunk_id
+ * Chunk ID of bin to look up
+ * @param bin_id
+ * Bin ID to look up
+ *
+ * @return
+ * Currently active permutation choice in the online table
+ */
+static inline uint8_t
+efd_get_choice(const struct rte_efd_table * const table,
+ const unsigned int socket_id, const uint32_t chunk_id,
+ const uint32_t bin_id)
+{
+ struct efd_online_chunk *chunk = &table->chunks[socket_id][chunk_id];
+
+ /*
+ * Grab the chunk (byte) that contains the choices
+ * for four neighboring bins.
+ */
+ uint8_t choice_chunk =
+ chunk->bin_choice_list[bin_id / EFD_CHUNK_NUM_BIN_TO_GROUP_SETS];
+
+ /*
+ * Compute the offset into the chunk that contains
+ * the group_id lookup position
+ */
+ int offset = (bin_id & 0x3) * 2;
+
+ /* Extract from the byte just the desired lookup position */
+ return (uint8_t) ((choice_chunk >> offset) & 0x3);
+}
+
+/**
+ * Compute the chunk_id and bin_id for a given key
+ *
+ * @param table
+ * EFD table to reference
+ * @param key
+ * Key to hash and find location of
+ * @param chunk_id
+ * Computed chunk ID
+ * @param bin_id
+ * Computed bin ID
+ *
+ */
+static inline void
+efd_compute_ids(const struct rte_efd_table * const table,
+ const void *key, uint32_t * const chunk_id, uint32_t * const bin_id)
+{
+ /* Compute the position of the entry in the hash table */
+ uint32_t h = EFD_HASH(key, table);
+
+ /* Compute the chunk_id where that entry can be found */
+ *chunk_id = efd_get_chunk_id(table, h);
+
+ /*
+ * Compute the bin within that chunk where the entry
+ * can be found (0 - 255)
+ */
+ *bin_id = efd_get_bin_id(table, h);
+}
+
+/**
+ * Search for a hash function for a group that satisfies all group results
+ */
+static inline int
+efd_search_hash(struct rte_efd_table * const table,
+ const struct efd_offline_group_rules * const off_group,
+ struct efd_online_group_entry * const on_group)
+{
+ efd_hashfunc_t hash_idx;
+ efd_hashfunc_t start_hash_idx[RTE_EFD_VALUE_NUM_BITS];
+ efd_lookuptbl_t start_lookup_table[RTE_EFD_VALUE_NUM_BITS];
+
+ uint32_t i, j, rule_id;
+ uint32_t hash_val_a[EFD_MAX_GROUP_NUM_RULES];
+ uint32_t hash_val_b[EFD_MAX_GROUP_NUM_RULES];
+ uint32_t hash_val[EFD_MAX_GROUP_NUM_RULES];
+
+
+ rte_prefetch0(off_group->value);
+
+ /*
+ * Prepopulate the hash_val tables by running the two hash functions
+ * for each provided rule
+ */
+ for (i = 0; i < off_group->num_rules; i++) {
+ void *key_stored = EFD_KEY(off_group->key_idx[i], table);
+ hash_val_b[i] = EFD_HASHFUNCB(key_stored, table);
+ hash_val_a[i] = EFD_HASHFUNCA(key_stored, table);
+ }
+
+ for (i = 0; i < RTE_EFD_VALUE_NUM_BITS; i++) {
+ hash_idx = on_group->hash_idx[i];
+ start_hash_idx[i] = hash_idx;
+ start_lookup_table[i] = on_group->lookup_table[i];
+
+ do {
+ efd_lookuptbl_t lookup_table = 0;
+ efd_lookuptbl_t lookup_table_complement = 0;
+
+ for (rule_id = 0; rule_id < off_group->num_rules; rule_id++)
+ hash_val[rule_id] = hash_val_a[rule_id] + (hash_idx *
+ hash_val_b[rule_id]);
+
+ /*
+ * The goal here is to find a hash function for this
+ * particular bit entry that meets the following criteria:
+ * The most significant bits of the hash result define a
+ * shift into the lookup table where the bit will be stored
+ */
+
+ /* Iterate over each provided rule */
+ for (rule_id = 0; rule_id < off_group->num_rules;
+ rule_id++) {
+ /*
+ * Use the few most significant bits (number based on
+ * EFD_LOOKUPTBL_SIZE) to see what position the
+ * expected bit should be set in the lookup_table
+ */
+ uint32_t bucket_idx = hash_val[rule_id] >>
+ EFD_LOOKUPTBL_SHIFT;
+
+ /*
+ * Get the current bit of interest.
+ * This only find an appropriate hash function
+ * for one bit at a time of the rule
+ */
+ efd_lookuptbl_t expected =
+ (off_group->value[rule_id] >> i) & 0x1;
+
+ /*
+ * Add the expected bit (if set) to a map
+ * (lookup_table). Also set its complement
+ * in lookup_table_complement
+ */
+ lookup_table |= expected << bucket_idx;
+ lookup_table_complement |= (1 - expected)
+ << bucket_idx;
+
+ /*
+ * If ever the hash function of two different
+ * elements result in different values at the
+ * same location in the lookup_table,
+ * the current hash_idx is not valid.
+ */
+ if (lookup_table & lookup_table_complement)
+ break;
+ }
+
+ /*
+ * Check if the previous loop completed without
+ * breaking early
+ */
+ if (rule_id == off_group->num_rules) {
+ /*
+ * Current hash function worked, store it
+ * for the current group
+ */
+ on_group->hash_idx[i] = hash_idx;
+ on_group->lookup_table[i] = lookup_table;
+
+ /*
+ * Make sure that the hash function has changed
+ * from the starting value
+ */
+ hash_idx = start_hash_idx[i] + 1;
+ break;
+ }
+ hash_idx++;
+
+ } while (hash_idx != start_hash_idx[i]);
+
+ /* Failed to find perfect hash for this group */
+ if (hash_idx == start_hash_idx[i]) {
+ /*
+ * Restore previous hash_idx and lookup_table
+ * for all value bits
+ */
+ for (j = 0; j < i; j++) {
+ on_group->hash_idx[j] = start_hash_idx[j];
+ on_group->lookup_table[j] = start_lookup_table[j];
+ }
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+struct rte_efd_table *
+rte_efd_create(const char *name, uint32_t max_num_rules, uint32_t key_len,
+ uint8_t online_cpu_socket_bitmask, uint8_t offline_cpu_socket)
+{
+ struct rte_efd_table *table = NULL;
+ uint8_t *key_array = NULL;
+ uint32_t num_chunks, num_chunks_shift;
+ uint8_t socket_id;
+ struct rte_efd_list *efd_list = NULL;
+ struct rte_tailq_entry *te;
+ uint64_t offline_table_size;
+ char ring_name[RTE_RING_NAMESIZE];
+ struct rte_ring *r = NULL;
+ unsigned int i;
+
+ efd_list = RTE_TAILQ_CAST(rte_efd_tailq.head, rte_efd_list);
+
+ if (online_cpu_socket_bitmask == 0) {
+ RTE_LOG(ERR, EFD, "At least one CPU socket must be enabled "
+ "in the bitmask\n");
+ return NULL;
+ }
+
+ if (max_num_rules == 0) {
+ RTE_LOG(ERR, EFD, "Max num rules must be higher than 0\n");
+ return NULL;
+ }
+
+ /*
+ * Compute the minimum number of chunks (smallest power of 2)
+ * that can hold all of the rules
+ */
+ if (max_num_rules % EFD_TARGET_CHUNK_NUM_RULES == 0)
+ num_chunks = rte_align32pow2(max_num_rules /
+ EFD_TARGET_CHUNK_NUM_RULES);
+ else
+ num_chunks = rte_align32pow2((max_num_rules /
+ EFD_TARGET_CHUNK_NUM_RULES) + 1);
+
+ num_chunks_shift = log2(num_chunks);
+
+ rte_rwlock_write_lock(RTE_EAL_TAILQ_RWLOCK);
+
+ /*
+ * Guarantee there's no existing: this is normally already checked
+ * by ring creation above
+ */
+ TAILQ_FOREACH(te, efd_list, next)
+ {
+ table = (struct rte_efd_table *) te->data;
+ if (strncmp(name, table->name, RTE_EFD_NAMESIZE) == 0)
+ break;
+ }
+
+ table = NULL;
+ if (te != NULL) {
+ rte_errno = EEXIST;
+ te = NULL;
+ goto error_unlock_exit;
+ }
+
+ te = rte_zmalloc("EFD_TAILQ_ENTRY", sizeof(*te), 0);
+ if (te == NULL) {
+ RTE_LOG(ERR, EFD, "tailq entry allocation failed\n");
+ goto error_unlock_exit;
+ }
+
+ /* Create a new EFD table management structure */
+ table = (struct rte_efd_table *) rte_zmalloc_socket(NULL,
+ sizeof(struct rte_efd_table),
+ RTE_CACHE_LINE_SIZE,
+ offline_cpu_socket);
+ if (table == NULL) {
+ RTE_LOG(ERR, EFD, "Allocating EFD table management structure"
+ " on socket %u failed\n",
+ offline_cpu_socket);
+ goto error_unlock_exit;
+ }
+
+
+ RTE_LOG(DEBUG, EFD, "Allocated EFD table management structure "
+ "on socket %u\n", offline_cpu_socket);
+
+ table->max_num_rules = num_chunks * EFD_TARGET_CHUNK_MAX_NUM_RULES;
+ table->num_rules = 0;
+ table->num_chunks = num_chunks;
+ table->num_chunks_shift = num_chunks_shift;
+ table->key_len = key_len;
+
+ /* key_array */
+ key_array = (uint8_t *) rte_zmalloc_socket(NULL,
+ table->max_num_rules * table->key_len,
+ RTE_CACHE_LINE_SIZE,
+ offline_cpu_socket);
+ if (key_array == NULL) {
+ RTE_LOG(ERR, EFD, "Allocating key array"
+ " on socket %u failed\n",
+ offline_cpu_socket);
+ goto error_unlock_exit;
+ }
+ table->keys = key_array;
+ snprintf(table->name, sizeof(table->name), "%s", name);
+
+ RTE_LOG(DEBUG, EFD, "Creating an EFD table with %u chunks,"
+ " which potentially supports %u entries\n",
+ num_chunks, table->max_num_rules);
+
+ /* Make sure all the allocatable table pointers are NULL initially */
+ for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++)
+ table->chunks[socket_id] = NULL;
+ table->offline_chunks = NULL;
+
+ /*
+ * Allocate one online table per socket specified
+ * in the user-supplied bitmask
+ */
+ uint64_t online_table_size = num_chunks * sizeof(struct efd_online_chunk) +
+ EFD_NUM_CHUNK_PADDING_BYTES;
+
+ for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++) {
+ if ((online_cpu_socket_bitmask >> socket_id) & 0x01) {
+ /*
+ * Allocate all of the EFD table chunks (the online portion)
+ * as a continuous block
+ */
+ table->chunks[socket_id] =
+ (struct efd_online_chunk *) rte_zmalloc_socket(
+ NULL,
+ online_table_size,
+ RTE_CACHE_LINE_SIZE,
+ socket_id);
+ if (table->chunks[socket_id] == NULL) {
+ RTE_LOG(ERR, EFD,
+ "Allocating EFD online table on "
+ "socket %u failed\n",
+ socket_id);
+ goto error_unlock_exit;
+ }
+ RTE_LOG(DEBUG, EFD,
+ "Allocated EFD online table of size "
+ "%"PRIu64" bytes (%.2f MB) on socket %u\n",
+ online_table_size,
+ (float) online_table_size /
+ (1024.0F * 1024.0F),
+ socket_id);
+ }
+ }
+
+#if defined(RTE_ARCH_X86)
+ if (RTE_EFD_VALUE_NUM_BITS > 3 && rte_cpu_get_flag_enabled(RTE_CPUFLAG_AVX2))
+ table->cmp_fn = RTE_HASH_COMPARE_AVX2;
+ else
+#endif
+ table->cmp_fn = RTE_HASH_COMPARE_SCALAR;
+
+ /*
+ * Allocate the EFD table offline portion (with the actual rules
+ * mapping keys to values) as a continuous block.
+ * This could be several gigabytes of memory.
+ */
+ offline_table_size = num_chunks * sizeof(struct efd_offline_chunk_rules);
+ table->offline_chunks =
+ (struct efd_offline_chunk_rules *) rte_zmalloc_socket(NULL,
+ offline_table_size,
+ RTE_CACHE_LINE_SIZE,
+ offline_cpu_socket);
+ if (table->offline_chunks == NULL) {
+ RTE_LOG(ERR, EFD, "Allocating EFD offline table on socket %u "
+ "failed\n", offline_cpu_socket);
+ goto error_unlock_exit;
+ }
+
+ RTE_LOG(DEBUG, EFD,
+ "Allocated EFD offline table of size %"PRIu64" bytes "
+ " (%.2f MB) on socket %u\n", offline_table_size,
+ (float) offline_table_size / (1024.0F * 1024.0F),
+ offline_cpu_socket);
+
+ te->data = (void *) table;
+ TAILQ_INSERT_TAIL(efd_list, te, next);
+ rte_rwlock_write_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+ snprintf(ring_name, sizeof(ring_name), "HT_%s", table->name);
+ /* Create ring (Dummy slot index is not enqueued) */
+ r = rte_ring_create(ring_name, rte_align32pow2(table->max_num_rules),
+ offline_cpu_socket, 0);
+ if (r == NULL) {
+ RTE_LOG(ERR, EFD, "memory allocation failed\n");
+ goto error_unlock_exit;
+ }
+
+ /* Populate free slots ring. Entry zero is reserved for key misses. */
+ for (i = 0; i < table->max_num_rules; i++)
+ rte_ring_sp_enqueue(r, (void *) ((uintptr_t) i));
+
+ table->free_slots = r;
+ return table;
+
+error_unlock_exit:
+ rte_rwlock_write_unlock(RTE_EAL_TAILQ_RWLOCK);
+ rte_efd_free(table);
+
+ return NULL;
+}
+
+struct rte_efd_table *
+rte_efd_find_existing(const char *name)
+{
+ struct rte_efd_table *table = NULL;
+ struct rte_tailq_entry *te;
+ struct rte_efd_list *efd_list;
+
+ efd_list = RTE_TAILQ_CAST(rte_efd_tailq.head, rte_efd_list);
+
+ rte_rwlock_read_lock(RTE_EAL_TAILQ_RWLOCK);
+
+ TAILQ_FOREACH(te, efd_list, next)
+ {
+ table = (struct rte_efd_table *) te->data;
+ if (strncmp(name, table->name, RTE_EFD_NAMESIZE) == 0)
+ break;
+ }
+ rte_rwlock_read_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+ if (te == NULL) {
+ rte_errno = ENOENT;
+ return NULL;
+ }
+ return table;
+}
+
+void
+rte_efd_free(struct rte_efd_table *table)
+{
+ uint8_t socket_id;
+
+ if (table == NULL)
+ return;
+
+ for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++)
+ rte_free(table->chunks[socket_id]);
+
+ rte_ring_free(table->free_slots);
+ rte_free(table->offline_chunks);
+ rte_free(table->keys);
+ rte_free(table);
+}
+
+/**
+ * Applies a previously computed table entry to the specified table for all
+ * socket-local copies of the online table.
+ * Intended to apply an update for only a single change
+ * to a key/value pair at a time
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing values (ideally caller's socket id)
+ * @param chunk_id
+ * Chunk index to update
+ * @param group_id
+ * Group index to update
+ * @param bin_id
+ * Bin within the group that this update affects
+ * @param new_bin_choice
+ * Newly chosen permutation which this bin should use - only lower 2 bits
+ * @param new_group_entry
+ * Previously computed updated chunk/group entry
+ */
+static inline void
+efd_apply_update(struct rte_efd_table * const table, const unsigned int socket_id,
+ const uint32_t chunk_id, const uint32_t group_id,
+ const uint32_t bin_id, const uint8_t new_bin_choice,
+ const struct efd_online_group_entry * const new_group_entry)
+{
+ int i;
+ struct efd_online_chunk *chunk = &table->chunks[socket_id][chunk_id];
+ uint8_t bin_index = bin_id / EFD_CHUNK_NUM_BIN_TO_GROUP_SETS;
+
+ /*
+ * Grab the current byte that contains the choices
+ * for four neighboring bins
+ */
+ uint8_t choice_chunk =
+ chunk->bin_choice_list[bin_index];
+
+
+ /* Compute the offset into the chunk that needs to be updated */
+ int offset = (bin_id & 0x3) * 2;
+
+ /* Zero the two bits of interest and set them to new_bin_choice */
+ choice_chunk = (choice_chunk & (~(0x03 << offset)))
+ | ((new_bin_choice & 0x03) << offset);
+
+ /* Update the online table with the new data across all sockets */
+ for (i = 0; i < RTE_MAX_NUMA_NODES; i++) {
+ if (table->chunks[i] != NULL) {
+ memcpy(&(table->chunks[i][chunk_id].groups[group_id]),
+ new_group_entry,
+ sizeof(struct efd_online_group_entry));
+ table->chunks[i][chunk_id].bin_choice_list[bin_index] =
+ choice_chunk;
+ }
+ }
+}
+
+/*
+ * Move the bin from prev group to the new group
+ */
+static inline void
+move_groups(uint32_t bin_id, uint8_t bin_size,
+ struct efd_offline_group_rules *new_group,
+ struct efd_offline_group_rules * const current_group)
+{
+
+ uint8_t empty_idx = 0;
+ unsigned int i;
+
+ if (new_group == current_group)
+ return;
+
+ for (i = 0; i < current_group->num_rules; i++) {
+ /*
+ * Move keys that belong to the same bin
+ * to the new group
+ */
+ if (current_group->bin_id[i] == bin_id) {
+ new_group->key_idx[new_group->num_rules] =
+ current_group->key_idx[i];
+ new_group->value[new_group->num_rules] =
+ current_group->value[i];
+ new_group->bin_id[new_group->num_rules] =
+ current_group->bin_id[i];
+ new_group->num_rules++;
+ } else {
+ if (i != empty_idx) {
+ /*
+ * Need to move this key towards
+ * the top of the array
+ */
+ current_group->key_idx[empty_idx] =
+ current_group->key_idx[i];
+ current_group->value[empty_idx] =
+ current_group->value[i];
+ current_group->bin_id[empty_idx] =
+ current_group->bin_id[i];
+ }
+ empty_idx++;
+ }
+
+ }
+ current_group->num_rules -= bin_size;
+}
+
+/*
+ * Revert group/s to their previous state before
+ * trying to insert/add a new key
+ */
+static inline void
+revert_groups(struct efd_offline_group_rules *previous_group,
+ struct efd_offline_group_rules *current_group, uint8_t bin_size)
+{
+ unsigned int i;
+
+ if (current_group == previous_group)
+ return;
+
+ /* Move keys back to previous group */
+ for (i = current_group->num_rules - bin_size;
+ i < current_group->num_rules; i++) {
+ previous_group->key_idx[previous_group->num_rules] =
+ current_group->key_idx[i];
+ previous_group->value[previous_group->num_rules] =
+ current_group->value[i];
+ previous_group->bin_id[previous_group->num_rules] =
+ current_group->bin_id[i];
+ previous_group->num_rules++;
+ }
+
+ /*
+ * Decrease number of rules after the move
+ * in the new group
+ */
+ current_group->num_rules -= bin_size;
+}
+
+/**
+ * Computes an updated table entry where the supplied key points to a new host.
+ * If no entry exists, one is inserted.
+ *
+ * This function does NOT modify the online table(s)
+ * This function DOES modify the offline table
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing values (ideally caller's socket id)
+ * @param key
+ * Key to insert
+ * @param value
+ * Value to associate with key
+ * @param chunk_id
+ * Chunk ID of the chunk that was modified
+ * @param group_id
+ * Group ID of the group that was modified
+ * @param bin_id
+ * Bin ID that was modified
+ * @param new_bin_choice
+ * Newly chosen permutation which this bin will use
+ * @param entry
+ * Newly computed online entry to apply later with efd_apply_update
+ *
+ * @return
+ * RTE_EFD_UPDATE_WARN_GROUP_FULL
+ * Operation is insert, and the last available space in the
+ * key's group was just used. Future inserts may fail as groups fill up.
+ * This operation was still successful, and entry contains a valid update
+ * RTE_EFD_UPDATE_FAILED
+ * Either the EFD failed to find a suitable perfect hash or the group was full
+ * This is a fatal error, and the table is now in an indeterminite state
+ * RTE_EFD_UPDATE_NO_CHANGE
+ * Operation resulted in no change to the table (same value already exists)
+ * 0
+ * Insert or update was successful, and the new efd_online_group_entry
+ * is stored in *entry
+ *
+ * @warning
+ * Note that entry will be UNCHANGED if the update has no effect, and thus any
+ * subsequent use of the entry content will likely be invalid
+ */
+static inline int
+efd_compute_update(struct rte_efd_table * const table,
+ const unsigned int socket_id, const void *key,
+ const efd_value_t value, uint32_t * const chunk_id,
+ uint32_t * const group_id, uint32_t * const bin_id,
+ uint8_t * const new_bin_choice,
+ struct efd_online_group_entry * const entry)
+{
+ unsigned int i;
+ int ret;
+ uint32_t new_idx;
+ void *new_k, *slot_id = NULL;
+ int status = EXIT_SUCCESS;
+ unsigned int found = 0;
+
+ efd_compute_ids(table, key, chunk_id, bin_id);
+
+ struct efd_offline_chunk_rules * const chunk =
+ &table->offline_chunks[*chunk_id];
+ struct efd_offline_group_rules *new_group;
+
+ uint8_t current_choice = efd_get_choice(table, socket_id,
+ *chunk_id, *bin_id);
+ uint32_t current_group_id = efd_bin_to_group[current_choice][*bin_id];
+ struct efd_offline_group_rules * const current_group =
+ &chunk->group_rules[current_group_id];
+ uint8_t bin_size = 0;
+ uint8_t key_changed_index = 0;
+ efd_value_t key_changed_previous_value = 0;
+ uint32_t key_idx_previous = 0;
+
+ /* Scan the current group and see if the key is already present */
+ for (i = 0; i < current_group->num_rules; i++) {
+ if (current_group->bin_id[i] == *bin_id)
+ bin_size++;
+ else
+ continue;
+
+ void *key_stored = EFD_KEY(current_group->key_idx[i], table);
+ if (found == 0 && unlikely(memcmp(key_stored, key,
+ table->key_len) == 0)) {
+ /* Key is already present */
+
+ /*
+ * If previous value is same as new value,
+ * no additional work is required
+ */
+ if (current_group->value[i] == value)
+ return RTE_EFD_UPDATE_NO_CHANGE;
+
+ key_idx_previous = current_group->key_idx[i];
+ key_changed_previous_value = current_group->value[i];
+ key_changed_index = i;
+ current_group->value[i] = value;
+ found = 1;
+ }
+ }
+
+ if (found == 0) {
+ /* Key does not exist. Insert the rule into the bin/group */
+ if (unlikely(current_group->num_rules >= EFD_MAX_GROUP_NUM_RULES)) {
+ RTE_LOG(ERR, EFD,
+ "Fatal: No room remaining for insert into "
+ "chunk %u group %u bin %u\n",
+ *chunk_id,
+ current_group_id, *bin_id);
+ return RTE_EFD_UPDATE_FAILED;
+ }
+
+ if (unlikely(current_group->num_rules ==
+ (EFD_MAX_GROUP_NUM_RULES - 1))) {
+ RTE_LOG(INFO, EFD, "Warn: Insert into last "
+ "available slot in chunk %u "
+ "group %u bin %u\n", *chunk_id,
+ current_group_id, *bin_id);
+ status = RTE_EFD_UPDATE_WARN_GROUP_FULL;
+ }
+
+ if (rte_ring_sc_dequeue(table->free_slots, &slot_id) != 0)
+ return RTE_EFD_UPDATE_FAILED;
+
+ new_k = RTE_PTR_ADD(table->keys, (uintptr_t) slot_id *
+ table->key_len);
+ rte_prefetch0(new_k);
+ new_idx = (uint32_t) ((uintptr_t) slot_id);
+
+ rte_memcpy(EFD_KEY(new_idx, table), key, table->key_len);
+ current_group->key_idx[current_group->num_rules] = new_idx;
+ current_group->value[current_group->num_rules] = value;
+ current_group->bin_id[current_group->num_rules] = *bin_id;
+ current_group->num_rules++;
+ table->num_rules++;
+ bin_size++;
+ } else {
+ uint32_t last = current_group->num_rules - 1;
+ /* Swap the key with the last key inserted*/
+ current_group->key_idx[key_changed_index] =
+ current_group->key_idx[last];
+ current_group->value[key_changed_index] =
+ current_group->value[last];
+ current_group->bin_id[key_changed_index] =
+ current_group->bin_id[last];
+
+ /*
+ * Key to be updated will always be available
+ * at the end of the group
+ */
+ current_group->key_idx[last] = key_idx_previous;
+ current_group->value[last] = value;
+ current_group->bin_id[last] = *bin_id;
+ }
+
+ *new_bin_choice = current_choice;
+ *group_id = current_group_id;
+ new_group = current_group;
+
+ /* Group need to be rebalanced when it starts to get loaded */
+ if (current_group->num_rules > EFD_MIN_BALANCED_NUM_RULES) {
+
+ /*
+ * Subtract the number of entries in the bin from
+ * the original group
+ */
+ current_group->num_rules -= bin_size;
+
+ /*
+ * Figure out which of the available groups that this bin
+ * can map to is the smallest (using the current group
+ * as baseline)
+ */
+ uint8_t smallest_choice = current_choice;
+ uint8_t smallest_size = current_group->num_rules;
+ uint32_t smallest_group_id = current_group_id;
+ unsigned char choice;
+
+ for (choice = 0; choice < EFD_CHUNK_NUM_BIN_TO_GROUP_SETS;
+ choice++) {
+ uint32_t test_group_id =
+ efd_bin_to_group[choice][*bin_id];
+ uint32_t num_rules =
+ chunk->group_rules[test_group_id].num_rules;
+ if (num_rules < smallest_size) {
+ smallest_choice = choice;
+ smallest_size = num_rules;
+ smallest_group_id = test_group_id;
+ }
+ }
+
+ *new_bin_choice = smallest_choice;
+ *group_id = smallest_group_id;
+ new_group = &chunk->group_rules[smallest_group_id];
+ current_group->num_rules += bin_size;
+
+ }
+
+ uint8_t choice = 0;
+ for (;;) {
+ if (current_group != new_group &&
+ new_group->num_rules + bin_size >
+ EFD_MAX_GROUP_NUM_RULES) {
+ RTE_LOG(DEBUG, EFD,
+ "Unable to move_groups to dest group "
+ "containing %u entries."
+ "bin_size:%u choice:%02x\n",
+ new_group->num_rules, bin_size,
+ choice - 1);
+ goto next_choice;
+ }
+ move_groups(*bin_id, bin_size, new_group, current_group);
+ /*
+ * Recompute the hash function for the modified group,
+ * and return it to the caller
+ */
+ ret = efd_search_hash(table, new_group, entry);
+
+ if (!ret)
+ return status;
+
+ RTE_LOG(DEBUG, EFD,
+ "Failed to find perfect hash for group "
+ "containing %u entries. bin_size:%u choice:%02x\n",
+ new_group->num_rules, bin_size, choice - 1);
+ /* Restore groups modified to their previous state */
+ revert_groups(current_group, new_group, bin_size);
+
+next_choice:
+ if (choice == EFD_CHUNK_NUM_BIN_TO_GROUP_SETS)
+ break;
+ *new_bin_choice = choice;
+ *group_id = efd_bin_to_group[choice][*bin_id];
+ new_group = &chunk->group_rules[*group_id];
+ choice++;
+ }
+
+ if (!found) {
+ current_group->num_rules--;
+ table->num_rules--;
+ } else
+ current_group->value[current_group->num_rules - 1] =
+ key_changed_previous_value;
+ return RTE_EFD_UPDATE_FAILED;
+}
+
+int
+rte_efd_update(struct rte_efd_table * const table, const unsigned int socket_id,
+ const void *key, const efd_value_t value)
+{
+ uint32_t chunk_id = 0, group_id = 0, bin_id = 0;
+ uint8_t new_bin_choice = 0;
+ struct efd_online_group_entry entry;
+
+ int status = efd_compute_update(table, socket_id, key, value,
+ &chunk_id, &group_id, &bin_id,
+ &new_bin_choice, &entry);
+
+ if (status == RTE_EFD_UPDATE_NO_CHANGE)
+ return EXIT_SUCCESS;
+
+ if (status == RTE_EFD_UPDATE_FAILED)
+ return status;
+
+ efd_apply_update(table, socket_id, chunk_id, group_id, bin_id,
+ new_bin_choice, &entry);
+ return status;
+}
+
+int
+rte_efd_delete(struct rte_efd_table * const table, const unsigned int socket_id,
+ const void *key, efd_value_t * const prev_value)
+{
+ unsigned int i;
+ uint32_t chunk_id, bin_id;
+ uint8_t not_found = 1;
+
+ efd_compute_ids(table, key, &chunk_id, &bin_id);
+
+ struct efd_offline_chunk_rules * const chunk =
+ &table->offline_chunks[chunk_id];
+
+ uint8_t current_choice = efd_get_choice(table, socket_id,
+ chunk_id, bin_id);
+ uint32_t current_group_id = efd_bin_to_group[current_choice][bin_id];
+ struct efd_offline_group_rules * const current_group =
+ &chunk->group_rules[current_group_id];
+
+ /*
+ * Search the current group for the specified key.
+ * If it exists, remove it and re-pack the other values
+ */
+ for (i = 0; i < current_group->num_rules; i++) {
+ if (not_found) {
+ /* Found key that needs to be removed */
+ if (memcmp(EFD_KEY(current_group->key_idx[i], table),
+ key, table->key_len) == 0) {
+ /* Store previous value if requested by caller */
+ if (prev_value != NULL)
+ *prev_value = current_group->value[i];
+
+ not_found = 0;
+ rte_ring_sp_enqueue(table->free_slots,
+ (void *)((uintptr_t)current_group->key_idx[i]));
+ }
+ } else {
+ /*
+ * If the desired key has been found,
+ * need to shift other values up one
+ */
+
+ /* Need to shift this entry back up one index */
+ current_group->key_idx[i - 1] = current_group->key_idx[i];
+ current_group->value[i - 1] = current_group->value[i];
+ current_group->bin_id[i - 1] = current_group->bin_id[i];
+ }
+ }
+
+ if (not_found == 0) {
+ table->num_rules--;
+ current_group->num_rules--;
+ }
+
+ return not_found;
+}
+
+
+#if (RTE_EFD_VALUE_NUM_BITS == 8 || RTE_EFD_VALUE_NUM_BITS == 16 || \
+ RTE_EFD_VALUE_NUM_BITS == 24 || RTE_EFD_VALUE_NUM_BITS == 32)
+#define EFD_LOAD_SI128(val) _mm_load_si128(val)
+#else
+#define EFD_LOAD_SI128(val) _mm_lddqu_si128(val)
+#endif
+
+static inline efd_value_t
+efd_lookup_internal(const struct efd_online_group_entry * const group,
+ const uint32_t hash_val_a, const uint32_t hash_val_b,
+ enum rte_efd_compare_function cmp_fn)
+{
+ efd_value_t value = 0;
+ uint32_t i;
+
+ switch (cmp_fn) {
+#ifdef RTE_MACHINE_CPUFLAG_AVX2
+ case RTE_HASH_COMPARE_AVX2:
+
+ i = 0;
+ __m256i vhash_val_a = _mm256_set1_epi32(hash_val_a);
+ __m256i vhash_val_b = _mm256_set1_epi32(hash_val_b);
+
+ for (; i < RTE_EFD_VALUE_NUM_BITS; i += 8) {
+ __m256i vhash_idx =
+ _mm256_cvtepu16_epi32(EFD_LOAD_SI128(
+ (__m128i const *) &group->hash_idx[i]));
+ __m256i vlookup_table = _mm256_cvtepu16_epi32(
+ EFD_LOAD_SI128((__m128i const *)
+ &group->lookup_table[i]));
+ __m256i vhash = _mm256_add_epi32(vhash_val_a,
+ _mm256_mullo_epi32(vhash_idx, vhash_val_b));
+ __m256i vbucket_idx = _mm256_srli_epi32(vhash,
+ EFD_LOOKUPTBL_SHIFT);
+ __m256i vresult = _mm256_srlv_epi32(vlookup_table,
+ vbucket_idx);
+
+ value |= (_mm256_movemask_ps(
+ (__m256) _mm256_slli_epi32(vresult, 31))
+ & ((1 << (RTE_EFD_VALUE_NUM_BITS - i)) - 1)) << i;
+ }
+ break;
+#endif
+ default:
+
+ i = 0;
+ for (; i < RTE_EFD_VALUE_NUM_BITS; i++) {
+ value <<= 1;
+
+ uint32_t h = hash_val_a + (hash_val_b *
+ group->hash_idx[RTE_EFD_VALUE_NUM_BITS - i - 1]);
+ uint16_t bucket_idx = h >> EFD_LOOKUPTBL_SHIFT;
+
+ value |= (group->lookup_table[
+ RTE_EFD_VALUE_NUM_BITS - i - 1] >>
+ bucket_idx) & 0x1;
+ }
+ }
+ return value;
+}
+
+efd_value_t
+rte_efd_lookup(const struct rte_efd_table * const table,
+ const unsigned int socket_id, const void *key)
+{
+ uint32_t chunk_id, group_id, bin_id;
+ uint8_t bin_choice;
+ const struct efd_online_group_entry *group;
+ const struct efd_online_chunk * const chunks = table->chunks[socket_id];
+
+ /* Determine the chunk and group location for the given key */
+ efd_compute_ids(table, key, &chunk_id, &bin_id);
+ bin_choice = efd_get_choice(table, socket_id, chunk_id, bin_id);
+ group_id = efd_bin_to_group[bin_choice][bin_id];
+ group = &chunks[chunk_id].groups[group_id];
+
+ return efd_lookup_internal(group,
+ EFD_HASHFUNCA(key, table),
+ EFD_HASHFUNCB(key, table),
+ table->cmp_fn);
+}
+
+void rte_efd_lookup_bulk(const struct rte_efd_table * const table,
+ const unsigned int socket_id, const int num_keys,
+ const void **key_list, efd_value_t * const value_list)
+{
+ int i;
+ uint32_t chunk_id_list[RTE_EFD_BURST_MAX];
+ uint32_t bin_id_list[RTE_EFD_BURST_MAX];
+ uint8_t bin_choice_list[RTE_EFD_BURST_MAX];
+ uint32_t group_id_list[RTE_EFD_BURST_MAX];
+ struct efd_online_group_entry *group;
+
+ struct efd_online_chunk *chunks = table->chunks[socket_id];
+
+ for (i = 0; i < num_keys; i++) {
+ efd_compute_ids(table, key_list[i], &chunk_id_list[i],
+ &bin_id_list[i]);
+ rte_prefetch0(&chunks[chunk_id_list[i]].bin_choice_list);
+ }
+
+ for (i = 0; i < num_keys; i++) {
+ bin_choice_list[i] = efd_get_choice(table, socket_id,
+ chunk_id_list[i], bin_id_list[i]);
+ group_id_list[i] =
+ efd_bin_to_group[bin_choice_list[i]][bin_id_list[i]];
+ group = &chunks[chunk_id_list[i]].groups[group_id_list[i]];
+ rte_prefetch0(group);
+ }
+
+ for (i = 0; i < num_keys; i++) {
+ group = &chunks[chunk_id_list[i]].groups[group_id_list[i]];
+ value_list[i] = efd_lookup_internal(group,
+ EFD_HASHFUNCA(key_list[i], table),
+ EFD_HASHFUNCB(key_list[i], table),
+ table->cmp_fn);
+ }
+}
diff --git a/lib/librte_efd/rte_efd.h b/lib/librte_efd/rte_efd.h
new file mode 100644
index 0000000..a728096
--- /dev/null
+++ b/lib/librte_efd/rte_efd.h
@@ -0,0 +1,294 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _RTE_EFD_H_
+#define _RTE_EFD_H_
+
+/**
+ * @file
+ *
+ * RTE EFD Table
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*************************************************************************
+ * User selectable constants
+ *************************************************************************/
+
+/*
+ * If possible, best lookup performance will be achieved by ensuring that
+ * the entire table fits in the L3 cache.
+ *
+ * Some formulas for calculating various sizes are listed below:
+ *
+ * # of chunks =
+ * 2 ^ (ceiling(log2((requested # of rules) /
+ * (EFD_CHUNK_NUM_GROUPS * EFD_TARGET_GROUP_NUM_RULES))))
+ *
+ * Target # of rules = (# of chunks) * EFD_CHUNK_NUM_GROUPS *
+ * EFD_TARGET_GROUP_NUM_RULES
+ *
+ * Group Size (in bytes) = 4 (per value bit)
+ *
+ * Table size (in bytes) = RTE_EFD_VALUE_NUM_BITS * (# of chunks) *
+ * EFD_CHUNK_NUM_GROUPS * (group size)
+ */
+
+/**
+ * !!! This parameter should be adjusted for your application !!!
+ *
+ * This parameter adjusts the number of bits of value that can be
+ * stored in the table.
+ * For example, setting the number of bits to 3 will allow storing 8 values
+ * in the table (between 0 and 7).
+ *
+ * This number directly affects the performance of both lookups and insertion.
+ * In general, performance decreases as more bits are stored in the table.
+ *
+ * This number is directly proportional to the size of the online region
+ * used for lookups.
+ *
+ * Note that due to the way the CPU operates on memory, best lookup performance
+ * will be achieved when RTE_EFD_VALUE_NUM_BITS is a multiple of 8.
+ * These values align the hash indexes on 16-byte boundaries.
+ * The greatest performance drop is moving from 8->9 bits, 16->17 bits, etc.
+ *
+ * This value must be between 1 and 32
+ */
+#ifndef RTE_EFD_VALUE_NUM_BITS
+#define RTE_EFD_VALUE_NUM_BITS (8)
+#endif
+
+/*
+ * EFD_TARGET_GROUP_NUM_RULES:
+ * Adjusts how many groups/chunks are allocated at table creation time
+ * to support the requested number of rules. Higher values pack entries
+ * more tightly in memory, resulting in a smaller memory footprint
+ * for the online table.
+ * This comes at the cost of lower insert/update performance.
+ *
+ * EFD_MAX_GROUP_NUM_RULES:
+ * This adjusts the amount of offline memory allocated to store key/value
+ * pairs for the table. The recommended numbers are upper-bounds for
+ * this parameter
+ * - any higher and it becomes very unlikely that a perfect hash function
+ * can be found for that group size. This value should be at
+ * least 40% larger than EFD_TARGET_GROUP_NUM_RULES
+ *
+ * Recommended values for various lookuptable and hashfunc sizes are:
+ *
+ * HASH_FUNC_SIZE = 16, LOOKUPTBL_SIZE = 16:
+ * EFD_TARGET_GROUP_NUM_RULES = 22
+ * EFD_MAX_GROUP_NUM_RULES = 28
+ */
+#define EFD_TARGET_GROUP_NUM_RULES (22)
+#define EFD_MAX_GROUP_NUM_RULES (28LU)
+
+#define EFD_MIN_BALANCED_NUM_RULES 5
+
+/**
+ * Maximum number of keys that can be looked up in one call to efd_lookup_bulk
+ */
+#ifndef RTE_EFD_BURST_MAX
+#define RTE_EFD_BURST_MAX (32)
+#endif
+
+/** Maximum number of characters in efd name.*/
+#define RTE_EFD_NAMESIZE 32
+
+#if (RTE_EFD_VALUE_NUM_BITS > 0 && RTE_EFD_VALUE_NUM_BITS <= 8)
+typedef uint8_t efd_value_t;
+#elif (RTE_EFD_VALUE_NUM_BITS > 8 && RTE_EFD_VALUE_NUM_BITS <= 16)
+typedef uint16_t efd_value_t;
+#elif (RTE_EFD_VALUE_NUM_BITS > 16 && RTE_EFD_VALUE_NUM_BITS <= 32)
+typedef uint32_t efd_value_t;
+#else
+#error("RTE_EFD_VALUE_NUM_BITS must be in the range [1:32]")
+#endif
+
+/**
+ * Creates an EFD table with a single offline region and multiple per-socket
+ * internally-managed copies of the online table used for lookups
+ *
+ * @param name
+ * EFD table name
+ * @param max_num_rules
+ * Minimum number of rules the table should be sized to hold.
+ * Will be rounded up to the next smallest valid table size
+ * @param online_cpu_socket_bitmask
+ * Bitmask specifying which sockets should get a copy of the online table.
+ * LSB = socket 0, etc.
+ * @param offline_cpu_socket
+ * Identifies the socket where the offline table will be allocated
+ * (and most efficiently accessed in the case of updates/insertions)
+ *
+ * @return
+ * EFD table, or NULL if table allocation failed or the bitmask is invalid
+ */
+struct rte_efd_table *
+rte_efd_create(const char *name, uint32_t max_num_rules, uint32_t key_len,
+ uint8_t online_cpu_socket_bitmask, uint8_t offline_cpu_socket);
+
+/**
+ * Releases the resources from an EFD table
+ *
+ * @param table
+ * Table to free
+ */
+void
+rte_efd_free(struct rte_efd_table *table);
+
+/**
+ * Find an existing EFD table object and return a pointer to it.
+ *
+ * @param name
+ * Name of the EFD table as passed to rte_efd_create()
+ * @return
+ * Pointer to EFD table or NULL if object not found
+ * with rte_errno set appropriately. Possible rte_errno values include:
+ * - ENOENT - value not available for return
+ */
+struct rte_efd_table*
+rte_efd_find_existing(const char *name);
+
+#define RTE_EFD_UPDATE_WARN_GROUP_FULL (1)
+#define RTE_EFD_UPDATE_NO_CHANGE (2)
+#define RTE_EFD_UPDATE_FAILED (3)
+
+/**
+ * Computes an updated table entry for the supplied key/value pair.
+ * The update is then immediately applied to the provided table and
+ * all socket-local copies of the chunks are updated.
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param key
+ * EFD table key to modify
+ * @param value
+ * Value to associate with the key
+ *
+ * @return
+ * RTE_EFD_UPDATE_WARN_GROUP_FULL
+ * Operation is insert, and the last available space in the
+ * key's group was just used
+ * Future inserts may fail as groups fill up
+ * This operation was still successful, and entry contains a valid update
+ * RTE_EFD_UPDATE_FAILED
+ * Either the EFD failed to find a suitable perfect hash or the group was full
+ * This is a fatal error, and the table is now in an indeterminite state
+ * RTE_EFD_UPDATE_NO_CHANGE
+ * Operation resulted in no change to the table (same value already exists)
+ * 0 - success
+ */
+int
+rte_efd_update(struct rte_efd_table *table, unsigned int socket_id,
+ const void *key, efd_value_t value);
+
+/**
+ * Removes any value currently associated with the specified key from the table
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param key
+ * EFD table key to delete
+ * @param prev_value
+ * If not NULL, will store the previous value here before deleting it
+ *
+ * @return
+ * 0 - successfully found and deleted the key
+ * nonzero otherwise
+ */
+int
+rte_efd_delete(struct rte_efd_table *table, unsigned int socket_id,
+ const void *key, efd_value_t *prev_value);
+
+/**
+ * Looks up the value associated with a key
+ *
+ * NOTE: Lookups will *always* succeed - this is a property of
+ * using a perfect hash table.
+ * If the specified key was never inserted, a pseudorandom answer will be returned.
+ * There is no way to know based on the lookup if the key was ever inserted
+ * originally, so this must be tracked elsewhere.
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param key
+ * EFD table key to look up
+ *
+ * @return
+ * Value associated with the key, or random junk if they key was never inserted
+ */
+efd_value_t
+rte_efd_lookup(const struct rte_efd_table *table, unsigned int socket_id,
+ const void *key);
+
+/**
+ * Looks up the value associated with several keys.
+ *
+ * NOTE: Lookups will *always* succeed - this is a property of
+ * using a perfect hash table.
+ * If the specified key was never inserted, a pseudorandom answer will be returned.
+ * There is no way to know based on the lookup if the key was ever inserted
+ * originally, so this must be tracked elsewhere.
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param num_keys
+ * Number of keys in the key_list array, must be less than RTE_EFD_BURST_MAX
+ * @param key_list
+ * Array of num_keys pointers which point to keys to look up
+ * @param value_list
+ * Array of size num_keys where lookup values will be stored
+ */
+void
+rte_efd_lookup_bulk(const struct rte_efd_table *table, unsigned int socket_id,
+ int num_keys, const void **key_list,
+ efd_value_t *value_list);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RTE_EFD_H_ */
diff --git a/lib/librte_efd/rte_efd_version.map b/lib/librte_efd/rte_efd_version.map
new file mode 100644
index 0000000..91810b3
--- /dev/null
+++ b/lib/librte_efd/rte_efd_version.map
@@ -0,0 +1,12 @@
+DPDK_17.02 {
+ global:
+
+ rte_efd_create;
+ rte_efd_delete;
+ rte_efd_free;
+ rte_efd_lookup;
+ rte_efd_lookup_bulk;
+ rte_efd_update;
+
+ local: *;
+};
diff --git a/mk/rte.app.mk b/mk/rte.app.mk
index f75f0e2..ed1e68a 100644
--- a/mk/rte.app.mk
+++ b/mk/rte.app.mk
@@ -1,6 +1,6 @@
# BSD LICENSE
#
-# Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+# Copyright(c) 2010-2017 Intel Corporation. All rights reserved.
# Copyright(c) 2014-2015 6WIND S.A.
# All rights reserved.
#
@@ -86,6 +86,7 @@ _LDLIBS-y += --whole-archive
_LDLIBS-$(CONFIG_RTE_LIBRTE_TIMER) += -lrte_timer
_LDLIBS-$(CONFIG_RTE_LIBRTE_HASH) += -lrte_hash
+_LDLIBS-$(CONFIG_RTE_LIBRTE_EFD) += -lrte_efd
_LDLIBS-$(CONFIG_RTE_LIBRTE_VHOST) += -lrte_vhost
_LDLIBS-$(CONFIG_RTE_LIBRTE_KVARGS) += -lrte_kvargs
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v3 2/5] app/test: add EFD functional and perf tests
2017-01-12 22:15 ` [dpdk-dev] [PATCH v3 " Pablo de Lara
2017-01-12 22:15 ` [dpdk-dev] [PATCH v3 1/5] efd: new Elastic Flow Distributor library Pablo de Lara
@ 2017-01-12 22:15 ` Pablo de Lara
2017-01-12 22:15 ` [dpdk-dev] [PATCH v3 3/5] examples/flow_distributor: sample app to demonstrate EFD usage Pablo de Lara
` (3 subsequent siblings)
5 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-12 22:15 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Byron Marohn, Karla Saur, Saikrishna Edupuganti
Signed-off-by: Byron Marohn <byron.marohn@intel.com>
Signed-off-by: Karla Saur <karla.saur@intel.com>
Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 1 +
app/test/Makefile | 5 +-
app/test/test_efd.c | 494 +++++++++++++++++++++++++++++++++++++++++++++++
app/test/test_efd_perf.c | 407 ++++++++++++++++++++++++++++++++++++++
4 files changed, 906 insertions(+), 1 deletion(-)
create mode 100644 app/test/test_efd.c
create mode 100644 app/test/test_efd_perf.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 9c60d67..d812962 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -532,6 +532,7 @@ EFD
M: Byron Marohn <byron.marohn@intel.com>
M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
F: lib/librte_efd/
+F: app/test/test_efd*
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
diff --git a/app/test/Makefile b/app/test/Makefile
index 5be023a..9de301f 100644
--- a/app/test/Makefile
+++ b/app/test/Makefile
@@ -1,6 +1,6 @@
# BSD LICENSE
#
-# Copyright(c) 2010-2015 Intel Corporation. All rights reserved.
+# Copyright(c) 2010-2017 Intel Corporation. All rights reserved.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -123,6 +123,9 @@ SRCS-y += test_logs.c
SRCS-y += test_memcpy.c
SRCS-y += test_memcpy_perf.c
+SRCS-$(CONFIG_RTE_LIBRTE_EFD) += test_efd.c
+SRCS-$(CONFIG_RTE_LIBRTE_EFD) += test_efd_perf.c
+
SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_hash.c
SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_thash.c
SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_hash_perf.c
diff --git a/app/test/test_efd.c b/app/test/test_efd.c
new file mode 100644
index 0000000..d5c3bd9
--- /dev/null
+++ b/app/test/test_efd.c
@@ -0,0 +1,494 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rte_memcpy.h>
+#include <rte_malloc.h>
+#include <rte_efd.h>
+#include <rte_byteorder.h>
+#include <rte_random.h>
+#include <rte_debug.h>
+#include <rte_ip.h>
+
+#include "test.h"
+
+#define EFD_TEST_KEY_LEN 8
+#define TABLE_SIZE (1 << 21)
+#define ITERATIONS 3
+static unsigned int test_socket_id;
+
+/* 5-tuple key type */
+struct flow_key {
+ uint32_t ip_src;
+ uint32_t ip_dst;
+ uint16_t port_src;
+ uint16_t port_dst;
+ uint8_t proto;
+} __attribute__((packed));
+/*
+ * Print out result of unit test efd operation.
+ */
+#if defined(UNIT_TEST_EFD_VERBOSE)
+
+static void print_key_info(const char *msg, const struct flow_key *key,
+ efd_value_t val)
+{
+ const uint8_t *p = (const uint8_t *) key;
+ unsigned int i;
+
+ printf("%s key:0x", msg);
+ for (i = 0; i < sizeof(struct flow_key); i++)
+ printf("%02X", p[i]);
+
+ printf(" @ val %d\n", val);
+}
+#else
+
+static void print_key_info(__attribute__((unused)) const char *msg,
+ __attribute__((unused)) const struct flow_key *key,
+ __attribute__((unused)) efd_value_t val)
+{
+}
+#endif
+
+/* Keys used by unit test functions */
+static struct flow_key keys[5] = {
+ {
+ .ip_src = IPv4(0x03, 0x02, 0x01, 0x00),
+ .ip_dst = IPv4(0x07, 0x06, 0x05, 0x04),
+ .port_src = 0x0908,
+ .port_dst = 0x0b0a,
+ .proto = 0x0c,
+ },
+ {
+ .ip_src = IPv4(0x13, 0x12, 0x11, 0x10),
+ .ip_dst = IPv4(0x17, 0x16, 0x15, 0x14),
+ .port_src = 0x1918,
+ .port_dst = 0x1b1a,
+ .proto = 0x1c,
+ },
+ {
+ .ip_src = IPv4(0x23, 0x22, 0x21, 0x20),
+ .ip_dst = IPv4(0x27, 0x26, 0x25, 0x24),
+ .port_src = 0x2928,
+ .port_dst = 0x2b2a,
+ .proto = 0x2c,
+ },
+ {
+ .ip_src = IPv4(0x33, 0x32, 0x31, 0x30),
+ .ip_dst = IPv4(0x37, 0x36, 0x35, 0x34),
+ .port_src = 0x3938,
+ .port_dst = 0x3b3a,
+ .proto = 0x3c,
+ },
+ {
+ .ip_src = IPv4(0x43, 0x42, 0x41, 0x40),
+ .ip_dst = IPv4(0x47, 0x46, 0x45, 0x44),
+ .port_src = 0x4948,
+ .port_dst = 0x4b4a,
+ .proto = 0x4c,
+ }
+};
+/* Array to store the data */
+efd_value_t data[5];
+
+static inline uint8_t efd_get_all_sockets_bitmask(void)
+{
+ uint8_t all_cpu_sockets_bitmask = 0;
+ unsigned int i;
+ unsigned int next_lcore = rte_get_master_lcore();
+ const int val_true = 1, val_false = 0;
+ for (i = 0; i < rte_lcore_count(); i++) {
+ all_cpu_sockets_bitmask |= 1 << rte_lcore_to_socket_id(next_lcore);
+ next_lcore = rte_get_next_lcore(next_lcore, val_false, val_true);
+ }
+
+ return all_cpu_sockets_bitmask;
+}
+
+/*
+ * Basic sequence of operations for a single key:
+ * - add
+ * - lookup (hit)
+ * - delete
+ * Note: lookup (miss) is not applicable since this is a filter
+ */
+static int test_add_delete(void)
+{
+ struct rte_efd_table *handle;
+ /* test with standard add/lookup/delete functions */
+ efd_value_t prev_value;
+ printf("Entering %s\n", __func__);
+
+ handle = rte_efd_create("test_add_delete",
+ TABLE_SIZE, sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the EFD table\n");
+
+ data[0] = mrand48() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id, &keys[0],
+ data[0]),
+ "Error inserting the key");
+ print_key_info("Add", &keys[0], data[0]);
+
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id, &keys[0]),
+ data[0],
+ "failed to find key");
+
+ TEST_ASSERT_SUCCESS(rte_efd_delete(handle, test_socket_id, &keys[0],
+ &prev_value),
+ "failed to delete key");
+ TEST_ASSERT_EQUAL(prev_value, data[0],
+ "failed to delete the expected value, got %d, "
+ "expected %d", prev_value, data[0]);
+ print_key_info("Del", &keys[0], data[0]);
+
+ rte_efd_free(handle);
+
+ return 0;
+}
+
+/*
+ * Sequence of operations for a single key:
+ * - add
+ * - lookup: hit
+ * - add: update
+ * - lookup: hit (updated data)
+ * - delete: hit
+ */
+static int test_add_update_delete(void)
+{
+ struct rte_efd_table *handle;
+ printf("Entering %s\n", __func__);
+ /* test with standard add/lookup/delete functions */
+ efd_value_t prev_value;
+ data[1] = mrand48() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+
+ handle = rte_efd_create("test_add_update_delete", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the efd table\n");
+
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id, &keys[1],
+ data[1]), "Error inserting the key");
+ print_key_info("Add", &keys[1], data[1]);
+
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id, &keys[1]),
+ data[1], "failed to find key");
+ print_key_info("Lkp", &keys[1], data[1]);
+
+ data[1] = data[1] + 1;
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id, &keys[1],
+ data[1]), "Error re-inserting the key");
+ print_key_info("Add", &keys[1], data[1]);
+
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id, &keys[1]),
+ data[1], "failed to find key");
+ print_key_info("Lkp", &keys[1], data[1]);
+
+ TEST_ASSERT_SUCCESS(rte_efd_delete(handle, test_socket_id, &keys[1],
+ &prev_value), "failed to delete key");
+ TEST_ASSERT_EQUAL(prev_value, data[1],
+ "failed to delete the expected value, got %d, "
+ "expected %d", prev_value, data[1]);
+ print_key_info("Del", &keys[1], data[1]);
+
+
+ rte_efd_free(handle);
+ return 0;
+}
+
+/*
+ * Sequence of operations for find existing EFD table
+ *
+ * - create table
+ * - find existing table: hit
+ * - find non-existing table: miss
+ *
+ */
+static int test_efd_find_existing(void)
+{
+ struct rte_efd_table *handle = NULL, *result = NULL;
+
+ printf("Entering %s\n", __func__);
+
+ /* Create EFD table. */
+ handle = rte_efd_create("efd_find_existing", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the efd table\n");
+
+ /* Try to find existing EFD table */
+ result = rte_efd_find_existing("efd_find_existing");
+ TEST_ASSERT_EQUAL(result, handle, "could not find existing efd table");
+
+ /* Try to find non-existing EFD table */
+ result = rte_efd_find_existing("efd_find_non_existing");
+ TEST_ASSERT_NULL(result, "found table that shouldn't exist");
+
+ /* Cleanup. */
+ rte_efd_free(handle);
+
+ return 0;
+}
+
+/*
+ * Sequence of operations for 5 keys
+ * - add keys
+ * - lookup keys: hit (bulk)
+ * - add keys (update)
+ * - lookup keys: hit (updated data)
+ * - delete keys : hit
+ */
+static int test_five_keys(void)
+{
+ struct rte_efd_table *handle;
+ const void *key_array[5] = {0};
+ efd_value_t result[5] = {0};
+ efd_value_t prev_value;
+ unsigned int i;
+ printf("Entering %s\n", __func__);
+
+ handle = rte_efd_create("test_five_keys", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the efd table\n");
+
+ /* Setup data */
+ for (i = 0; i < 5; i++)
+ data[i] = mrand48() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+
+ /* Add */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id,
+ &keys[i], data[i]),
+ "Error inserting the key");
+ print_key_info("Add", &keys[i], data[i]);
+ }
+
+ /* Lookup */
+ for (i = 0; i < 5; i++)
+ key_array[i] = &keys[i];
+
+ rte_efd_lookup_bulk(handle, test_socket_id, 5,
+ (const void **) (void *) &key_array, result);
+
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_EQUAL(result[i], data[i],
+ "bulk: failed to find key. Expected %d, got %d",
+ data[i], result[i]);
+ print_key_info("Lkp", &keys[i], data[i]);
+ }
+
+ /* Modify data (bulk) */
+ for (i = 0; i < 5; i++)
+ data[i] = data[i] + 1;
+
+ /* Add - update */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id,
+ &keys[i], data[i]),
+ "Error inserting the key");
+ print_key_info("Add", &keys[i], data[i]);
+ }
+
+ /* Lookup */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id,
+ &keys[i]), data[i],
+ "failed to find key");
+ print_key_info("Lkp", &keys[i], data[i]);
+ }
+
+ /* Delete */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_SUCCESS(rte_efd_delete(handle, test_socket_id,
+ &keys[i], &prev_value),
+ "failed to delete key");
+ TEST_ASSERT_EQUAL(prev_value, data[i],
+ "failed to delete the expected value, got %d, "
+ "expected %d", prev_value, data[i]);
+ print_key_info("Del", &keys[i], data[i]);
+ }
+
+
+ rte_efd_free(handle);
+
+ return 0;
+}
+
+/*
+ * Test to see the average table utilization (entries added/max entries)
+ * before hitting a random entry that cannot be added
+ */
+static int test_average_table_utilization(void)
+{
+ struct rte_efd_table *handle = NULL;
+ uint32_t num_rules_in = TABLE_SIZE;
+ uint8_t simple_key[EFD_TEST_KEY_LEN];
+ unsigned int i, j;
+ unsigned int added_keys, average_keys_added = 0;
+
+ printf("Evaluating table utilization and correctness, please wait\n");
+ fflush(stdout);
+
+ for (j = 0; j < ITERATIONS; j++) {
+ handle = rte_efd_create("test_efd", num_rules_in,
+ EFD_TEST_KEY_LEN, efd_get_all_sockets_bitmask(),
+ test_socket_id);
+ if (handle == NULL) {
+ printf("efd table creation failed\n");
+ return -1;
+ }
+
+ unsigned int succeeded = 0;
+ unsigned int lost_keys = 0;
+
+ /* Add random entries until key cannot be added */
+ for (added_keys = 0; added_keys < num_rules_in; added_keys++) {
+
+ for (i = 0; i < EFD_TEST_KEY_LEN; i++)
+ simple_key[i] = rte_rand() & 0xFF;
+
+ efd_value_t val = simple_key[0];
+
+ if (rte_efd_update(handle, test_socket_id, simple_key,
+ val))
+ break; /* continue;*/
+ if (rte_efd_lookup(handle, test_socket_id, simple_key)
+ != val)
+ lost_keys++;
+ else
+ succeeded++;
+ }
+
+ average_keys_added += succeeded;
+
+ /* Reset the table */
+ rte_efd_free(handle);
+
+ /* Print progress on operations */
+ printf("Added %10u Succeeded %10u Lost %10u\n",
+ added_keys, succeeded, lost_keys);
+ fflush(stdout);
+ }
+
+ average_keys_added /= ITERATIONS;
+
+ printf("\nAverage table utilization = %.2f%% (%u/%u)\n",
+ ((double) average_keys_added / num_rules_in * 100),
+ average_keys_added, num_rules_in);
+
+ return 0;
+}
+
+/*
+ * Do tests for EFD creation with bad parameters.
+ */
+static int test_efd_creation_with_bad_parameters(void)
+{
+ struct rte_efd_table *handle, *tmp;
+ printf("Entering %s, **Errors are expected **\n", __func__);
+
+ handle = rte_efd_create("creation_with_bad_parameters_0", TABLE_SIZE, 0,
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ if (handle != NULL) {
+ rte_efd_free(handle);
+ printf("Impossible creating EFD table successfully "
+ "if key_len in parameter is zero\n");
+ return -1;
+ }
+
+ handle = rte_efd_create("creation_with_bad_parameters_1", TABLE_SIZE,
+ sizeof(struct flow_key), 0, test_socket_id);
+ if (handle != NULL) {
+ rte_efd_free(handle);
+ printf("Impossible creating EFD table successfully "
+ "with invalid socket bitmask\n");
+ return -1;
+ }
+
+ handle = rte_efd_create("creation_with_bad_parameters_2", TABLE_SIZE,
+ sizeof(struct flow_key), efd_get_all_sockets_bitmask(),
+ 255);
+ if (handle != NULL) {
+ rte_efd_free(handle);
+ printf("Impossible creating EFD table successfully "
+ "with invalid socket\n");
+ return -1;
+ }
+
+ /* test with same name should fail */
+ handle = rte_efd_create("same_name", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), 0);
+ if (handle == NULL) {
+ printf("Cannot create first EFD table with 'same_name'\n");
+ return -1;
+ }
+ tmp = rte_efd_create("same_name", TABLE_SIZE, sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), 0);
+ if (tmp != NULL) {
+ printf("Creation of EFD table with same name should fail\n");
+ rte_efd_free(handle);
+ rte_efd_free(tmp);
+ return -1;
+ }
+ rte_efd_free(handle);
+
+ printf("# Test successful. No more errors expected\n");
+
+ return 0;
+}
+
+static int
+test_efd(void)
+{
+
+ /* Unit tests */
+ if (test_add_delete() < 0)
+ return -1;
+ if (test_efd_find_existing() < 0)
+ return -1;
+ if (test_add_update_delete() < 0)
+ return -1;
+ if (test_five_keys() < 0)
+ return -1;
+ if (test_efd_creation_with_bad_parameters() < 0)
+ return -1;
+ if (test_average_table_utilization() < 0)
+ return -1;
+
+ return 0;
+}
+
+REGISTER_TEST_COMMAND(efd_autotest, test_efd);
diff --git a/app/test/test_efd_perf.c b/app/test/test_efd_perf.c
new file mode 100644
index 0000000..998a25b
--- /dev/null
+++ b/app/test/test_efd_perf.c
@@ -0,0 +1,407 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <inttypes.h>
+
+#include <rte_lcore.h>
+#include <rte_cycles.h>
+#include <rte_malloc.h>
+#include <rte_random.h>
+#include <rte_efd.h>
+#include <rte_memcpy.h>
+#include <rte_thash.h>
+
+#include "test.h"
+#define NUM_KEYSIZES 10
+#define NUM_SHUFFLES 10
+#define MAX_KEYSIZE 64
+#define MAX_ENTRIES (1 << 19)
+#define KEYS_TO_ADD (MAX_ENTRIES * 3 / 4) /* 75% table utilization */
+#define NUM_LOOKUPS (KEYS_TO_ADD * 5) /* Loop among keys added, several times */
+static unsigned int test_socket_id;
+
+static inline uint8_t efd_get_all_sockets_bitmask(void)
+{
+ uint8_t all_cpu_sockets_bitmask = 0;
+ unsigned int i;
+ unsigned int next_lcore = rte_get_master_lcore();
+ const int val_true = 1, val_false = 0;
+ for (i = 0; i < rte_lcore_count(); i++) {
+ all_cpu_sockets_bitmask |= 1 << rte_lcore_to_socket_id(next_lcore);
+ next_lcore = rte_get_next_lcore(next_lcore, val_false, val_true);
+ }
+
+ return all_cpu_sockets_bitmask;
+}
+
+enum operations {
+ ADD = 0,
+ LOOKUP,
+ LOOKUP_MULTI,
+ DELETE,
+ NUM_OPERATIONS
+};
+
+struct efd_perf_params {
+ struct rte_efd_table *efd_table;
+ uint32_t key_size;
+ unsigned int cycle;
+};
+
+static uint32_t hashtest_key_lens[] = {
+ /* standard key sizes */
+ 4, 8, 16, 32, 48, 64,
+ /* IPv4 SRC + DST + protocol, unpadded */
+ 9,
+ /* IPv4 5-tuple, unpadded */
+ 13,
+ /* IPv6 5-tuple, unpadded */
+ 37,
+ /* IPv6 5-tuple, padded to 8-byte boundary */
+ 40
+};
+
+/* Array to store number of cycles per operation */
+uint64_t cycles[NUM_KEYSIZES][NUM_OPERATIONS];
+
+/* Array to store the data */
+efd_value_t data[KEYS_TO_ADD];
+
+/* Array to store all input keys */
+uint8_t keys[KEYS_TO_ADD][MAX_KEYSIZE];
+
+/* Shuffle the keys that have been added, so lookups will be totally random */
+static void
+shuffle_input_keys(struct efd_perf_params *params)
+{
+ efd_value_t temp_data;
+ unsigned int i;
+ uint32_t swap_idx;
+ uint8_t temp_key[MAX_KEYSIZE];
+
+ for (i = KEYS_TO_ADD - 1; i > 0; i--) {
+ swap_idx = rte_rand() % i;
+
+ memcpy(temp_key, keys[i], hashtest_key_lens[params->cycle]);
+ temp_data = data[i];
+
+ memcpy(keys[i], keys[swap_idx], hashtest_key_lens[params->cycle]);
+ data[i] = data[swap_idx];
+
+ memcpy(keys[swap_idx], temp_key, hashtest_key_lens[params->cycle]);
+ data[swap_idx] = temp_data;
+ }
+}
+
+static int key_compare(const void *key1, const void *key2)
+{
+ return memcmp(key1, key2, MAX_KEYSIZE);
+}
+
+/*
+ * TODO: we could "error proof" these as done in test_hash_perf.c ln 165:
+ *
+ * The current setup may give errors if too full in some cases which we check
+ * for. However, since EFD allows for ~99% capacity, these errors are rare for
+ * #"KEYS_TO_ADD" which is 75% capacity.
+ */
+static int
+setup_keys_and_data(struct efd_perf_params *params, unsigned int cycle)
+{
+ unsigned int i, j;
+ int num_duplicates;
+
+ params->key_size = hashtest_key_lens[cycle];
+ params->cycle = cycle;
+
+ /* Reset all arrays */
+ for (i = 0; i < params->key_size; i++)
+ keys[0][i] = 0;
+
+ /* Generate a list of keys, some of which may be duplicates */
+ for (i = 0; i < KEYS_TO_ADD; i++) {
+ for (j = 0; j < params->key_size; j++)
+ keys[i][j] = rte_rand() & 0xFF;
+
+ data[i] = rte_rand() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+ }
+
+ /* Remove duplicates from the keys array */
+ do {
+ num_duplicates = 0;
+
+ /* Sort the list of keys to make it easier to find duplicates */
+ qsort(keys, KEYS_TO_ADD, MAX_KEYSIZE, key_compare);
+
+ /* Sift through the list of keys and look for duplicates */
+ int num_duplicates = 0;
+ for (i = 0; i < KEYS_TO_ADD - 1; i++) {
+ if (memcmp(keys[i], keys[i + 1], params->key_size) == 0) {
+ /* This key already exists, try again */
+ num_duplicates++;
+ for (j = 0; j < params->key_size; j++)
+ keys[i][j] = rte_rand() & 0xFF;
+ }
+ }
+ } while (num_duplicates != 0);
+
+ /* Shuffle the random values again */
+ shuffle_input_keys(params);
+
+ params->efd_table = rte_efd_create("test_efd_perf",
+ MAX_ENTRIES, params->key_size,
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(params->efd_table, "Error creating the efd table\n");
+
+ return 0;
+}
+
+static int
+timed_adds(struct efd_perf_params *params)
+{
+ const uint64_t start_tsc = rte_rdtsc();
+ unsigned int i, a;
+ int32_t ret;
+
+ for (i = 0; i < KEYS_TO_ADD; i++) {
+ ret = rte_efd_update(params->efd_table, test_socket_id, keys[i],
+ data[i]);
+ if (ret != 0) {
+ printf("Error %d in rte_efd_update - key=0x", ret);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x", keys[i][a]);
+ printf(" value=%d\n", data[i]);
+
+ return -1;
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][ADD] = time_taken / KEYS_TO_ADD;
+ return 0;
+}
+
+static int
+timed_lookups(struct efd_perf_params *params)
+{
+ unsigned int i, j, a;
+ const uint64_t start_tsc = rte_rdtsc();
+ efd_value_t ret_data;
+
+ for (i = 0; i < NUM_LOOKUPS / KEYS_TO_ADD; i++) {
+ for (j = 0; j < KEYS_TO_ADD; j++) {
+ ret_data = rte_efd_lookup(params->efd_table,
+ test_socket_id, keys[j]);
+ if (ret_data != data[j]) {
+ printf("Value mismatch using rte_efd_lookup: "
+ "key #%d (0x", i);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x", keys[i][a]);
+ printf(")\n");
+ printf(" Expected %d, got %d\n", data[i],
+ ret_data);
+
+ return -1;
+ }
+
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][LOOKUP] = time_taken / NUM_LOOKUPS;
+
+ return 0;
+}
+
+static int
+timed_lookups_multi(struct efd_perf_params *params)
+{
+ unsigned int i, j, k, a;
+ efd_value_t result[RTE_EFD_BURST_MAX] = {0};
+ const void *keys_burst[RTE_EFD_BURST_MAX];
+ const uint64_t start_tsc = rte_rdtsc();
+
+ for (i = 0; i < NUM_LOOKUPS / KEYS_TO_ADD; i++) {
+ for (j = 0; j < KEYS_TO_ADD / RTE_EFD_BURST_MAX; j++) {
+ for (k = 0; k < RTE_EFD_BURST_MAX; k++)
+ keys_burst[k] = keys[j * RTE_EFD_BURST_MAX + k];
+
+ rte_efd_lookup_bulk(params->efd_table, test_socket_id,
+ RTE_EFD_BURST_MAX,
+ keys_burst, result);
+
+ for (k = 0; k < RTE_EFD_BURST_MAX; k++) {
+ uint32_t data_idx = j * RTE_EFD_BURST_MAX + k;
+ if (result[k] != data[data_idx]) {
+ printf("Value mismatch using "
+ "rte_efd_lookup_bulk: key #%d "
+ "(0x", i);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x",
+ keys[data_idx][a]);
+ printf(")\n");
+ printf(" Expected %d, got %d\n",
+ data[data_idx], result[k]);
+
+ return -1;
+ }
+ }
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][LOOKUP_MULTI] = time_taken / NUM_LOOKUPS;
+
+ return 0;
+}
+
+static int
+timed_deletes(struct efd_perf_params *params)
+{
+ unsigned int i, a;
+ const uint64_t start_tsc = rte_rdtsc();
+ int32_t ret;
+
+ for (i = 0; i < KEYS_TO_ADD; i++) {
+ ret = rte_efd_delete(params->efd_table, test_socket_id, keys[i],
+ NULL);
+
+ if (ret != 0) {
+ printf("Error %d in rte_efd_delete - key=0x", ret);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x", keys[i][a]);
+ printf("\n");
+
+ return -1;
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][DELETE] = time_taken / KEYS_TO_ADD;
+
+ return 0;
+}
+
+static void
+perform_frees(struct efd_perf_params *params)
+{
+ if (params->efd_table != NULL) {
+ rte_efd_free(params->efd_table);
+ params->efd_table = NULL;
+ }
+}
+
+static int
+exit_with_fail(const char *testname, struct efd_perf_params *params,
+ unsigned int i)
+{
+
+ printf("<<<<<Test %s failed at keysize %d iteration %d >>>>>\n",
+ testname, hashtest_key_lens[params->cycle], i);
+ perform_frees(params);
+ return -1;
+}
+
+static int
+run_all_tbl_perf_tests(void)
+{
+ unsigned int i, j;
+ struct efd_perf_params params;
+
+ printf("Measuring performance, please wait\n");
+ fflush(stdout);
+
+ test_socket_id = rte_socket_id();
+
+ for (i = 0; i < NUM_KEYSIZES; i++) {
+
+ if (setup_keys_and_data(¶ms, i) < 0) {
+ printf("Could not create keys/data/table\n");
+ return -1;
+ }
+
+ if (timed_adds(¶ms) < 0)
+ return exit_with_fail("timed_adds", ¶ms, i);
+
+ for (j = 0; j < NUM_SHUFFLES; j++)
+ shuffle_input_keys(¶ms);
+
+ if (timed_lookups(¶ms) < 0)
+ return exit_with_fail("timed_lookups", ¶ms, i);
+
+ if (timed_lookups_multi(¶ms) < 0)
+ return exit_with_fail("timed_lookups_multi", ¶ms, i);
+
+ if (timed_deletes(¶ms) < 0)
+ return exit_with_fail("timed_deletes", ¶ms, i);
+
+ /* Print a dot to show progress on operations */
+ printf(".");
+ fflush(stdout);
+
+ perform_frees(¶ms);
+ }
+
+ printf("\nResults (in CPU cycles/operation)\n");
+ printf("-----------------------------------\n");
+ printf("\n%-18s%-18s%-18s%-18s%-18s\n",
+ "Keysize", "Add", "Lookup", "Lookup_bulk", "Delete");
+ for (i = 0; i < NUM_KEYSIZES; i++) {
+ printf("%-18d", hashtest_key_lens[i]);
+ for (j = 0; j < NUM_OPERATIONS; j++)
+ printf("%-18"PRIu64, cycles[i][j]);
+ printf("\n");
+ }
+ return 0;
+}
+
+static int
+test_efd_perf(void)
+{
+
+ if (run_all_tbl_perf_tests() < 0)
+ return -1;
+
+ return 0;
+}
+
+REGISTER_TEST_COMMAND(efd_perf_autotest, test_efd_perf);
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v3 3/5] examples/flow_distributor: sample app to demonstrate EFD usage
2017-01-12 22:15 ` [dpdk-dev] [PATCH v3 " Pablo de Lara
2017-01-12 22:15 ` [dpdk-dev] [PATCH v3 1/5] efd: new Elastic Flow Distributor library Pablo de Lara
2017-01-12 22:15 ` [dpdk-dev] [PATCH v3 2/5] app/test: add EFD functional and perf tests Pablo de Lara
@ 2017-01-12 22:15 ` Pablo de Lara
2017-01-12 22:15 ` [dpdk-dev] [PATCH v3 4/5] doc: add EFD library section in Programmers guide Pablo de Lara
` (2 subsequent siblings)
5 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-12 22:15 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Saikrishna Edupuganti
This new sample app, based on the client/server sample app,
shows the user an scenario using the EFD library.
It consists of:
- A front-end server which has an EFD table that stores the
node id for each flow key, which will distribute the incoming
packets to the different nodes
- A back-end node, which has a hash table where node checks,
after reading packets coming from the server, whether the packet
is meant to be used in such node, in which case it will be TXed,
or not, in which case, packet will be dropped.
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 1 +
doc/api/examples.dox | 4 +
examples/Makefile | 1 +
examples/flow_distributor/Makefile | 44 +++
examples/flow_distributor/distributor/Makefile | 57 ++++
examples/flow_distributor/distributor/args.c | 200 ++++++++++++
examples/flow_distributor/distributor/args.h | 39 +++
examples/flow_distributor/distributor/init.c | 371 ++++++++++++++++++++++
examples/flow_distributor/distributor/init.h | 76 +++++
examples/flow_distributor/distributor/main.c | 362 +++++++++++++++++++++
examples/flow_distributor/node/Makefile | 48 +++
examples/flow_distributor/node/node.c | 417 +++++++++++++++++++++++++
examples/flow_distributor/shared/common.h | 99 ++++++
13 files changed, 1719 insertions(+)
create mode 100644 examples/flow_distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/args.c
create mode 100644 examples/flow_distributor/distributor/args.h
create mode 100644 examples/flow_distributor/distributor/init.c
create mode 100644 examples/flow_distributor/distributor/init.h
create mode 100644 examples/flow_distributor/distributor/main.c
create mode 100644 examples/flow_distributor/node/Makefile
create mode 100644 examples/flow_distributor/node/node.c
create mode 100644 examples/flow_distributor/shared/common.h
diff --git a/MAINTAINERS b/MAINTAINERS
index d812962..b124f6e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -533,6 +533,7 @@ M: Byron Marohn <byron.marohn@intel.com>
M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
F: lib/librte_efd/
F: app/test/test_efd*
+F: examples/flow_distributor/
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
diff --git a/doc/api/examples.dox b/doc/api/examples.dox
index 1626852..c13e574 100644
--- a/doc/api/examples.dox
+++ b/doc/api/examples.dox
@@ -52,6 +52,10 @@
@example load_balancer/init.c
@example load_balancer/main.c
@example load_balancer/runtime.c
+@example flow_distributor/distributor/args.c
+@example flow_distributor/distributor/init.c
+@example flow_distributor/distributor/main.c
+@example flow_distributor/node/node.c
@example multi_process/client_server_mp/mp_client/client.c
@example multi_process/client_server_mp/mp_server/args.c
@example multi_process/client_server_mp/mp_server/init.c
diff --git a/examples/Makefile b/examples/Makefile
index d49c7f2..b404982 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -45,6 +45,7 @@ DIRS-y += dpdk_qat
endif
DIRS-y += ethtool
DIRS-y += exception_path
+DIRS-$(CONFIG_RTE_LIBRTE_EFD) += flow_distributor
DIRS-y += helloworld
DIRS-$(CONFIG_RTE_LIBRTE_PIPELINE) += ip_pipeline
ifeq ($(CONFIG_RTE_LIBRTE_LPM),y)
diff --git a/examples/flow_distributor/Makefile b/examples/flow_distributor/Makefile
new file mode 100644
index 0000000..5bae706
--- /dev/null
+++ b/examples/flow_distributor/Makefile
@@ -0,0 +1,44 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overridden by command line or environment
+RTE_TARGET ?= x86_64-native-linuxapp-gcc
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+DIRS-$(CONFIG_RTE_EXEC_ENV_LINUXAPP) += distributor
+DIRS-$(CONFIG_RTE_EXEC_ENV_LINUXAPP) += node
+
+include $(RTE_SDK)/mk/rte.extsubdir.mk
diff --git a/examples/flow_distributor/distributor/Makefile b/examples/flow_distributor/distributor/Makefile
new file mode 100644
index 0000000..8714151
--- /dev/null
+++ b/examples/flow_distributor/distributor/Makefile
@@ -0,0 +1,57 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overridden by command line or environment
+RTE_TARGET ?= x86_64-native-linuxapp-gcc
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+ifneq ($(CONFIG_RTE_EXEC_ENV), "linuxapp")
+$(error This application can only operate in a linuxapp environment, \
+please change the definition of the RTE_TARGET environment variable)
+endif
+
+# binary name
+APP = distributor
+
+# all source are stored in SRCS-y
+SRCS-y := main.c init.c args.c
+
+INC := $(wildcard *.h)
+
+CFLAGS += $(WERROR_FLAGS) -O3
+CFLAGS += -I$(SRCDIR)/../shared
+
+include $(RTE_SDK)/mk/rte.extapp.mk
diff --git a/examples/flow_distributor/distributor/args.c b/examples/flow_distributor/distributor/args.c
new file mode 100644
index 0000000..ee29203
--- /dev/null
+++ b/examples/flow_distributor/distributor/args.c
@@ -0,0 +1,200 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <getopt.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include <rte_memory.h>
+#include <rte_string_fns.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+/* 1M flows by default */
+#define DEFAULT_NUM_FLOWS 0x100000
+
+/* global var for number of nodes - extern in header */
+uint8_t num_nodes;
+/* global var for number of flows - extern in header */
+uint32_t num_flows = DEFAULT_NUM_FLOWS;
+
+static const char *progname;
+
+/**
+ * Prints out usage information to stdout
+ */
+static void
+usage(void)
+{
+ printf("%s [EAL options] -- -p PORTMASK -n NUM_NODES -f NUM_FLOWS\n"
+ " -p PORTMASK: hexadecimal bitmask of ports to use\n"
+ " -n NUM_NODES: number of node processes to use\n"
+ " -f NUM_FLOWS: number of flows to be added in the EFD table\n",
+ progname);
+}
+
+/**
+ * The ports to be used by the application are passed in
+ * the form of a bitmask. This function parses the bitmask
+ * and places the port numbers to be used into the port[]
+ * array variable
+ */
+static int
+parse_portmask(uint8_t max_ports, const char *portmask)
+{
+ char *end = NULL;
+ unsigned long pm;
+ uint8_t count = 0;
+
+ if (portmask == NULL || *portmask == '\0')
+ return -1;
+
+ /* convert parameter to a number and verify */
+ pm = strtoul(portmask, &end, 16);
+ if (end == NULL || *end != '\0' || pm == 0)
+ return -1;
+
+ /* loop through bits of the mask and mark ports */
+ while (pm != 0) {
+ if (pm & 0x01) { /* bit is set in mask, use port */
+ if (count >= max_ports)
+ printf("WARNING: requested port %u not present"
+ " - ignoring\n", (unsigned int)count);
+ else
+ info->id[info->num_ports++] = count;
+ }
+ pm = (pm >> 1);
+ count++;
+ }
+
+ return 0;
+}
+
+/**
+ * Take the number of nodes parameter passed to the app
+ * and convert to a number to store in the num_nodes variable
+ */
+static int
+parse_num_nodes(const char *nodes)
+{
+ char *end = NULL;
+ unsigned long temp;
+
+ if (nodes == NULL || *nodes == '\0')
+ return -1;
+
+ temp = strtoul(nodes, &end, 10);
+ if (end == NULL || *end != '\0' || temp == 0)
+ return -1;
+
+ num_nodes = (uint8_t)temp;
+ return 0;
+}
+
+static int
+parse_num_flows(const char *flows)
+{
+ char *end = NULL;
+
+ /* parse hexadecimal string */
+ num_flows = strtoul(flows, &end, 16);
+ if ((flows[0] == '\0') || (end == NULL) || (*end != '\0'))
+ return -1;
+
+ if (num_flows == 0)
+ return -1;
+
+ return 0;
+}
+
+/**
+ * The application specific arguments follow the DPDK-specific
+ * arguments which are stripped by the DPDK init. This function
+ * processes these application arguments, printing usage info
+ * on error.
+ */
+int
+parse_app_args(uint8_t max_ports, int argc, char *argv[])
+{
+ int option_index, opt;
+ char **argvopt = argv;
+ static struct option lgopts[] = { /* no long options */
+ {NULL, 0, 0, 0 }
+ };
+ progname = argv[0];
+
+ while ((opt = getopt_long(argc, argvopt, "n:f:p:", lgopts,
+ &option_index)) != EOF) {
+ switch (opt) {
+ case 'p':
+ if (parse_portmask(max_ports, optarg) != 0) {
+ usage();
+ return -1;
+ }
+ break;
+ case 'n':
+ if (parse_num_nodes(optarg) != 0) {
+ usage();
+ return -1;
+ }
+ break;
+ case 'f':
+ if (parse_num_flows(optarg) != 0) {
+ usage();
+ return -1;
+ }
+ break;
+ default:
+ printf("ERROR: Unknown option '%c'\n", opt);
+ usage();
+ return -1;
+ }
+ }
+
+ if (info->num_ports == 0 || num_nodes == 0) {
+ usage();
+ return -1;
+ }
+
+ if (info->num_ports % 2 != 0) {
+ printf("ERROR: application requires an even "
+ "number of ports to use\n");
+ return -1;
+ }
+ return 0;
+}
diff --git a/examples/flow_distributor/distributor/args.h b/examples/flow_distributor/distributor/args.h
new file mode 100644
index 0000000..cacf395
--- /dev/null
+++ b/examples/flow_distributor/distributor/args.h
@@ -0,0 +1,39 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _ARGS_H_
+#define _ARGS_H_
+
+int parse_app_args(uint8_t max_ports, int argc, char *argv[]);
+
+#endif /* ifndef _ARGS_H_ */
diff --git a/examples/flow_distributor/distributor/init.c b/examples/flow_distributor/distributor/init.c
new file mode 100644
index 0000000..3b0aa85
--- /dev/null
+++ b/examples/flow_distributor/distributor/init.c
@@ -0,0 +1,371 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/queue.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <inttypes.h>
+
+#include <rte_common.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_byteorder.h>
+#include <rte_atomic.h>
+#include <rte_launch.h>
+#include <rte_per_lcore.h>
+#include <rte_lcore.h>
+#include <rte_branch_prediction.h>
+#include <rte_debug.h>
+#include <rte_ring.h>
+#include <rte_log.h>
+#include <rte_mempool.h>
+#include <rte_memcpy.h>
+#include <rte_mbuf.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ether.h>
+#include <rte_ethdev.h>
+#include <rte_malloc.h>
+#include <rte_string_fns.h>
+#include <rte_cycles.h>
+#include <rte_efd.h>
+#include <rte_hash.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+#define MBUFS_PER_NODE 1536
+#define MBUFS_PER_PORT 1536
+#define MBUF_CACHE_SIZE 512
+
+#define RTE_MP_RX_DESC_DEFAULT 512
+#define RTE_MP_TX_DESC_DEFAULT 512
+#define NODE_QUEUE_RINGSIZE 128
+
+#define NO_FLAGS 0
+
+/* The mbuf pool for packet rx */
+struct rte_mempool *pktmbuf_pool;
+
+/* array of info/queues for nodes */
+struct node *nodes;
+
+/* Flow distributor table */
+struct rte_efd_table *efd_table;
+
+/* Shared info between distributor and nodes */
+struct shared_info *info;
+
+/**
+ * Initialise the mbuf pool for packet reception for the NIC, and any other
+ * buffer pools needed by the app - currently none.
+ */
+static int
+init_mbuf_pools(void)
+{
+ const unsigned int num_mbufs = (num_nodes * MBUFS_PER_NODE) +
+ (info->num_ports * MBUFS_PER_PORT);
+
+ /*
+ * Don't pass single-producer/single-consumer flags to mbuf create as it
+ * seems faster to use a cache instead
+ */
+ printf("Creating mbuf pool '%s' [%u mbufs] ...\n",
+ PKTMBUF_POOL_NAME, num_mbufs);
+ pktmbuf_pool = rte_pktmbuf_pool_create(PKTMBUF_POOL_NAME, num_mbufs,
+ MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
+
+ return pktmbuf_pool == NULL; /* 0 on success */
+}
+
+/**
+ * Initialise an individual port:
+ * - configure number of rx and tx rings
+ * - set up each rx ring, to pull from the main mbuf pool
+ * - set up each tx ring
+ * - start the port and report its status to stdout
+ */
+static int
+init_port(uint8_t port_num)
+{
+ /* for port configuration all features are off by default */
+ const struct rte_eth_conf port_conf = {
+ .rxmode = {
+ .mq_mode = ETH_MQ_RX_RSS
+ }
+ };
+ const uint16_t rx_rings = 1, tx_rings = num_nodes;
+ const uint16_t rx_ring_size = RTE_MP_RX_DESC_DEFAULT;
+ const uint16_t tx_ring_size = RTE_MP_TX_DESC_DEFAULT;
+
+ uint16_t q;
+ int retval;
+
+ printf("Port %u init ... ", (unsigned int)port_num);
+ fflush(stdout);
+
+ /*
+ * Standard DPDK port initialisation - config port, then set up
+ * rx and tx rings.
+ */
+ retval = rte_eth_dev_configure(port_num, rx_rings, tx_rings, &port_conf);
+ if (retval != 0)
+ return retval;
+
+ for (q = 0; q < rx_rings; q++) {
+ retval = rte_eth_rx_queue_setup(port_num, q, rx_ring_size,
+ rte_eth_dev_socket_id(port_num),
+ NULL, pktmbuf_pool);
+ if (retval < 0)
+ return retval;
+ }
+
+ for (q = 0; q < tx_rings; q++) {
+ retval = rte_eth_tx_queue_setup(port_num, q, tx_ring_size,
+ rte_eth_dev_socket_id(port_num),
+ NULL);
+ if (retval < 0)
+ return retval;
+ }
+
+ rte_eth_promiscuous_enable(port_num);
+
+ retval = rte_eth_dev_start(port_num);
+ if (retval < 0)
+ return retval;
+
+ printf("done:\n");
+
+ return 0;
+}
+
+/**
+ * Set up the DPDK rings which will be used to pass packets, via
+ * pointers, between the multi-process distributor and node processes.
+ * Each node needs one RX queue.
+ */
+static int
+init_shm_rings(void)
+{
+ unsigned int i;
+ unsigned int socket_id;
+ const char *q_name;
+ const unsigned int ringsize = NODE_QUEUE_RINGSIZE;
+
+ nodes = rte_malloc("node details",
+ sizeof(*nodes) * num_nodes, 0);
+ if (nodes == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot allocate memory for "
+ "node program details\n");
+
+ for (i = 0; i < num_nodes; i++) {
+ /* Create an RX queue for each node */
+ socket_id = rte_socket_id();
+ q_name = get_rx_queue_name(i);
+ nodes[i].rx_q = rte_ring_create(q_name,
+ ringsize, socket_id,
+ RING_F_SP_ENQ | RING_F_SC_DEQ);
+ if (nodes[i].rx_q == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot create rx ring queue "
+ "for node %u\n", i);
+ }
+ return 0;
+}
+
+/*
+ * Create flow distributor table which will contain all the flows
+ * that will be distributed among the nodes
+ */
+static void
+create_flow_distributor_table(void)
+{
+ uint8_t socket_id = rte_socket_id();
+
+ /* create table */
+ efd_table = rte_efd_create("flow table", num_flows * 2, sizeof(uint32_t),
+ 1 << socket_id, socket_id);
+
+ if (efd_table == NULL)
+ rte_exit(EXIT_FAILURE, "Problem creating the flow table\n");
+}
+
+static void
+populate_flow_distributor_table(void)
+{
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint8_t socket_id = rte_socket_id();
+ uint64_t node_id;
+
+ /* Add flows in table */
+ for (i = 0; i < num_flows; i++) {
+ node_id = i % num_nodes;
+
+ ip_dst = rte_cpu_to_be_32(i);
+ ret = rte_efd_update(efd_table, socket_id,
+ (void *)&ip_dst, (efd_value_t)node_id);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u in "
+ "flow distributor table\n", i);
+ }
+
+ printf("EFD table: Adding 0x%x keys\n", num_flows);
+}
+
+/* Check the link status of all ports in up to 9s, and print them finally */
+static void
+check_all_ports_link_status(uint8_t port_num, uint32_t port_mask)
+{
+#define CHECK_INTERVAL 100 /* 100ms */
+#define MAX_CHECK_TIME 90 /* 9s (90 * 100ms) in total */
+ uint8_t portid, count, all_ports_up, print_flag = 0;
+ struct rte_eth_link link;
+
+ printf("\nChecking link status");
+ fflush(stdout);
+ for (count = 0; count <= MAX_CHECK_TIME; count++) {
+ all_ports_up = 1;
+ for (portid = 0; portid < port_num; portid++) {
+ if ((port_mask & (1 << info->id[portid])) == 0)
+ continue;
+ memset(&link, 0, sizeof(link));
+ rte_eth_link_get_nowait(info->id[portid], &link);
+ /* print link status if flag set */
+ if (print_flag == 1) {
+ if (link.link_status)
+ printf("Port %d Link Up - speed %u "
+ "Mbps - %s\n", info->id[portid],
+ (unsigned int)link.link_speed,
+ (link.link_duplex == ETH_LINK_FULL_DUPLEX) ?
+ ("full-duplex") : ("half-duplex\n"));
+ else
+ printf("Port %d Link Down\n",
+ (uint8_t)info->id[portid]);
+ continue;
+ }
+ /* clear all_ports_up flag if any link down */
+ if (link.link_status == ETH_LINK_DOWN) {
+ all_ports_up = 0;
+ break;
+ }
+ }
+ /* after finally printing all link status, get out */
+ if (print_flag == 1)
+ break;
+
+ if (all_ports_up == 0) {
+ printf(".");
+ fflush(stdout);
+ rte_delay_ms(CHECK_INTERVAL);
+ }
+
+ /* set the print_flag if all ports up or timeout */
+ if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
+ print_flag = 1;
+ printf("done\n");
+ }
+ }
+}
+
+/**
+ * Main init function for the multi-process distributor app,
+ * calls subfunctions to do each stage of the initialisation.
+ */
+int
+init(int argc, char *argv[])
+{
+ int retval;
+ const struct rte_memzone *mz;
+ uint8_t i, total_ports;
+
+ /* init EAL, parsing EAL args */
+ retval = rte_eal_init(argc, argv);
+ if (retval < 0)
+ return -1;
+ argc -= retval;
+ argv += retval;
+
+ /* get total number of ports */
+ total_ports = rte_eth_dev_count();
+
+ /* set up array for port data */
+ mz = rte_memzone_reserve(MZ_SHARED_INFO, sizeof(*info),
+ rte_socket_id(), NO_FLAGS);
+ if (mz == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot reserve memory zone "
+ "for port information\n");
+ memset(mz->addr, 0, sizeof(*info));
+ info = mz->addr;
+
+ /* parse additional, application arguments */
+ retval = parse_app_args(total_ports, argc, argv);
+ if (retval != 0)
+ return -1;
+
+ /* initialise mbuf pools */
+ retval = init_mbuf_pools();
+ if (retval != 0)
+ rte_exit(EXIT_FAILURE, "Cannot create needed mbuf pools\n");
+
+ /* now initialise the ports we will use */
+ for (i = 0; i < info->num_ports; i++) {
+ retval = init_port(info->id[i]);
+ if (retval != 0)
+ rte_exit(EXIT_FAILURE, "Cannot initialise port %u\n",
+ (unsigned int) i);
+ }
+
+ check_all_ports_link_status(info->num_ports, (~0x0));
+
+ /* initialise the node queues/rings for inter-eu comms */
+ init_shm_rings();
+
+ /* Create the flow distributor table */
+ create_flow_distributor_table();
+
+ /* Populate the flow distributor table */
+ populate_flow_distributor_table();
+
+ /* Share the total number of nodes */
+ info->num_nodes = num_nodes;
+
+ /* Share the total number of flows */
+ info->num_flows = num_flows;
+ return 0;
+}
diff --git a/examples/flow_distributor/distributor/init.h b/examples/flow_distributor/distributor/init.h
new file mode 100644
index 0000000..d11aacf
--- /dev/null
+++ b/examples/flow_distributor/distributor/init.h
@@ -0,0 +1,76 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _INIT_H_
+#define _INIT_H_
+
+/*
+ * #include <rte_ring.h>
+ * #include "args.h"
+ */
+
+/*
+ * Define a node structure with all needed info, including
+ * stats from the nodes.
+ */
+struct node {
+ struct rte_ring *rx_q;
+ unsigned int node_id;
+ /* these stats hold how many packets the node will actually receive,
+ * and how many packets were dropped because the node's queue was full.
+ * The port-info stats, in contrast, record how many packets were received
+ * or transmitted on an actual NIC port.
+ */
+ struct {
+ uint64_t rx;
+ uint64_t rx_drop;
+ } stats;
+};
+
+extern struct rte_efd_table *efd_table;
+extern struct node *nodes;
+
+/*
+ * shared information between distributor and nodes: number of clients,
+ * port numbers, rx and tx stats etc.
+ */
+extern struct shared_info *info;
+
+extern struct rte_mempool *pktmbuf_pool;
+extern uint8_t num_nodes;
+extern unsigned int num_sockets;
+extern uint32_t num_flows;
+
+int init(int argc, char *argv[]);
+
+#endif /* ifndef _INIT_H_ */
diff --git a/examples/flow_distributor/distributor/main.c b/examples/flow_distributor/distributor/main.c
new file mode 100644
index 0000000..f97f003
--- /dev/null
+++ b/examples/flow_distributor/distributor/main.c
@@ -0,0 +1,362 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <inttypes.h>
+#include <sys/queue.h>
+#include <errno.h>
+#include <netinet/ip.h>
+
+#include <rte_common.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_byteorder.h>
+#include <rte_launch.h>
+#include <rte_per_lcore.h>
+#include <rte_lcore.h>
+#include <rte_branch_prediction.h>
+#include <rte_atomic.h>
+#include <rte_ring.h>
+#include <rte_log.h>
+#include <rte_debug.h>
+#include <rte_mempool.h>
+#include <rte_memcpy.h>
+#include <rte_mbuf.h>
+#include <rte_ether.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ethdev.h>
+#include <rte_byteorder.h>
+#include <rte_malloc.h>
+#include <rte_string_fns.h>
+#include <rte_efd.h>
+#include <rte_ip.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+/*
+ * When doing reads from the NIC or the node queues,
+ * use this batch size
+ */
+#define PACKET_READ_SIZE 32
+
+/*
+ * Local buffers to put packets in, used to send packets in bursts to the
+ * nodes
+ */
+struct node_rx_buf {
+ struct rte_mbuf *buffer[PACKET_READ_SIZE];
+ uint16_t count;
+};
+
+struct flow_distributor_stats {
+ uint64_t distributed;
+ uint64_t drop;
+} flow_dist_stats;
+
+/* One buffer per node rx queue - dynamically allocate array */
+static struct node_rx_buf *cl_rx_buf;
+
+static const char *
+get_printable_mac_addr(uint8_t port)
+{
+ static const char err_address[] = "00:00:00:00:00:00";
+ static char addresses[RTE_MAX_ETHPORTS][sizeof(err_address)];
+ struct ether_addr mac;
+
+ if (unlikely(port >= RTE_MAX_ETHPORTS))
+ return err_address;
+ if (unlikely(addresses[port][0] == '\0')) {
+ rte_eth_macaddr_get(port, &mac);
+ snprintf(addresses[port], sizeof(addresses[port]),
+ "%02x:%02x:%02x:%02x:%02x:%02x\n",
+ mac.addr_bytes[0], mac.addr_bytes[1],
+ mac.addr_bytes[2], mac.addr_bytes[3],
+ mac.addr_bytes[4], mac.addr_bytes[5]);
+ }
+ return addresses[port];
+}
+
+/*
+ * This function displays the recorded statistics for each port
+ * and for each node. It uses ANSI terminal codes to clear
+ * screen when called. It is called from a single non-master
+ * thread in the distributor process, when the process is run with more
+ * than one lcore enabled.
+ */
+static void
+do_stats_display(void)
+{
+ unsigned int i, j;
+ const char clr[] = {27, '[', '2', 'J', '\0'};
+ const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
+ uint64_t port_tx[RTE_MAX_ETHPORTS], port_tx_drop[RTE_MAX_ETHPORTS];
+ uint64_t node_tx[MAX_NODES], node_tx_drop[MAX_NODES];
+
+ /* to get TX stats, we need to do some summing calculations */
+ memset(port_tx, 0, sizeof(port_tx));
+ memset(port_tx_drop, 0, sizeof(port_tx_drop));
+ memset(node_tx, 0, sizeof(node_tx));
+ memset(node_tx_drop, 0, sizeof(node_tx_drop));
+
+ for (i = 0; i < num_nodes; i++) {
+ const struct tx_stats *tx = &info->tx_stats[i];
+
+ for (j = 0; j < info->num_ports; j++) {
+ const uint64_t tx_val = tx->tx[info->id[j]];
+ const uint64_t drop_val = tx->tx_drop[info->id[j]];
+
+ port_tx[j] += tx_val;
+ port_tx_drop[j] += drop_val;
+ node_tx[i] += tx_val;
+ node_tx_drop[i] += drop_val;
+ }
+ }
+
+ /* Clear screen and move to top left */
+ printf("%s%s", clr, topLeft);
+
+ printf("PORTS\n");
+ printf("-----\n");
+ for (i = 0; i < info->num_ports; i++)
+ printf("Port %u: '%s'\t", (unsigned int)info->id[i],
+ get_printable_mac_addr(info->id[i]));
+ printf("\n\n");
+ for (i = 0; i < info->num_ports; i++) {
+ printf("Port %u - rx: %9"PRIu64"\t"
+ "tx: %9"PRIu64"\n",
+ (unsigned int)info->id[i], info->rx_stats.rx[i],
+ port_tx[i]);
+ }
+
+ printf("\nFLOW DISTRIBUTOR\n");
+ printf("-----\n");
+ printf("distributed: %9"PRIu64", drop: %9"PRIu64"\n",
+ flow_dist_stats.distributed, flow_dist_stats.drop);
+
+ printf("\nNODES\n");
+ printf("-------\n");
+ for (i = 0; i < num_nodes; i++) {
+ const unsigned long long rx = nodes[i].stats.rx;
+ const unsigned long long rx_drop = nodes[i].stats.rx_drop;
+ const struct filter_stats *filter = &info->filter_stats[i];
+
+ printf("Node %2u - rx: %9llu, rx_drop: %9llu\n"
+ " tx: %9"PRIu64", tx_drop: %9"PRIu64"\n"
+ " filter_passed: %9"PRIu64", "
+ "filter_drop: %9"PRIu64"\n",
+ i, rx, rx_drop, node_tx[i], node_tx_drop[i],
+ filter->passed, filter->drop);
+ }
+
+ printf("\n");
+}
+
+/*
+ * The function called from each non-master lcore used by the process.
+ * The test_and_set function is used to randomly pick a single lcore on which
+ * the code to display the statistics will run. Otherwise, the code just
+ * repeatedly sleeps.
+ */
+static int
+sleep_lcore(__attribute__((unused)) void *dummy)
+{
+ /* Used to pick a display thread - static, so zero-initialised */
+ static rte_atomic32_t display_stats;
+
+ /* Only one core should display stats */
+ if (rte_atomic32_test_and_set(&display_stats)) {
+ const unsigned int sleeptime = 1;
+
+ printf("Core %u displaying statistics\n", rte_lcore_id());
+
+ /* Longer initial pause so above printf is seen */
+ sleep(sleeptime * 3);
+
+ /* Loop forever: sleep always returns 0 or <= param */
+ while (sleep(sleeptime) <= sleeptime)
+ do_stats_display();
+ }
+ return 0;
+}
+
+/*
+ * Function to set all the node statistic values to zero.
+ * Called at program startup.
+ */
+static void
+clear_stats(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_nodes; i++)
+ nodes[i].stats.rx = nodes[i].stats.rx_drop = 0;
+}
+
+/*
+ * send a burst of traffic to a node, assuming there are packets
+ * available to be sent to this node
+ */
+static void
+flush_rx_queue(uint16_t node)
+{
+ uint16_t j;
+ struct node *cl;
+
+ if (cl_rx_buf[node].count == 0)
+ return;
+
+ cl = &nodes[node];
+ if (rte_ring_enqueue_bulk(cl->rx_q, (void **)cl_rx_buf[node].buffer,
+ cl_rx_buf[node].count) != 0){
+ for (j = 0; j < cl_rx_buf[node].count; j++)
+ rte_pktmbuf_free(cl_rx_buf[node].buffer[j]);
+ cl->stats.rx_drop += cl_rx_buf[node].count;
+ } else
+ cl->stats.rx += cl_rx_buf[node].count;
+
+ cl_rx_buf[node].count = 0;
+}
+
+/*
+ * marks a packet down to be sent to a particular node process
+ */
+static inline void
+enqueue_rx_packet(uint8_t node, struct rte_mbuf *buf)
+{
+ cl_rx_buf[node].buffer[cl_rx_buf[node].count++] = buf;
+}
+
+/*
+ * This function takes a group of packets and routes them
+ * individually to the node process. Very simply round-robins the packets
+ * without checking any of the packet contents.
+ */
+static void
+process_packets(uint32_t port_num __rte_unused, struct rte_mbuf *pkts[],
+ uint16_t rx_count, unsigned int socket_id)
+{
+ uint16_t i;
+ uint8_t node;
+ efd_value_t data[RTE_EFD_BURST_MAX];
+ const void *key_ptrs[RTE_EFD_BURST_MAX];
+
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[RTE_EFD_BURST_MAX];
+
+ for (i = 0; i < rx_count; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(pkts[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = (void *)&ipv4_dst_ip[i];
+ }
+
+ rte_efd_lookup_bulk(efd_table, socket_id, rx_count,
+ (const void **) key_ptrs, data);
+ for (i = 0; i < rx_count; i++) {
+ node = (uint8_t) ((uintptr_t)data[i]);
+
+ if (node >= num_nodes) {
+ /*
+ * Node is out of range, which means that
+ * flow has not been inserted
+ */
+ flow_dist_stats.drop++;
+ rte_pktmbuf_free(pkts[i]);
+ } else {
+ flow_dist_stats.distributed++;
+ enqueue_rx_packet(node, pkts[i]);
+ }
+ }
+
+ for (i = 0; i < num_nodes; i++)
+ flush_rx_queue(i);
+}
+
+/*
+ * Function called by the master lcore of the DPDK process.
+ */
+static void
+do_packet_forwarding(void)
+{
+ unsigned int port_num = 0; /* indexes the port[] array */
+ unsigned int socket_id = rte_socket_id();
+
+ for (;;) {
+ struct rte_mbuf *buf[PACKET_READ_SIZE];
+ uint16_t rx_count;
+
+ /* read a port */
+ rx_count = rte_eth_rx_burst(info->id[port_num], 0,
+ buf, PACKET_READ_SIZE);
+ info->rx_stats.rx[port_num] += rx_count;
+
+ /* Now process the NIC packets read */
+ if (likely(rx_count > 0))
+ process_packets(port_num, buf, rx_count, socket_id);
+
+ /* move to next port */
+ if (++port_num == info->num_ports)
+ port_num = 0;
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ /* initialise the system */
+ if (init(argc, argv) < 0)
+ return -1;
+ RTE_LOG(INFO, APP, "Finished Process Init.\n");
+
+ cl_rx_buf = calloc(num_nodes, sizeof(cl_rx_buf[0]));
+
+ /* clear statistics */
+ clear_stats();
+
+ /* put all other cores to sleep bar master */
+ rte_eal_mp_remote_launch(sleep_lcore, NULL, SKIP_MASTER);
+
+ do_packet_forwarding();
+ return 0;
+}
diff --git a/examples/flow_distributor/node/Makefile b/examples/flow_distributor/node/Makefile
new file mode 100644
index 0000000..8cf7b65
--- /dev/null
+++ b/examples/flow_distributor/node/Makefile
@@ -0,0 +1,48 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overridden by command line or environment
+include $(RTE_SDK)/mk/rte.vars.mk
+
+# binary name
+APP = node
+
+# all source are stored in SRCS-y
+SRCS-y := node.c
+
+CFLAGS += $(WERROR_FLAGS) -O3
+CFLAGS += -I$(SRCDIR)/../shared
+
+include $(RTE_SDK)/mk/rte.extapp.mk
diff --git a/examples/flow_distributor/node/node.c b/examples/flow_distributor/node/node.c
new file mode 100644
index 0000000..1f1e7e7
--- /dev/null
+++ b/examples/flow_distributor/node/node.c
@@ -0,0 +1,417 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <sys/queue.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_malloc.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_atomic.h>
+#include <rte_branch_prediction.h>
+#include <rte_log.h>
+#include <rte_per_lcore.h>
+#include <rte_launch.h>
+#include <rte_lcore.h>
+#include <rte_ring.h>
+#include <rte_launch.h>
+#include <rte_lcore.h>
+#include <rte_debug.h>
+#include <rte_mempool.h>
+#include <rte_mbuf.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ether.h>
+#include <rte_ethdev.h>
+#include <rte_string_fns.h>
+#include <rte_ip.h>
+
+#include "common.h"
+
+/* Number of packets to attempt to read from queue */
+#define PKT_READ_SIZE ((uint16_t)32)
+
+/*
+ * Our node id number - tells us which rx queue to read, and NIC TX
+ * queue to write to.
+ */
+static uint8_t node_id;
+
+#define MBQ_CAPACITY 32
+
+/* maps input ports to output ports for packets */
+static uint8_t output_ports[RTE_MAX_ETHPORTS];
+
+/* buffers up a set of packet that are ready to send */
+struct rte_eth_dev_tx_buffer *tx_buffer[RTE_MAX_ETHPORTS];
+
+/* shared data from distributor. We update statistics here */
+static struct tx_stats *tx_stats;
+
+static struct filter_stats *filter_stats;
+
+/*
+ * print a usage message
+ */
+static void
+usage(const char *progname)
+{
+ printf("Usage: %s [EAL args] -- -n <node_id>\n\n", progname);
+}
+
+/*
+ * Convert the node id number from a string to an int.
+ */
+static int
+parse_node_num(const char *node)
+{
+ char *end = NULL;
+ unsigned long temp;
+
+ if (node == NULL || *node == '\0')
+ return -1;
+
+ temp = strtoul(node, &end, 10);
+ if (end == NULL || *end != '\0')
+ return -1;
+
+ node_id = (uint8_t)temp;
+ return 0;
+}
+
+/*
+ * Parse the application arguments to the node app.
+ */
+static int
+parse_app_args(int argc, char *argv[])
+{
+ int option_index, opt;
+ char **argvopt = argv;
+ const char *progname = NULL;
+ static struct option lgopts[] = { /* no long options */
+ {NULL, 0, 0, 0 }
+ };
+ progname = argv[0];
+
+ while ((opt = getopt_long(argc, argvopt, "n:", lgopts,
+ &option_index)) != EOF) {
+ switch (opt) {
+ case 'n':
+ if (parse_node_num(optarg) != 0) {
+ usage(progname);
+ return -1;
+ }
+ break;
+ default:
+ usage(progname);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Tx buffer error callback
+ */
+static void
+flush_tx_error_callback(struct rte_mbuf **unsent, uint16_t count,
+ void *userdata) {
+ int i;
+ uint8_t port_id = (uintptr_t)userdata;
+
+ tx_stats->tx_drop[port_id] += count;
+
+ /* free the mbufs which failed from transmit */
+ for (i = 0; i < count; i++)
+ rte_pktmbuf_free(unsent[i]);
+
+}
+
+static void
+configure_tx_buffer(uint8_t port_id, uint16_t size)
+{
+ int ret;
+
+ /* Initialize TX buffers */
+ tx_buffer[port_id] = rte_zmalloc_socket("tx_buffer",
+ RTE_ETH_TX_BUFFER_SIZE(size), 0,
+ rte_eth_dev_socket_id(port_id));
+ if (tx_buffer[port_id] == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot allocate buffer for tx "
+ "on port %u\n", (unsigned int) port_id);
+
+ rte_eth_tx_buffer_init(tx_buffer[port_id], size);
+
+ ret = rte_eth_tx_buffer_set_err_callback(tx_buffer[port_id],
+ flush_tx_error_callback, (void *)(intptr_t)port_id);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Cannot set error callback for "
+ "tx buffer on port %u\n", (unsigned int) port_id);
+}
+
+/*
+ * set up output ports so that all traffic on port gets sent out
+ * its paired port. Index using actual port numbers since that is
+ * what comes in the mbuf structure.
+ */
+static void
+configure_output_ports(const struct shared_info *info)
+{
+ int i;
+
+ if (info->num_ports > RTE_MAX_ETHPORTS)
+ rte_exit(EXIT_FAILURE, "Too many ethernet ports. "
+ "RTE_MAX_ETHPORTS = %u\n",
+ (unsigned int)RTE_MAX_ETHPORTS);
+ for (i = 0; i < info->num_ports - 1; i += 2) {
+ uint8_t p1 = info->id[i];
+ uint8_t p2 = info->id[i+1];
+
+ output_ports[p1] = p2;
+ output_ports[p2] = p1;
+
+ configure_tx_buffer(p1, MBQ_CAPACITY);
+ configure_tx_buffer(p2, MBQ_CAPACITY);
+
+ }
+}
+
+/*
+ * Create the hash table that will contain the flows that
+ * the node will handle, which will be used to decide if packet
+ * is transmitted or dropped.
+ */
+static struct rte_hash *
+create_hash_table(const struct shared_info *info)
+{
+ uint32_t num_flows_node = info->num_flows / info->num_nodes;
+ char name[RTE_HASH_NAMESIZE];
+ struct rte_hash *h;
+
+ /* create table */
+ struct rte_hash_parameters hash_params = {
+ .entries = num_flows_node * 2, /* table load = 50% */
+ .key_len = sizeof(uint32_t), /* Store IPv4 dest IP address */
+ .socket_id = rte_socket_id(),
+ .hash_func_init_val = 0,
+ };
+
+ snprintf(name, sizeof(name), "hash_table_%d", node_id);
+ hash_params.name = name;
+ h = rte_hash_create(&hash_params);
+
+ if (h == NULL)
+ rte_exit(EXIT_FAILURE,
+ "Problem creating the hash table for node %d\n",
+ node_id);
+ return h;
+}
+
+static void
+populate_hash_table(const struct rte_hash *h, const struct shared_info *info)
+{
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint32_t num_flows_node = 0;
+ uint64_t target_node;
+
+ /* Add flows in table */
+ for (i = 0; i < info->num_flows; i++) {
+ target_node = i % info->num_nodes;
+ if (target_node != node_id)
+ continue;
+
+ ip_dst = rte_cpu_to_be_32(i);
+
+ ret = rte_hash_add_key(h, (void *) &ip_dst);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u "
+ "in hash table\n", i);
+ else
+ num_flows_node++;
+
+ }
+
+ printf("Hash table: Adding 0x%x keys\n", num_flows_node);
+}
+
+/*
+ * This function performs routing of packets
+ * Just sends each input packet out an output port based solely on the input
+ * port it arrived on.
+ */
+static inline void
+transmit_packet(struct rte_mbuf *buf)
+{
+ int sent;
+ const uint8_t in_port = buf->port;
+ const uint8_t out_port = output_ports[in_port];
+ struct rte_eth_dev_tx_buffer *buffer = tx_buffer[out_port];
+
+ sent = rte_eth_tx_buffer(out_port, node_id, buffer, buf);
+ if (sent)
+ tx_stats->tx[out_port] += sent;
+
+}
+
+static inline void
+handle_packets(struct rte_hash *h, struct rte_mbuf **bufs, uint16_t num_packets)
+{
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[PKT_READ_SIZE];
+ const void *key_ptrs[PKT_READ_SIZE];
+ unsigned int i;
+ int32_t positions[PKT_READ_SIZE] = {0};
+
+ for (i = 0; i < num_packets; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(bufs[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = &ipv4_dst_ip[i];
+ }
+ /* Check if packets belongs to any flows handled by this node */
+ rte_hash_lookup_bulk(h, key_ptrs, num_packets, positions);
+
+ for (i = 0; i < num_packets; i++) {
+ if (likely(positions[i] >= 0)) {
+ filter_stats->passed++;
+ transmit_packet(bufs[i]);
+ } else {
+ filter_stats->drop++;
+ /* Drop packet, as flow is not handled by this node */
+ rte_pktmbuf_free(bufs[i]);
+ }
+ }
+}
+
+/*
+ * Application main function - loops through
+ * receiving and processing packets. Never returns
+ */
+int
+main(int argc, char *argv[])
+{
+ const struct rte_memzone *mz;
+ struct rte_ring *rx_ring;
+ struct rte_hash *h;
+ struct rte_mempool *mp;
+ struct shared_info *info;
+ int need_flush = 0; /* indicates whether we have unsent packets */
+ int retval;
+ void *pkts[PKT_READ_SIZE];
+ uint16_t sent;
+
+ retval = rte_eal_init(argc, argv);
+ if (retval < 0)
+ return -1;
+ argc -= retval;
+ argv += retval;
+
+ if (parse_app_args(argc, argv) < 0)
+ rte_exit(EXIT_FAILURE, "Invalid command-line arguments\n");
+
+ if (rte_eth_dev_count() == 0)
+ rte_exit(EXIT_FAILURE, "No Ethernet ports - bye\n");
+
+ rx_ring = rte_ring_lookup(get_rx_queue_name(node_id));
+ if (rx_ring == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get RX ring - "
+ "is distributor process running?\n");
+
+ mp = rte_mempool_lookup(PKTMBUF_POOL_NAME);
+ if (mp == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get mempool for mbufs\n");
+
+ mz = rte_memzone_lookup(MZ_SHARED_INFO);
+ if (mz == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get port info structure\n");
+ info = mz->addr;
+ tx_stats = &(info->tx_stats[node_id]);
+ filter_stats = &(info->filter_stats[node_id]);
+
+ configure_output_ports(info);
+
+ h = create_hash_table(info);
+
+ populate_hash_table(h, info);
+
+ RTE_LOG(INFO, APP, "Finished Process Init.\n");
+
+ printf("\nNode process %d handling packets\n", node_id);
+ printf("[Press Ctrl-C to quit ...]\n");
+
+ for (;;) {
+ uint16_t rx_pkts = PKT_READ_SIZE;
+ uint8_t port;
+
+ /*
+ * Try dequeuing max possible packets first, if that fails,
+ * get the most we can. Loop body should only execute once,
+ * maximum
+ */
+ while (rx_pkts > 0 &&
+ unlikely(rte_ring_dequeue_bulk(rx_ring, pkts,
+ rx_pkts) != 0))
+ rx_pkts = (uint16_t)RTE_MIN(rte_ring_count(rx_ring),
+ PKT_READ_SIZE);
+
+ if (unlikely(rx_pkts == 0)) {
+ if (need_flush)
+ for (port = 0; port < info->num_ports; port++) {
+ sent = rte_eth_tx_buffer_flush(
+ info->id[port],
+ node_id,
+ tx_buffer[port]);
+ if (unlikely(sent))
+ tx_stats->tx[port] += sent;
+ }
+ need_flush = 0;
+ continue;
+ }
+
+ handle_packets(h, (struct rte_mbuf **)pkts, rx_pkts);
+
+ need_flush = 1;
+ }
+}
diff --git a/examples/flow_distributor/shared/common.h b/examples/flow_distributor/shared/common.h
new file mode 100644
index 0000000..5dcffd6
--- /dev/null
+++ b/examples/flow_distributor/shared/common.h
@@ -0,0 +1,99 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _COMMON_H_
+#define _COMMON_H_
+
+#include <rte_hash_crc.h>
+#include <rte_hash.h>
+
+#define MAX_NODES 16
+/*
+ * Shared port info, including statistics information for display by distributor.
+ * Structure will be put in a memzone.
+ * - All port id values share one cache line as this data will be read-only
+ * during operation.
+ * - All rx statistic values share cache lines, as this data is written only
+ * by the distributor process. (rare reads by stats display)
+ * - The tx statistics have values for all ports per cache line, but the stats
+ * themselves are written by the nodes, so we have a distinct set, on different
+ * cache lines for each node to use.
+ */
+struct rx_stats {
+ uint64_t rx[RTE_MAX_ETHPORTS];
+} __rte_cache_aligned;
+
+struct tx_stats {
+ uint64_t tx[RTE_MAX_ETHPORTS];
+ uint64_t tx_drop[RTE_MAX_ETHPORTS];
+} __rte_cache_aligned;
+
+struct filter_stats {
+ uint64_t drop;
+ uint64_t passed;
+} __rte_cache_aligned;
+
+struct shared_info {
+ uint8_t num_nodes;
+ uint8_t num_ports;
+ uint32_t num_flows;
+ uint8_t id[RTE_MAX_ETHPORTS];
+ struct rx_stats rx_stats;
+ struct tx_stats tx_stats[MAX_NODES];
+ struct filter_stats filter_stats[MAX_NODES];
+};
+
+/* define common names for structures shared between distributor and node */
+#define MP_NODE_RXQ_NAME "MProc_Node_%u_RX"
+#define PKTMBUF_POOL_NAME "MProc_pktmbuf_pool"
+#define MZ_SHARED_INFO "MProc_shared_info"
+
+/*
+ * Given the rx queue name template above, get the queue name
+ */
+static inline const char *
+get_rx_queue_name(unsigned int id)
+{
+ /*
+ * Buffer for return value. Size calculated by %u being replaced
+ * by maximum 3 digits (plus an extra byte for safety)
+ */
+ static char buffer[sizeof(MP_NODE_RXQ_NAME) + 2];
+
+ snprintf(buffer, sizeof(buffer) - 1, MP_NODE_RXQ_NAME, id);
+ return buffer;
+}
+
+#define RTE_LOGTYPE_APP RTE_LOGTYPE_USER1
+
+#endif
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v3 4/5] doc: add EFD library section in Programmers guide
2017-01-12 22:15 ` [dpdk-dev] [PATCH v3 " Pablo de Lara
` (2 preceding siblings ...)
2017-01-12 22:15 ` [dpdk-dev] [PATCH v3 3/5] examples/flow_distributor: sample app to demonstrate EFD usage Pablo de Lara
@ 2017-01-12 22:15 ` Pablo de Lara
2017-01-12 22:16 ` [dpdk-dev] [PATCH v3 5/5] doc: add flow distributor guide Pablo de Lara
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 0/5] Elastic Flow Distributor Pablo de Lara
5 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-12 22:15 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Sameh Gobriel
Signed-off-by: Sameh Gobriel <sameh.gobriel@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 1 +
doc/guides/prog_guide/efd_lib.rst | 428 +++++++++++
doc/guides/prog_guide/img/efd_i1.svg | 130 ++++
doc/guides/prog_guide/img/efd_i10.svg | 384 ++++++++++
doc/guides/prog_guide/img/efd_i11.svg | 319 ++++++++
doc/guides/prog_guide/img/efd_i12.svg | 1008 +++++++++++++++++++++++++
doc/guides/prog_guide/img/efd_i2.svg | 280 +++++++
doc/guides/prog_guide/img/efd_i3.svg | 634 ++++++++++++++++
doc/guides/prog_guide/img/efd_i4.svg | 203 ++++++
doc/guides/prog_guide/img/efd_i5.svg | 183 +++++
doc/guides/prog_guide/img/efd_i6.svg | 1254 ++++++++++++++++++++++++++++++++
doc/guides/prog_guide/img/efd_i7.svg | 790 ++++++++++++++++++++
doc/guides/prog_guide/img/efd_i8.svg | 182 +++++
doc/guides/prog_guide/img/efd_i9.svg | 390 ++++++++++
doc/guides/prog_guide/index.rst | 23 +
doc/guides/rel_notes/release_17_02.rst | 3 +
16 files changed, 6212 insertions(+)
create mode 100644 doc/guides/prog_guide/efd_lib.rst
create mode 100644 doc/guides/prog_guide/img/efd_i1.svg
create mode 100644 doc/guides/prog_guide/img/efd_i10.svg
create mode 100644 doc/guides/prog_guide/img/efd_i11.svg
create mode 100644 doc/guides/prog_guide/img/efd_i12.svg
create mode 100644 doc/guides/prog_guide/img/efd_i2.svg
create mode 100644 doc/guides/prog_guide/img/efd_i3.svg
create mode 100644 doc/guides/prog_guide/img/efd_i4.svg
create mode 100644 doc/guides/prog_guide/img/efd_i5.svg
create mode 100644 doc/guides/prog_guide/img/efd_i6.svg
create mode 100644 doc/guides/prog_guide/img/efd_i7.svg
create mode 100644 doc/guides/prog_guide/img/efd_i8.svg
create mode 100644 doc/guides/prog_guide/img/efd_i9.svg
diff --git a/MAINTAINERS b/MAINTAINERS
index b124f6e..66e9466 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -532,6 +532,7 @@ EFD
M: Byron Marohn <byron.marohn@intel.com>
M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
F: lib/librte_efd/
+F: doc/guides/prog_guide/efd_lib.rst
F: app/test/test_efd*
F: examples/flow_distributor/
diff --git a/doc/guides/prog_guide/efd_lib.rst b/doc/guides/prog_guide/efd_lib.rst
new file mode 100644
index 0000000..2358661
--- /dev/null
+++ b/doc/guides/prog_guide/efd_lib.rst
@@ -0,0 +1,428 @@
+.. BSD LICENSE
+ Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of Intel Corporation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+.. _Efd_Library:
+
+Elastic Flow Distributor Library
+================================
+
+Introduction
+------------
+
+In Data Centers today, clustering and scheduling of distributed workloads
+is a very common task. Many workloads require a deterministic
+partitioning of a flat key space among a cluster of machines. When a
+packet enters the cluster, the ingress node will direct the packet to
+its handling node. For example, data-centers with disaggregated storage
+use storage metadata tables to forward I/O requests to the correct back end
+storage cluster, stateful packet inspection will use match incoming
+flows to signatures in flow tables to send incoming packets to their
+intended deep packet inspection (DPI) devices, and so on.
+
+EFD is a distributor library that uses perfect hashing to determine a
+target/value for a given incoming flow key. It has the following
+advantages: first, because it uses perfect hashing it does not store the
+key itself and hence lookup performance is not dependent on the key
+size. Second, the target/value can be any arbitrary value hence the
+system designer and/or operator can better optimize service rates and
+inter-cluster network traffic locating. Third, since the storage
+requirement is much smaller than a hash-based flow table (i.e. better
+fit for CPU cache), EFD can scale to millions of flow keys. Finally,
+with the current optimized library implementation, performance is fully
+scalable with any number of CPU cores.
+
+Flow Based Distribution
+-----------------------
+
+Computation Based Schemes
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Flow distribution and/or load balancing can be simply done using a
+stateless computation, for instance using round-robin or a simple
+computation based on the flow key as an input. For example, a hash
+function can be used to direct a certain flow to a target based on
+the flow key (e.g. ``h(key) mod n``) where h(key) is the hash value of the
+flow key and n is the number of possible targets.
+
+.. _figure_efd1:
+
+.. figure:: img/efd_i1.*
+
+ Load Balancing Using Front End Node
+
+In this scheme (:numref:`figure_efd1`), the front end server/distributor/load balancer
+extracts the flow key from the input packet and applies a computation to determine where
+this flow should be directed. Intuitively, this scheme is very simple
+and requires no state to be kept at the front end node, and hence,
+storage requirements are minimum.
+
+.. _figure_efd2:
+
+.. figure:: img/efd_i2.*
+
+ Consistent Hashing
+
+A widely used flow distributor that belongs to the same category of
+computation-based schemes is ``consistent hashing``, shown in :numref:`figure_efd2`.
+Target destinations (shown in red) are hashed into the same space as the flow
+keys (shown in blue), and keys are mapped to the nearest target in a clockwise
+fashion. Dynamically adding and removing targets with consistent hashing
+requires only K/n keys to be remapped on average, where K is the number of
+keys, and n is the number of targets. In contrast, in a traditional hash-based
+scheme, a change in the number of targets causes nearly all keys to be
+remapped.
+
+Although computation-based schemes are simple and need very little
+storage requirement, they suffer from the drawback that the system
+designer/operator can’t fully control the target to assign a specific
+key, as this is dictated by the hash function.
+Deterministically co-locating of keys together (for example, to minimize
+inter-server traffic or to optimize for network traffic conditions,
+target load, etc.) is simply not possible.
+
+Flow-Table Based Schemes
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+When using a Flow-Table based scheme to handle flow distribution/load
+balancing, in contrast with computation-based schemes, the system designer
+has the flexibility of assigning a given flow to any given
+target. The flow table (e.g. DPDK RTE Hash Library) will simply store
+both the flow key and the target value.
+
+.. _figure_efd3:
+
+.. figure:: img/efd_i3.*
+
+ Table Based Flow Distribution
+
+As shown in :numref:`figure_efd3`, when doing a lookup, the flow-table
+is indexed with the hash of the flow key and the keys (more than one is possible,
+because of hash collision) stored in this index and corresponding values
+are retrieved. The retrieved key(s) is matched with the input flow key
+and if there is a match the value (target id) is returned.
+
+The drawback of using a hash table for flow distribution/load balancing
+is the storage requirement, since the flow table need to store keys,
+signatures and target values. This doesn't allow this scheme to scale to
+millions of flow keys. Large tables will usually not fit in
+the CPU cache, and hence, the lookup performance is degraded because of
+the latency to access the main memory.
+
+EFD Based Scheme
+~~~~~~~~~~~~~~~~
+
+EFD combines the advantages of both flow-table based and computation-based
+schemes. It doesn't require the large storage necessary for
+flow-table based schemes (because EFD doesn't store the key as explained
+below), and it supports any arbitrary value for any given key.
+
+.. _figure_efd4:
+
+.. figure:: img/efd_i4.*
+
+ Searching for Perfect Hash Function
+
+The basic idea of EFD is when a given key is to be inserted, a family of
+hash functions is searched until the correct hash function that maps the
+input key to the correct value is found, as shown in :numref:`figure_efd4`.
+However, rather than explicitly storing all keys and their associated values,
+EFD stores only indices of hash functions that map keys to values, and
+thereby consumes much less space than conventional flow-based tables.
+The lookup operation is very simple, similar to a computational-based
+scheme: given an input key the lookup operation is reduced to hashing
+that key with the correct hash function.
+
+.. _figure_efd5:
+
+.. figure:: img/efd_i5.*
+
+ Divide and Conquer for Millions of Keys
+
+Intuitively, finding a hash function that maps each of a large number
+(millions) of input keys to the correct output value is effectively
+impossible, as a result EFD, as shown in :numref:`figure_efd5`,
+breaks the problem into smaller pieces (divide and conquer).
+EFD divides the entire input key set into many small groups.
+Each group consists of approximately 20-28 keys (a configurable parameter
+for the library), then, for each small group, a brute force search to find
+a hash function that produces the correct outputs for each key in the group.
+
+It should be mentioned that, since the online lookup table for EFD
+doesn't store the key itself, the size of the EFD table is independent
+of the key size and hence EFD lookup performance which is almost
+constant irrespective of the length of the key which is a highly
+desirable feature especially for longer keys.
+
+In summary, EFD is a set separation data structure that supports millions of
+keys. It is used to distribute a given key to an intended target. By itself
+EFD is not a FIB data structure with an exact match the input flow key.
+
+.. _Efd_example:
+
+Example of EFD Library Usage
+----------------------------
+
+EFD can be used along the data path of many network functions and middleboxes.
+As previously mentioned, it can used as an index table for
+<key,value> pairs, meta-data for objects, a flow-level load balancer, etc.
+:numref:`figure_efd6` shows an example of using EFD as a flow-level load
+balancer, where flows are received at a front end server before being forwarded
+to the target back end server for processing. The system designer would
+deterministically co-locate flows together in order to minimize cross-server
+interaction.
+(For example, flows requesting certain webpage objects are co-located
+together, to minimize forwarding of common objects across servers).
+
+.. _figure_efd6:
+
+.. figure:: img/efd_i6.*
+
+ EFD as a Flow-Level Load Balancer
+
+As shown in :numref:`figure_efd6`, the front end server will have an EFD table that
+stores for each group what is the perfect hash index that satisfies the
+correct output. Because the table size is small and fits in cache (since
+keys are not stored), it sustains a large number of flows (N*X, where N
+is the maximum number of flows served by each back end server of the X
+possible targets).
+
+With an input flow key, the group id is computed (for example, using
+last few bits of CRC hash) and then the EFD table is indexed with the
+group id to retrieve the corresponding hash index to use. Once the index
+is retrieved the key is hashed using this hash function and the result
+will be the intended correct target where this flow is supposed to be
+processed.
+
+It should be noted that as a result of EFD not matching the exact key but
+rather distributing the flows to a target back end node based on the
+perfect hash index, a key that has not been inserted before
+will be distributed to a valid target. Hence, a local table which stores
+the flows served at each node is used and is
+exact matched with the input key to rule out new never seen before
+flows.
+
+.. _Efd_api:
+
+Library API Overview
+--------------------
+
+The EFD library API is created with a very similar semantics of a
+hash-index or a flow table. The application creates an EFD table for a
+given maximum number of flows, a function is called to insert a flow key
+with a specific target value, and another function is used to retrieve
+target values for a given individual flow key or a bulk of keys.
+
+EFD Table Create
+~~~~~~~~~~~~~~~~
+
+The function ``rte_efd_create()`` is used to create and return a pointer
+to an EFD table that is sized to hold up to num_flows key.
+The online version of the EFD table (the one that does
+not store the keys and is used for lookups) will be allocated and
+created in the last level cache (LLC) of the socket defined by the
+online_socket_bitmask, while the offline EFD table (the one that
+stores the keys and is used for key inserts and for computing the
+perfect hashing) is allocated and created in the LLC of the socket
+defined by offline_socket_bitmask. It should be noted, that for
+highest performance the socket id should match that where the thread is
+running, i.e. the online EFD lookup table should be created on the same
+socket as where the lookup thread is running.
+
+EFD Insert and Update
+~~~~~~~~~~~~~~~~~~~~~
+
+The EFD function to insert a key or update a key to a new value is
+``rte_efd_update()``. This function will update an existing key to
+a new value (target) if the key has already been inserted
+before, or will insert the <key,value> pair if this key has not been inserted
+before. It will return 0 upon success. It will return
+``EFD_UPDATE_WARN_GROUP_FULL (1)`` if the operation is insert, and the
+last available space in the key's group was just used. It will return
+``EFD_UPDATE_FAILED (2)`` when the insertion or update has failed (either it
+failed to find a suitable perfect hash or the group was full). The function
+will return ``EFD_UPDATE_NO_CHANGE (3)`` if there is no change to the EFD
+table (i.e, same value already exists).
+
+EFD Lookup
+~~~~~~~~~~
+
+To lookup a certain key in an EFD table, the function ``rte_efd_lookup()``
+is used to return the value associated with single key.
+As previously mentioned, if the key has been inserted, the correct value
+inserted is returned, if the key has not been inserted before,
+a ‘random’ value (based on hashing of the key) is returned.
+For better performance and to decrease the overhead of
+function calls per key, it is always recommended to use a bulk lookup
+function (simultaneous lookup of multiple keys) instead of a single key
+lookup function. ``rte_efd_lookup_bulk()`` is the bulk lookup function,
+that looks up num_keys simultaneously stored in the key_list and the
+corresponding return values will be returned in the value_list.
+
+EFD Delete
+~~~~~~~~~~
+
+To delete a certain key in an EFD table, the function
+``rte_efd_delete()`` can be used. The function returns zero upon success
+when the key has been found and deleted. Socket_id is the parameter to
+use to lookup the existing value, which is ideally the caller's socket id.
+The previous value associated with this key will be returned
+in the prev_value argument.
+
+.. _Efd_internals:
+
+Library Internals
+-----------------
+
+This section provides the brief high-level idea and an overview
+of the library internals to accompany the RFC. The intent of this
+section is to explain to readers the high-level implementation of
+insert, lookup and group rebalancing in the EFD library.
+
+Insert Function Internals
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As previously mentioned the EFD divides the whole set of keys into
+groups of a manageable size (e.g. 28 keys) and then searches for the
+perfect hash that satisfies the intended target value for each key. EFD
+stores two version of the <key,value> table:
+
+- Offline Version (in memory): Only used for the insertion/update
+ operation, which is less frequent than the lookup operation. In the
+ offline version the exact keys for each group is stored. When a new
+ key is added, the hash function is updated that will satisfy the
+ value for the new key together with the all old keys already inserted
+ in this group.
+
+- Online Version (in cache): Used for the frequent lookup operation. In
+ the online version, as previously mentioned, the keys are not stored
+ but rather only the hash index for each group.
+
+.. _figure_efd7:
+
+.. figure:: img/efd_i7.*
+
+ Group Assignment
+
+:numref:`figure_efd7` depicts the group assignment for 7 flow keys as an example.
+Given a flow key, a hash function (in our implementation CRC hash) is
+used to get the group id. As shown in the figure, the groups can be
+unbalanced. (We highlight group rebalancing further below).
+
+.. _figure_efd8:
+
+.. figure:: img/efd_i8.*
+
+ Perfect Hash Search - Assigned Keys & Target Value
+
+Focusing on one group that has four keys, :numref:`figure_efd8` depicts the search
+algorithm to find the perfect hash function. Assuming that the target
+value bit for the keys is as shown in the figure, then the online EFD
+table will store a 16 bit hash index and 16 bit lookup table per group
+per value bit.
+
+.. _figure_efd9:
+
+.. figure:: img/efd_i9.*
+
+ Perfect Hash Search - Satisfy Target Values
+
+For a given keyX, a hash function ``(h(keyX, seed1) + index * h(keyX, seed2))``
+is used to point to certain bit index in the 16bit lookup_table value,
+as shown in :numref:`figure_efd9`.
+The insert function will brute force search for all possible values for the
+hash index until a non conflicting lookup_table is found.
+
+.. _figure_efd10:
+
+.. figure:: img/efd_i10.*
+
+ Finding Hash Index for Conflict Free lookup_table
+
+For example, since both key3 and key7 have a target bit value of 1, it
+is okay if the hash function of both keys point to the same bit in the
+lookup table. A conflict will occur if a hash index is used that maps
+both Key4 and Key7 to the same index in the lookup_table,
+as shown in :numref:`figure_efd10`, since their target value bit are not the same.
+Once a hash index is found that produces a lookup_table with no
+contradictions, this index is stored for this group. This procedure is
+repeated for each bit of target value.
+
+Lookup Function Internals
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The design principle of EFD is that lookups are much more frequent than
+inserts, and hence, EFD's design optimizes for the lookups which are
+faster and much simpler than the slower insert procedure (inserts are
+slow, because of perfect hash search as previously discussed).
+
+.. _figure_efd11:
+
+.. figure:: img/efd_i11.*
+
+ EFD Lookup Operation
+
+:numref:`figure_efd11` depicts the lookup operation for EFD. Given an input key,
+the group id is computed (using CRC hash) and then the hash index for this
+group is retrieved from the EFD table. Using the retrieved hash index,
+the hash function ``h(key, seed1) + index *h(key, seed2)`` is used which will
+result in an index in the lookup_table, the bit corresponding to this
+index will be the target value bit. This procedure is repeated for each
+bit of the target value.
+
+Group Rebalancing Function Internals
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When discussing EFD inserts and lookups, the discussion is simplified by
+assuming that a group id is simply a result of hash function. However,
+since hashing in general is not perfect and will not always produce a
+uniform output, this simplified assumption will lead to unbalanced
+groups, i.e., some group will have more keys than other groups.
+Typically, and to minimize insert time with an increasing number of keys,
+it is preferable that all groups will have a balanced number of keys, so
+the brute force search for the perfect hash terminates with a valid hash
+index. In order to achieve this target, groups are rebalanced during
+runtime inserts, and keys are moved around from a busy group to a less
+crowded group as the more keys are inserted.
+
+.. _figure_efd12:
+
+.. figure:: img/efd_i12.*
+
+ Runtime Group Rebalancing
+
+:numref:`figure_efd12` depicts the high level idea of group rebalancing, given an
+input key the hash result is split into two parts a chunk id and 8-bit
+bin id. A chunk contains 64 different groups and 256 bins (i.e. for any
+given bin it can map to 4 distinct groups). When a key is inserted, the
+bin id is computed, for example in :numref:`figure_efd12` bin_id=2,
+and since each bin can be mapped to one of four different groups (2 bit storage),
+the four possible mappings are evaluated and the one that will result in a
+balanced key distribution across these four is selected the mapping result
+is stored in these two bits.
diff --git a/doc/guides/prog_guide/img/efd_i1.svg b/doc/guides/prog_guide/img/efd_i1.svg
new file mode 100644
index 0000000..7f8fcb3
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i1.svg
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i1.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="3.25609in" height="3.375in"
+ viewBox="0 0 234.439 243" xml:space="preserve" color-interpolation-filters="sRGB" class="st10">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st5 {marker-end:url(#mrkr5-12);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:6}
+ .st6 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.70422535211268}
+ .st7 {stroke:#5b9bd5;stroke-dasharray:2.25,4.5;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.25}
+ .st8 {marker-end:url(#mrkr5-39);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st9 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st10 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-12" class="st6" v:arrowType="5" v:arrowSize="2" v:setback="2.485" refX="-2.485" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-1.42,-1.42) "/>
+ </marker>
+ <marker id="mrkr5-39" class="st9" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(77.718,-113.348)">
+ <title>Square</title>
+ <desc>LB</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="18" cy="225" width="36" height="36"/>
+ <g id="shadow2-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="207" width="36" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="207" width="36" height="36" class="st3"/>
+ <text x="13.18" y="228" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>LB</text> </g>
+ <g id="shape3-7" v:mID="3" v:groupContext="shape" transform="translate(37.0513,-131.348)">
+ <title>Sheet.3</title>
+ <path d="M0 243 L25.76 243" class="st5"/>
+ </g>
+ <g id="shape4-13" v:mID="4" v:groupContext="shape" transform="translate(167.718,-178.598)">
+ <title>Square.4</title>
+ <desc>Target 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="220.5" width="45" height="45"/>
+ <g id="shadow4-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="198" width="45" height="45" class="st2"/>
+ </g>
+ <rect x="0" y="198" width="45" height="45" class="st3"/>
+ <text x="5.74" y="223.5" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target 1</text> </g>
+ <g id="shape5-19" v:mID="5" v:groupContext="shape" transform="translate(167.718,-121.005)">
+ <title>Square.5</title>
+ <desc>Target 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="220.5" width="45" height="45"/>
+ <g id="shadow5-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="198" width="45" height="45" class="st2"/>
+ </g>
+ <rect x="0" y="198" width="45" height="45" class="st3"/>
+ <text x="5.74" y="223.5" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target 2</text> </g>
+ <g id="shape7-25" v:mID="7" v:groupContext="shape" transform="translate(167.718,-23.3478)">
+ <title>Square.7</title>
+ <desc>Target N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="220.5" width="45" height="45"/>
+ <g id="shadow7-26" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="198" width="45" height="45" class="st2"/>
+ </g>
+ <rect x="0" y="198" width="45" height="45" class="st3"/>
+ <text x="5.05" y="223.5" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target N</text> </g>
+ <g id="shape8-31" v:mID="8" v:groupContext="shape" transform="translate(433.218,132.402) rotate(90)">
+ <title>Sheet.8</title>
+ <path d="M0 243 L34.59 243" class="st7"/>
+ </g>
+ <g id="shape9-34" v:mID="9" v:groupContext="shape" transform="translate(-78.4279,-37.1059) rotate(-52.2532)">
+ <title>Sheet.9</title>
+ <path d="M0 243 L81.18 243" class="st8"/>
+ </g>
+ <g id="shape11-40" v:mID="11" v:groupContext="shape" transform="translate(60.3469,-125.414) rotate(-12.6875)">
+ <title>Sheet.11</title>
+ <path d="M0 243 L48.32 243" class="st8"/>
+ </g>
+ <g id="shape12-45" v:mID="12" v:groupContext="shape" transform="translate(319.172,-18.1081) rotate(57.7244)">
+ <title>Sheet.12</title>
+ <path d="M0 243 L94.09 243" class="st8"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i10.svg b/doc/guides/prog_guide/img/efd_i10.svg
new file mode 100644
index 0000000..d26ec61
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i10.svg
@@ -0,0 +1,384 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i11.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="9.76715in" height="2.82917in"
+ viewBox="0 0 703.234 203.701" xml:space="preserve" color-interpolation-filters="sRGB" class="st15">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st4 {fill:#000000;font-family:Arial;font-size:0.998566em}
+ .st5 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st6 {fill:#000000;font-family:Arial;font-size:0.918686em;font-style:italic}
+ .st7 {fill:#000000;font-family:Arial;font-size:0.918686em}
+ .st8 {fill:#7e8d96;font-family:Arial;font-size:0.998566em;font-weight:bold}
+ .st9 {fill:#00b050;font-family:Arial;font-size:0.998566em;font-weight:bold}
+ .st10 {fill:#ff0000;font-family:Arial;font-size:0.998566em;font-weight:bold}
+ .st11 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st12 {fill:#ffffff;font-family:Arial;font-size:1.49785em}
+ .st13 {stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st14 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st15 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(19.0195,-93.4328)">
+ <title>Sheet.3</title>
+ <path d="M0 182.93 C0 180.64 1.87 178.78 4.16 178.78 L109.18 178.78 C111.47 178.78 113.33 180.64 113.33 182.93 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.16 203.7 C1.87 203.7 0 201.84 0 199.55 L0 182.93 Z"
+ class="st1"/>
+ </g>
+ <g id="shape4-3" v:mID="4" v:groupContext="shape" transform="translate(19.0195,-93.4328)">
+ <title>Sheet.4</title>
+ <path d="M0 182.93 C0 180.64 1.87 178.78 4.16 178.78 L109.18 178.78 C111.47 178.78 113.33 180.64 113.33 182.93 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.16 203.7 C1.87 203.7 0 201.84 0 199.55 L0 182.93 Z"
+ class="st2"/>
+ </g>
+ <g id="shape5-5" v:mID="5" v:groupContext="shape" transform="translate(19.0195,-96.9057)">
+ <title>Sheet.5</title>
+ <desc>Key1: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.0575" cy="196.509" width="116.12" height="14.3829"/>
+ <path d="M116.11 189.32 L0 189.32 L0 203.7 L116.11 203.7 L116.11 189.32" class="st3"/>
+ <text x="15.59" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1: Value = 0</text> </g>
+ <g id="shape6-9" v:mID="6" v:groupContext="shape" transform="translate(19.0195,-68.6284)">
+ <title>Sheet.6</title>
+ <path d="M0 182.93 C0 180.64 1.87 178.78 4.16 178.78 L109.18 178.78 C111.47 178.78 113.33 180.64 113.33 182.93 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.16 203.7 C1.87 203.7 0 201.84 0 199.55 L0 182.93 Z"
+ class="st1"/>
+ </g>
+ <g id="shape7-11" v:mID="7" v:groupContext="shape" transform="translate(19.0195,-68.6284)">
+ <title>Sheet.7</title>
+ <path d="M0 182.93 C0 180.64 1.87 178.78 4.16 178.78 L109.18 178.78 C111.47 178.78 113.33 180.64 113.33 182.93 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.16 203.7 C1.87 203.7 0 201.84 0 199.55 L0 182.93 Z"
+ class="st2"/>
+ </g>
+ <g id="shape8-13" v:mID="8" v:groupContext="shape" transform="translate(19.0195,-72.0832)">
+ <title>Sheet.8</title>
+ <desc>Key3: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.0575" cy="196.509" width="116.12" height="14.3829"/>
+ <path d="M116.11 189.32 L0 189.32 L0 203.7 L116.11 203.7 L116.11 189.32" class="st3"/>
+ <text x="15.59" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3: Value = 1</text> </g>
+ <g id="shape9-17" v:mID="9" v:groupContext="shape" transform="translate(19.0195,-43.5843)">
+ <title>Sheet.9</title>
+ <path d="M0 182.84 C-0 180.53 1.88 178.66 4.19 178.66 L109.15 178.66 C111.46 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.53 C113.33 201.84 111.46 203.7 109.15 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.53 L0 182.84 Z"
+ class="st1"/>
+ </g>
+ <g id="shape10-19" v:mID="10" v:groupContext="shape" transform="translate(19.0195,-43.5843)">
+ <title>Sheet.10</title>
+ <path d="M0 182.84 C-0 180.53 1.88 178.66 4.19 178.66 L109.15 178.66 C111.46 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.53 C113.33 201.84 111.46 203.7 109.15 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.53 L0 182.84 Z"
+ class="st2"/>
+ </g>
+ <g id="shape11-21" v:mID="11" v:groupContext="shape" transform="translate(19.0195,-47.1109)">
+ <title>Sheet.11</title>
+ <desc>Key4: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.0575" cy="196.509" width="116.12" height="14.3829"/>
+ <path d="M116.11 189.32 L0 189.32 L0 203.7 L116.11 203.7 L116.11 189.32" class="st3"/>
+ <text x="15.59" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4: Value = 0</text> </g>
+ <g id="shape12-25" v:mID="12" v:groupContext="shape" transform="translate(19.0195,-19.0195)">
+ <title>Sheet.12</title>
+ <path d="M0 182.84 C-0 180.53 1.88 178.66 4.19 178.66 L109.15 178.66 C111.46 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.53 C113.33 201.84 111.46 203.7 109.15 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.53 L0 182.84 Z"
+ class="st1"/>
+ </g>
+ <g id="shape13-27" v:mID="13" v:groupContext="shape" transform="translate(19.0195,-19.0195)">
+ <title>Sheet.13</title>
+ <path d="M0 182.84 C-0 180.53 1.88 178.66 4.19 178.66 L109.15 178.66 C111.46 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.53 C113.33 201.84 111.46 203.7 109.15 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.53 L0 182.84 Z"
+ class="st2"/>
+ </g>
+ <g id="shape14-29" v:mID="14" v:groupContext="shape" transform="translate(19.0195,-22.5475)">
+ <title>Sheet.14</title>
+ <desc>Key7: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.0575" cy="196.509" width="116.12" height="14.3829"/>
+ <path d="M116.11 189.32 L0 189.32 L0 203.7 L116.11 203.7 L116.11 189.32" class="st3"/>
+ <text x="15.59" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7: Value = 1</text> </g>
+ <g id="shape15-33" v:mID="15" v:groupContext="shape" transform="translate(141.656,-45.5615)">
+ <title>Sheet.15</title>
+ <path d="M0 169.01 L22.75 169.01 L22.75 157.45 L45.5 180.57 L22.75 203.7 L22.75 192.14 L0 192.14 L0 169.01 Z"
+ class="st5"/>
+ </g>
+ <g id="shape16-35" v:mID="16" v:groupContext="shape" transform="translate(193.22,-56.0464)">
+ <title>Sheet.16</title>
+ <path d="M0 182.84 C0 180.53 1.88 178.66 4.19 178.66 L96.55 178.66 C98.87 178.66 100.73 180.53 100.73 182.84 L100.73
+ 199.54 C100.73 201.84 98.87 203.7 96.55 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.54 L0 182.84 Z"
+ class="st1"/>
+ </g>
+ <g id="shape17-37" v:mID="17" v:groupContext="shape" transform="translate(193.22,-56.0464)">
+ <title>Sheet.17</title>
+ <path d="M0 182.84 C0 180.53 1.88 178.66 4.19 178.66 L96.55 178.66 C98.87 178.66 100.73 180.53 100.73 182.84 L100.73
+ 199.54 C100.73 201.84 98.87 203.7 96.55 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.54 L0 182.84 Z"
+ class="st2"/>
+ </g>
+ <g id="shape18-39" v:mID="18" v:groupContext="shape" transform="translate(228.157,-66.9545)">
+ <title>Sheet.18</title>
+ <desc>F</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.63538" cy="197.084" width="11.28" height="13.2327"/>
+ <path d="M11.27 190.47 L0 190.47 L0 203.7 L11.27 203.7 L11.27 190.47" class="st3"/>
+ <text x="2.27" y="200.39" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>F</text> </g>
+ <g id="shape19-43" v:mID="19" v:groupContext="shape" transform="translate(234.88,-66.9545)">
+ <title>Sheet.19</title>
+ <desc>(key,</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.261" cy="197.084" width="34.53" height="13.2327"/>
+ <path d="M34.52 190.47 L0 190.47 L0 203.7 L34.52 203.7 L34.52 190.47" class="st3"/>
+ <text x="5.32" y="200.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(key, </text> </g>
+ <g id="shape20-47" v:mID="20" v:groupContext="shape" transform="translate(198.215,-53.7734)">
+ <title>Sheet.20</title>
+ <desc>hash_index =</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="41.4128" cy="197.084" width="82.83" height="13.2327"/>
+ <path d="M82.83 190.47 L0 190.47 L0 203.7 L82.83 203.7 L82.83 190.47" class="st3"/>
+ <text x="8.47" y="200.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash_index = </text> </g>
+ <g id="shape21-51" v:mID="21" v:groupContext="shape" transform="translate(274.858,-53.7734)">
+ <title>Sheet.21</title>
+ <desc>i)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.28241" cy="197.084" width="10.57" height="13.2327"/>
+ <path d="M10.56 190.47 L0 190.47 L0 203.7 L10.56 203.7 L10.56 190.47" class="st3"/>
+ <text x="2.22" y="200.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>i)</text> </g>
+ <g id="shape22-55" v:mID="22" v:groupContext="shape" transform="translate(351.453,-93.7923)">
+ <title>Sheet.22</title>
+ <path d="M0 182.84 C0 180.53 1.88 178.66 4.19 178.66 L109.16 178.66 C111.47 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.54 C113.33 201.84 111.47 203.7 109.16 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.54 L0 182.84 Z"
+ class="st1"/>
+ </g>
+ <g id="shape23-57" v:mID="23" v:groupContext="shape" transform="translate(351.453,-93.7923)">
+ <title>Sheet.23</title>
+ <path d="M0 182.84 C0 180.53 1.88 178.66 4.19 178.66 L109.16 178.66 C111.47 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.54 C113.33 201.84 111.47 203.7 109.16 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.54 L0 182.84 Z"
+ class="st2"/>
+ </g>
+ <g id="shape24-59" v:mID="24" v:groupContext="shape" transform="translate(355.798,-97.3147)">
+ <title>Sheet.24</title>
+ <desc>Key1: Position 4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="51.7083" cy="196.509" width="103.42" height="14.3829"/>
+ <path d="M103.42 189.32 L0 189.32 L0 203.7 L103.42 203.7 L103.42 189.32" class="st3"/>
+ <text x="8.41" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1: Position 4</text> </g>
+ <g id="shape25-63" v:mID="25" v:groupContext="shape" transform="translate(351.453,-68.9879)">
+ <title>Sheet.25</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape26-65" v:mID="26" v:groupContext="shape" transform="translate(351.453,-68.9879)">
+ <title>Sheet.26</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape27-67" v:mID="27" v:groupContext="shape" transform="translate(355.798,-72.4921)">
+ <title>Sheet.27</title>
+ <desc>Key3: Position 6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="51.7083" cy="196.509" width="103.42" height="14.3829"/>
+ <path d="M103.42 189.32 L0 189.32 L0 203.7 L103.42 203.7 L103.42 189.32" class="st3"/>
+ <text x="8.41" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3: Position 6</text> </g>
+ <g id="shape28-71" v:mID="28" v:groupContext="shape" transform="translate(351.453,-44.0636)">
+ <title>Sheet.28</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape29-73" v:mID="29" v:groupContext="shape" transform="translate(351.453,-44.0636)">
+ <title>Sheet.29</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape30-75" v:mID="30" v:groupContext="shape" transform="translate(351.215,-47.5198)">
+ <title>Sheet.30</title>
+ <desc>Key4: Position 14</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="55.5403" cy="196.509" width="111.09" height="14.3829"/>
+ <path d="M111.08 189.32 L0 189.32 L0 203.7 L111.08 203.7 L111.08 189.32" class="st3"/>
+ <text x="8.91" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4: Position 14</text> </g>
+ <g id="shape31-79" v:mID="31" v:groupContext="shape" transform="translate(351.453,-19.4988)">
+ <title>Sheet.31</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape32-81" v:mID="32" v:groupContext="shape" transform="translate(351.453,-19.4988)">
+ <title>Sheet.32</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape33-83" v:mID="33" v:groupContext="shape" transform="translate(351.215,-22.9565)">
+ <title>Sheet.33</title>
+ <desc>Key7: Position 14</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="55.5403" cy="196.509" width="111.09" height="14.3829"/>
+ <path d="M111.08 189.32 L0 189.32 L0 203.7 L111.08 203.7 L111.08 189.32" class="st3"/>
+ <text x="8.91" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7: Position 14</text> </g>
+ <g id="shape34-87" v:mID="34" v:groupContext="shape" transform="translate(299.89,-46.0408)">
+ <title>Sheet.34</title>
+ <path d="M0 169.01 L22.75 169.01 L22.75 157.45 L45.5 180.57 L22.75 203.7 L22.75 192.14 L0 192.14 L0 169.01 Z"
+ class="st5"/>
+ </g>
+ <g id="shape35-89" v:mID="35" v:groupContext="shape" transform="translate(528.896,-117.518)">
+ <title>Sheet.35</title>
+ <path d="M0 182.94 C0 180.66 1.89 178.78 4.17 178.78 L137.64 178.78 C139.92 178.78 141.79 180.66 141.79 182.94 L141.79
+ 199.57 C141.79 201.84 139.92 203.7 137.64 203.7 L4.17 203.7 C1.89 203.7 0 201.84 0 199.57 L0 182.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape36-91" v:mID="36" v:groupContext="shape" transform="translate(528.896,-117.518)">
+ <title>Sheet.36</title>
+ <path d="M0 182.94 C0 180.66 1.89 178.78 4.17 178.78 L137.64 178.78 C139.92 178.78 141.79 180.66 141.79 182.94 L141.79
+ 199.57 C141.79 201.84 139.92 203.7 137.64 203.7 L4.17 203.7 C1.89 203.7 0 201.84 0 199.57 L0 182.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape37-93" v:mID="37" v:groupContext="shape" transform="translate(530.056,-121.017)">
+ <title>Sheet.37</title>
+ <desc>0000</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="19.1585" cy="196.509" width="38.32" height="14.3829"/>
+ <path d="M38.32 189.32 L0 189.32 L0 203.7 L38.32 203.7 L38.32 189.32" class="st3"/>
+ <text x="5.83" y="200.1" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0000 </text> </g>
+ <g id="shape38-97" v:mID="38" v:groupContext="shape" transform="translate(567.215,-121.017)">
+ <title>Sheet.38</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.7483" cy="196.509" width="11.5" height="14.3829"/>
+ <path d="M11.5 189.32 L0 189.32 L0 203.7 L11.5 203.7 L11.5 189.32" class="st3"/>
+ <text x="2.42" y="200.1" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape39-101" v:mID="39" v:groupContext="shape" transform="translate(576.215,-121.017)">
+ <title>Sheet.39</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.7483" cy="196.509" width="11.5" height="14.3829"/>
+ <path d="M11.5 189.32 L0 189.32 L0 203.7 L11.5 203.7 L11.5 189.32" class="st3"/>
+ <text x="2.42" y="200.1" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape40-105" v:mID="40" v:groupContext="shape" transform="translate(584.486,-121.017)">
+ <title>Sheet.40</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.7483" cy="196.509" width="11.5" height="14.3829"/>
+ <path d="M11.5 189.32 L0 189.32 L0 203.7 L11.5 203.7 L11.5 189.32" class="st3"/>
+ <text x="2.42" y="200.1" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape41-109" v:mID="41" v:groupContext="shape" transform="translate(588.646,-121.017)">
+ <title>Sheet.41</title>
+ <desc>0 0000 00</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="32.5687" cy="196.509" width="65.14" height="14.3829"/>
+ <path d="M65.14 189.32 L0 189.32 L0 203.7 L65.14 203.7 L65.14 189.32" class="st3"/>
+ <text x="5.91" y="200.1" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0 0000 00</text> </g>
+ <g id="shape42-113" v:mID="42" v:groupContext="shape" transform="translate(644.965,-121.017)">
+ <title>Sheet.42</title>
+ <desc>?</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="6.12511" cy="196.509" width="12.26" height="14.3829"/>
+ <path d="M12.25 189.32 L0 189.32 L0 203.7 L12.25 203.7 L12.25 189.32" class="st3"/>
+ <text x="2.47" y="200.1" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>?</text> </g>
+ <g id="shape43-117" v:mID="43" v:groupContext="shape" transform="translate(654.718,-121.017)">
+ <title>Sheet.43</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.7483" cy="196.509" width="11.5" height="14.3829"/>
+ <path d="M11.5 189.32 L0 189.32 L0 203.7 L11.5 203.7 L11.5 189.32" class="st3"/>
+ <text x="2.42" y="200.1" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape44-121" v:mID="44" v:groupContext="shape" transform="translate(464.786,-105.296)">
+ <title>Sheet.44</title>
+ <path d="M0 203.7 L108.29 203.7 C108.86 203.7 109.31 203.22 109.31 202.68 L109.31 189.5 L107.27 189.5 L107.27 202.68
+ L108.29 201.66 L0 201.66 L0 203.7 ZM111.35 190.52 L108.29 184.41 L105.23 190.55 L111.35 190.52 Z"
+ class="st11"/>
+ </g>
+ <g id="shape45-123" v:mID="45" v:groupContext="shape" transform="translate(464.786,-80.4315)">
+ <title>Sheet.45</title>
+ <path d="M0 203.7 L123.63 203.7 C124.2 203.7 124.65 203.25 124.65 202.68 L124.65 164.28 L122.61 164.28 L122.61 202.68
+ L123.63 201.66 L0 201.66 L0 203.7 ZM126.69 165.3 L123.6 159.18 L120.57 165.33 L126.69 165.3 Z"
+ class="st11"/>
+ </g>
+ <g id="shape46-125" v:mID="46" v:groupContext="shape" transform="translate(464.786,-55.4772)">
+ <title>Sheet.46</title>
+ <path d="M0 203.7 L186.48 203.7 C186.75 203.7 186.99 203.61 187.2 203.4 C187.38 203.22 187.5 202.95 187.5 202.68 L187.41
+ 139.32 L185.37 139.32 L185.46 202.68 L186.48 201.66 L0 201.66 L0 203.7 ZM189.51 140.07 L185.94 134.23 L183.41
+ 140.61 L189.51 140.07 Z" class="st11"/>
+ </g>
+ <g id="shape47-127" v:mID="47" v:groupContext="shape" transform="translate(464.786,-30.9125)">
+ <title>Sheet.47</title>
+ <path d="M0 203.7 L186.48 203.7 C186.75 203.7 186.99 203.61 187.2 203.4 C187.38 203.22 187.5 202.95 187.5 202.68 L187.41
+ 114.76 L185.37 114.76 L185.46 202.68 L186.48 201.66 L0 201.66 L0 203.7 ZM189.51 115.51 L185.94 109.67 L183.41
+ 116.05 L189.51 115.51 Z" class="st11"/>
+ </g>
+ <g id="shape48-129" v:mID="48" v:groupContext="shape" transform="translate(442.996,-151.106)">
+ <title>Sheet.48</title>
+ <path d="M0 179.56 C0 176.89 2.19 174.7 4.86 174.7 L70.8 174.7 C73.47 174.7 75.64 176.89 75.64 179.56 L75.64 198.88 C75.64
+ 201.54 73.47 203.7 70.8 203.7 L4.86 203.7 C2.19 203.7 0 201.54 0 198.88 L0 179.56 Z" class="st5"/>
+ </g>
+ <g id="shape49-131" v:mID="49" v:groupContext="shape" transform="translate(443.529,-155.018)">
+ <title>Sheet.49</title>
+ <desc>Values</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="37.8175" cy="192.914" width="75.64" height="21.5726"/>
+ <path d="M75.64 182.13 L0 182.13 L0 203.7 L75.64 203.7 L75.64 182.13" class="st3"/>
+ <text x="10.34" y="198.31" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Values</text> </g>
+ <g id="shape50-135" v:mID="50" v:groupContext="shape" transform="translate(102.458,-122.192)">
+ <title>Sheet.50</title>
+ <path d="M0 203.7 C-0 199.21 0.62 195.55 1.37 195.55 L11.67 195.55 C12.42 195.55 13.03 191.9 13.03 187.4 C13.03 191.9
+ 13.64 195.55 14.39 195.55 L24.69 195.55 C25.44 195.55 26.05 199.21 26.05 203.7" class="st13"/>
+ </g>
+ <g id="shape51-138" v:mID="51" v:groupContext="shape" transform="translate(115.454,-137.5)">
+ <title>Sheet.51</title>
+ <path d="M0.2 203.7 L322.66 174.12 L322.48 172.1 L0 201.68 L0.2 203.7 L0.2 203.7 ZM321.84 176.24 L327.66 172.64 L321.28
+ 170.16 L321.84 176.24 L321.84 176.24 Z" class="st14"/>
+ </g>
+ <g id="shape52-140" v:mID="52" v:groupContext="shape" transform="translate(518.211,-142.473)">
+ <title>Sheet.52</title>
+ <path d="M0.99 176.74 L44.78 200.38 L43.82 202.17 L0 178.51 L0.99 176.74 L0.99 176.74 ZM44.87 198.1 L48.8 203.7 L41.96
+ 203.46 L44.87 198.1 L44.87 198.1 Z" class="st11"/>
+ </g>
+ <g id="shape53-142" v:mID="53" v:groupContext="shape" transform="translate(518.331,-141.963)">
+ <title>Sheet.53</title>
+ <path d="M0.75 176.17 L60.09 200.32 L59.34 202.2 L0 178.06 L0.75 176.17 L0.75 176.17 ZM59.91 198.04 L64.44 203.19 L57.6
+ 203.7 L59.91 198.04 L59.91 198.04 Z" class="st11"/>
+ </g>
+ <g id="shape54-144" v:mID="54" v:groupContext="shape" transform="translate(576.558,-153.706)">
+ <title>Sheet.54</title>
+ <path d="M0 177.04 C0 174.1 2.4 171.71 5.34 171.71 L101.51 171.71 C104.48 171.71 106.85 174.1 106.85 177.04 L106.85 198.37
+ C106.85 201.33 104.48 203.7 101.51 203.7 L5.34 203.7 C2.4 203.7 0 201.33 0 198.37 L0 177.04 Z" class="st1"/>
+ </g>
+ <g id="shape55-146" v:mID="55" v:groupContext="shape" transform="translate(577.365,-151.611)">
+ <title>Sheet.55</title>
+ <path d="M0 177.04 C0 174.1 2.4 171.71 5.34 171.71 L101.51 171.71 C104.48 171.71 106.85 174.1 106.85 177.04 L106.85 198.37
+ C106.85 201.33 104.48 203.7 101.51 203.7 L5.34 203.7 C2.4 203.7 0 201.33 0 198.37 L0 177.04 Z" class="st2"/>
+ </g>
+ <g id="shape56-148" v:mID="56" v:groupContext="shape" transform="translate(593.952,-167.894)">
+ <title>Sheet.56</title>
+ <desc>Lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.2942" cy="196.509" width="86.59" height="14.3829"/>
+ <path d="M86.59 189.32 L0 189.32 L0 203.7 L86.59 203.7 L86.59 189.32" class="st3"/>
+ <text x="7.31" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Lookup_table</text> </g>
+ <g id="shape57-152" v:mID="57" v:groupContext="shape" transform="translate(608.239,-153.515)">
+ <title>Sheet.57</title>
+ <desc>(16 bits)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="26.8054" cy="196.509" width="53.62" height="14.3829"/>
+ <path d="M53.61 189.32 L0 189.32 L0 203.7 L53.61 203.7 L53.61 189.32" class="st3"/>
+ <text x="5.16" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(16 bits)</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i11.svg b/doc/guides/prog_guide/img/efd_i11.svg
new file mode 100644
index 0000000..f2cc656
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i11.svg
@@ -0,0 +1,319 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i12.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="10.2783in" height="4.28958in"
+ viewBox="0 0 740.039 308.85" xml:space="preserve" color-interpolation-filters="sRGB" class="st21">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st4 {fill:#000000;font-family:Arial;font-size:0.998566em}
+ .st5 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st6 {stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st7 {stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st8 {fill:#000000;font-family:Arial;font-size:0.918686em;font-weight:bold}
+ .st9 {fill:#00b050;font-size:1em}
+ .st10 {fill:#c00000;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st11 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.00749637}
+ .st12 {fill:#ffffff;font-family:Arial;font-size:1.16833em}
+ .st13 {fill:#2e75b5;stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st14 {fill:#ffffff;font-family:Arial;font-size:1.16666em}
+ .st15 {font-size:1em}
+ .st16 {fill:none;stroke:none;stroke-width:0.25}
+ .st17 {fill:#000000;font-family:Calibri;font-size:1.00001em}
+ .st18 {marker-end:url(#mrkr5-121);stroke:#5b9bd5;stroke-dasharray:1.5,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st19 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st20 {fill:#000000;font-family:Calibri;font-size:1.16666em}
+ .st21 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-121" class="st19" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape5-1" v:mID="5" v:groupContext="shape" transform="translate(36.0674,-256.878)">
+ <title>Sheet.5</title>
+ <path d="M0 291.88 C0 290 1.52 288.48 3.41 288.48 L68.51 288.48 C70.4 288.48 71.91 290 71.91 291.88 L71.91 305.46 C71.91
+ 307.33 70.4 308.85 68.51 308.85 L3.41 308.85 C1.52 308.85 0 307.33 0 305.46 L0 291.88 Z" class="st1"/>
+ </g>
+ <g id="shape6-3" v:mID="6" v:groupContext="shape" transform="translate(36.0674,-256.878)">
+ <title>Sheet.6</title>
+ <path d="M0 291.88 C0 290 1.52 288.48 3.41 288.48 L68.51 288.48 C70.4 288.48 71.91 290 71.91 291.88 L71.91 305.46 C71.91
+ 307.33 70.4 308.85 68.51 308.85 L3.41 308.85 C1.52 308.85 0 307.33 0 305.46 L0 291.88 Z" class="st2"/>
+ </g>
+ <g id="shape7-5" v:mID="7" v:groupContext="shape" transform="translate(61.6502,-258.089)">
+ <title>Sheet.7</title>
+ <desc>Key</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="13.7891" cy="301.658" width="27.58" height="14.3829"/>
+ <path d="M27.58 294.47 L0 294.47 L0 308.85 L27.58 308.85 L27.58 294.47" class="st3"/>
+ <text x="3.46" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key</text> </g>
+ <g id="shape8-9" v:mID="8" v:groupContext="shape" transform="translate(51.9748,-236.328)">
+ <title>Sheet.8</title>
+ <path d="M0 298.54 L9.81 298.54 L9.81 288.24 L29.44 288.24 L29.44 298.54 L39.26 298.54 L19.63 308.85 L0 298.54 Z"
+ class="st5"/>
+ </g>
+ <g id="shape9-11" v:mID="9" v:groupContext="shape" transform="translate(36.0674,-215.298)">
+ <title>Sheet.9</title>
+ <path d="M0 291.77 C0 289.89 1.54 288.36 3.42 288.36 L68.49 288.36 C70.38 288.36 71.91 289.89 71.91 291.77 L71.91 305.43
+ C71.91 307.32 70.38 308.85 68.49 308.85 L3.42 308.85 C1.54 308.85 0 307.32 0 305.43 L0 291.77 Z"
+ class="st1"/>
+ </g>
+ <g id="shape10-13" v:mID="10" v:groupContext="shape" transform="translate(36.0674,-215.298)">
+ <title>Sheet.10</title>
+ <path d="M0 291.77 C0 289.89 1.54 288.36 3.42 288.36 L68.49 288.36 C70.38 288.36 71.91 289.89 71.91 291.77 L71.91 305.43
+ C71.91 307.32 70.38 308.85 68.49 308.85 L3.42 308.85 C1.54 308.85 0 307.32 0 305.43 L0 291.77 Z"
+ class="st2"/>
+ </g>
+ <g id="shape11-15" v:mID="11" v:groupContext="shape" transform="translate(58.8889,-216.57)">
+ <title>Sheet.11</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.8573" cy="301.658" width="33.72" height="14.3829"/>
+ <path d="M33.71 294.47 L0 294.47 L0 308.85 L33.71 308.85 L33.71 294.47" class="st3"/>
+ <text x="3.86" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape12-19" v:mID="12" v:groupContext="shape" transform="translate(27.3033,-174.437)">
+ <title>Sheet.12</title>
+ <path d="M0 292.58 C0 290.78 1.46 289.32 3.26 289.32 L87.15 289.32 C88.95 289.32 90.4 290.78 90.4 292.58 L90.4 305.6
+ C90.4 307.4 88.95 308.85 87.15 308.85 L3.26 308.85 C1.46 308.85 0 307.4 0 305.6 L0 292.58 Z" class="st1"/>
+ </g>
+ <g id="shape13-21" v:mID="13" v:groupContext="shape" transform="translate(27.3033,-174.437)">
+ <title>Sheet.13</title>
+ <path d="M0 292.58 C0 290.78 1.46 289.32 3.26 289.32 L87.15 289.32 C88.95 289.32 90.4 290.78 90.4 292.58 L90.4 305.6
+ C90.4 307.4 88.95 308.85 87.15 308.85 L3.26 308.85 C1.46 308.85 0 307.4 0 305.6 L0 292.58 Z" class="st2"/>
+ </g>
+ <g id="shape14-23" v:mID="14" v:groupContext="shape" transform="translate(36.0515,-175.256)">
+ <title>Sheet.14</title>
+ <desc>0x0102ABCD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.6644" cy="301.658" width="87.33" height="14.3829"/>
+ <path d="M87.33 294.47 L0 294.47 L0 308.85 L87.33 308.85 L87.33 294.47" class="st3"/>
+ <text x="7.36" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102ABCD</text> </g>
+ <g id="shape15-27" v:mID="15" v:groupContext="shape" transform="translate(51.9748,-194.029)">
+ <title>Sheet.15</title>
+ <path d="M0 298.48 L9.81 298.48 L9.81 288.12 L29.44 288.12 L29.44 298.48 L39.26 298.48 L19.63 308.85 L0 298.48 Z"
+ class="st5"/>
+ </g>
+ <g id="shape16-29" v:mID="16" v:groupContext="shape" transform="translate(48.9133,-159.818)">
+ <title>Sheet.16</title>
+ <path d="M26.41 296.87 C26.41 300.18 25.97 302.86 25.41 302.86 L14.21 302.86 C13.66 302.86 13.21 305.55 13.21 308.85
+ C13.21 305.55 12.76 302.86 12.21 302.86 L1.01 302.86 C0.45 302.86 0 300.18 0 296.87" class="st6"/>
+ </g>
+ <g id="shape17-32" v:mID="17" v:groupContext="shape" transform="translate(19.0195,-19.0195)">
+ <title>Sheet.17</title>
+ <path d="M0 196.93 L0 308.85 L145.15 308.85 L145.15 196.93 L0 196.93 L0 196.93 Z" class="st1"/>
+ </g>
+ <g id="shape18-34" v:mID="18" v:groupContext="shape" transform="translate(19.0195,-19.0195)">
+ <title>Sheet.18</title>
+ <path d="M0 196.93 L145.15 196.93 L145.15 308.85 L0 308.85 L0 196.93" class="st7"/>
+ </g>
+ <g id="shape19-37" v:mID="19" v:groupContext="shape" transform="translate(28.2638,-70.6655)">
+ <title>Sheet.19</title>
+ <path d="M0 280.69 C0 277.58 2.53 275.06 5.64 275.06 L124.14 275.06 C127.26 275.06 129.78 277.58 129.78 280.69 L129.78
+ 303.22 C129.78 306.33 127.26 308.85 124.14 308.85 L5.64 308.85 C2.53 308.85 0 306.33 0 303.22 L0 280.69
+ Z" class="st1"/>
+ </g>
+ <g id="shape20-39" v:mID="20" v:groupContext="shape" transform="translate(28.2638,-70.6655)">
+ <title>Sheet.20</title>
+ <path d="M0 280.69 C0 277.58 2.53 275.06 5.64 275.06 L124.14 275.06 C127.26 275.06 129.78 277.58 129.78 280.69 L129.78
+ 303.22 C129.78 306.33 127.26 308.85 124.14 308.85 L5.64 308.85 C2.53 308.85 0 306.33 0 303.22 L0 280.69
+ Z" class="st2"/>
+ </g>
+ <g id="shape21-41" v:mID="21" v:groupContext="shape" transform="translate(57.4514,-85.7513)">
+ <title>Sheet.21</title>
+ <desc>hash_index =</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="45.0133" cy="301.658" width="90.03" height="14.3829"/>
+ <path d="M90.03 294.47 L0 294.47 L0 308.85 L90.03 308.85 L90.03 294.47" class="st3"/>
+ <text x="9.2" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash_index = </text> </g>
+ <g id="shape22-45" v:mID="22" v:groupContext="shape" transform="translate(76.3001,-71.3719)">
+ <title>Sheet.22</title>
+ <desc>38123</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.0762" cy="301.658" width="42.16" height="14.3829"/>
+ <path d="M42.15 294.47 L0 294.47 L0 308.85 L42.15 308.85 L42.15 294.47" class="st3"/>
+ <text x="4.42" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>38123</text> </g>
+ <g id="shape23-49" v:mID="23" v:groupContext="shape" transform="translate(28.2638,-27.048)">
+ <title>Sheet.23</title>
+ <path d="M0 280.69 C0 277.59 2.54 275.06 5.64 275.06 L124.14 275.06 C127.26 275.06 129.78 277.59 129.78 280.69 L129.78
+ 303.22 C129.78 306.33 127.26 308.85 124.14 308.85 L5.64 308.85 C2.54 308.85 0 306.33 0 303.22 L0 280.69
+ Z" class="st1"/>
+ </g>
+ <g id="shape24-51" v:mID="24" v:groupContext="shape" transform="translate(28.2638,-27.048)">
+ <title>Sheet.24</title>
+ <path d="M0 280.69 C0 277.59 2.54 275.06 5.64 275.06 L124.14 275.06 C127.26 275.06 129.78 277.59 129.78 280.69 L129.78
+ 303.22 C129.78 306.33 127.26 308.85 124.14 308.85 L5.64 308.85 C2.54 308.85 0 306.33 0 303.22 L0 280.69
+ Z" class="st2"/>
+ </g>
+ <g id="shape25-53" v:mID="25" v:groupContext="shape" transform="translate(54.0924,-41.564)">
+ <title>Sheet.25</title>
+ <desc>lookup_table =</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="46.931" cy="301.658" width="93.87" height="14.3829"/>
+ <path d="M93.86 294.47 L0 294.47 L0 308.85 L93.86 308.85 L93.86 294.47" class="st3"/>
+ <text x="7.79" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table =</text> </g>
+ <g id="shape26-57" v:mID="26" v:groupContext="shape" transform="translate(28.0195,-28.5506)">
+ <title>Sheet.26</title>
+ <desc>0110 1100 0101 1101</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="64.89" cy="302.233" width="129.79" height="13.2327"/>
+ <path d="M129.78 295.62 L0 295.62 L0 308.85 L129.78 308.85 L129.78 295.62" class="st3"/>
+ <text x="11.25" y="305.54" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0110 11<tspan
+ class="st9">0</tspan>0 0101 1101</text> </g>
+ <g id="shape27-62" v:mID="27" v:groupContext="shape" transform="translate(26.2461,-113.863)">
+ <title>Sheet.27</title>
+ <desc>Group ID: 0x0102</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="48.6286" cy="302.881" width="97.26" height="11.9384"/>
+ <path d="M97.26 296.91 L0 296.91 L0 308.85 L97.26 308.85 L97.26 296.91" class="st3"/>
+ <text x="7.73" y="305.86" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group ID: 0x0102</text> </g>
+ <g id="shape28-66" v:mID="28" v:groupContext="shape" transform="translate(42.3703,-135.313)">
+ <title>Sheet.28</title>
+ <path d="M0 298.48 L9.84 298.48 L9.84 288.12 L29.53 288.12 L29.53 298.48 L39.38 298.48 L19.69 308.85 L0 298.48 Z"
+ class="st5"/>
+ </g>
+ <g id="shape29-68" v:mID="29" v:groupContext="shape" transform="translate(117.645,-244.476)">
+ <title>Sheet.29</title>
+ <path d="M0 274.07 L22.75 274.07 L22.75 262.48 L45.5 285.66 L22.75 308.85 L22.75 297.26 L0 297.26 L0 274.07 Z"
+ class="st5"/>
+ </g>
+ <g id="shape30-70" v:mID="30" v:groupContext="shape" transform="translate(169.209,-251.966)">
+ <title>Sheet.30</title>
+ <path d="M0 283.69 C0 280.91 2.27 278.65 5.04 278.65 L111.77 278.65 C114.56 278.65 116.81 280.91 116.81 283.69 L116.81
+ 303.82 C116.81 306.6 114.56 308.85 111.77 308.85 L5.04 308.85 C2.27 308.85 0 306.6 0 303.82 L0 283.69 Z"
+ class="st1"/>
+ </g>
+ <g id="shape31-72" v:mID="31" v:groupContext="shape" transform="translate(169.209,-251.966)">
+ <title>Sheet.31</title>
+ <path d="M0 283.69 C0 280.91 2.27 278.65 5.04 278.65 L111.77 278.65 C114.56 278.65 116.81 280.91 116.81 283.69 L116.81
+ 303.82 C116.81 306.6 114.56 308.85 111.77 308.85 L5.04 308.85 C2.27 308.85 0 306.6 0 303.82 L0 283.69 Z"
+ class="st2"/>
+ </g>
+ <g id="shape35-74" v:mID="35" v:groupContext="shape" transform="translate(291.966,-244.476)">
+ <title>Sheet.35</title>
+ <path d="M0 274.07 L22.69 274.07 L22.69 262.48 L45.38 285.66 L22.69 308.85 L22.69 297.26 L0 297.26 L0 274.07 Z"
+ class="st5"/>
+ </g>
+ <g id="shape36-76" v:mID="36" v:groupContext="shape" transform="translate(343.17,-254.482)">
+ <title>Sheet.36</title>
+ <path d="M0 288.09 C0 285.8 1.88 283.93 4.17 283.93 L109.18 283.93 C111.47 283.93 113.33 285.8 113.33 288.09 L113.33
+ 304.7 C113.33 306.99 111.47 308.85 109.18 308.85 L4.17 308.85 C1.88 308.85 0 306.99 0 304.7 L0 288.09 Z"
+ class="st1"/>
+ </g>
+ <g id="shape37-78" v:mID="37" v:groupContext="shape" transform="translate(343.17,-254.482)">
+ <title>Sheet.37</title>
+ <path d="M0 288.09 C0 285.8 1.88 283.93 4.17 283.93 L109.18 283.93 C111.47 283.93 113.33 285.8 113.33 288.09 L113.33
+ 304.7 C113.33 306.99 111.47 308.85 109.18 308.85 L4.17 308.85 C1.88 308.85 0 306.99 0 304.7 L0 288.09 Z"
+ class="st2"/>
+ </g>
+ <g id="shape38-80" v:mID="38" v:groupContext="shape" transform="translate(368.337,-257.958)">
+ <title>Sheet.38</title>
+ <desc>Position = 6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="38.1131" cy="301.658" width="76.23" height="14.3829"/>
+ <path d="M76.23 294.47 L0 294.47 L0 308.85 L76.23 308.85 L76.23 294.47" class="st3"/>
+ <text x="6.64" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Position = 6</text> </g>
+ <g id="shape39-84" v:mID="39" v:groupContext="shape" transform="translate(158.044,-86.5202)">
+ <title>Sheet.39</title>
+ <path d="M0 308.85 L69.59 308.85 C70.16 308.85 70.62 308.39 70.62 307.83 L70.62 148.5 L68.57 148.5 L68.57 307.83 L69.59
+ 306.81 L0 306.81 L0 308.85 ZM72.66 149.52 L69.59 143.4 L66.53 149.52 L72.66 149.52 Z" class="st11"/>
+ </g>
+ <g id="shape41-86" v:mID="41" v:groupContext="shape" transform="translate(335.112,-199.647)">
+ <title>Sheet.41</title>
+ <desc>Apply the equation</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="71.2648" cy="300.436" width="142.53" height="16.8275"/>
+ <path d="M142.53 292.02 L0 292.02 L0 308.85 L142.53 308.85 L142.53 292.02" class="st3"/>
+ <text x="13.19" y="304.64" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Apply the equation </text> </g>
+ <g id="shape42-90" v:mID="42" v:groupContext="shape" transform="translate(341.115,-182.871)">
+ <title>Sheet.42</title>
+ <desc>to retrieve the bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="64.5256" cy="300.436" width="129.06" height="16.8275"/>
+ <path d="M129.05 292.02 L0 292.02 L0 308.85 L129.05 308.85 L129.05 292.02" class="st3"/>
+ <text x="12.31" y="304.64" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>to retrieve the bit </text> </g>
+ <g id="shape43-94" v:mID="43" v:groupContext="shape" transform="translate(349.999,-166.095)">
+ <title>Sheet.43</title>
+ <desc>position in the</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="54.2285" cy="300.436" width="108.46" height="16.8275"/>
+ <path d="M108.46 292.02 L0 292.02 L0 308.85 L108.46 308.85 L108.46 292.02" class="st3"/>
+ <text x="10.97" y="304.64" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>position in the </text> </g>
+ <g id="shape44-98" v:mID="44" v:groupContext="shape" transform="translate(353.361,-149.319)">
+ <title>Sheet.44</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="47.9619" cy="300.436" width="95.93" height="16.8275"/>
+ <path d="M95.92 292.02 L0 292.02 L0 308.85 L95.92 308.85 L95.92 292.02" class="st3"/>
+ <text x="8.21" y="304.64" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape47-102" v:mID="47" v:groupContext="shape" transform="translate(115.17,255.2) rotate(-90)">
+ <title>1-D word balloon</title>
+ <desc>Retrieve the value “0' from the specified location in the loo...</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ <v:ud v:nameU="Scale" v:val="VT0(1):26"/>
+ <v:ud v:nameU="AntiScale" v:val="VT0(1):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="29.2016" cy="218.85" width="180" height="58.4032" transform="rotate(90)"/>
+ <path d="M0 308.85 L58.4 308.85 L58.4 128.85 L0 128.85 L0 204.67 L-11.87 38.85 L-7.09 233.03 L0 233.03 L0 308.85 Z"
+ class="st13"/>
+ <text x="136.98" y="-41.8" transform="rotate(90)" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Retrieve the value “0' from <tspan
+ x="134.41" dy="1.2em" class="st15">the specified location in the </tspan><tspan x="181.1" dy="1.2em"
+ class="st15">lookup table</tspan></text> </g>
+ <g id="shape48-107" v:mID="48" v:groupContext="shape" transform="translate(169.209,-251.966)">
+ <title>Sheet.48</title>
+ <desc>F(Key, hash_index = 38123</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54.2285" cy="295.35" width="108.46" height="27"/>
+ <rect x="0" y="281.85" width="108.457" height="27" class="st16"/>
+ <text x="5.86" y="291.75" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>F(Key, hash_index = <tspan
+ x="39.02" dy="1.2em" class="st15">38123</tspan></text> </g>
+ <g id="shape49-111" v:mID="49" v:groupContext="shape" transform="translate(553.962,99) rotate(90)">
+ <title>1-D word balloon.49</title>
+ <desc>Apply the equation to retrieve the bit position in the lookup...</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ <v:ud v:nameU="Scale" v:val="VT0(1):26"/>
+ <v:ud v:nameU="AntiScale" v:val="VT0(1):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="29.2016" cy="218.85" width="180" height="58.4032" transform="rotate(-90)"/>
+ <path d="M0 308.85 L58.4 308.85 L58.4 128.85 L0 128.85 L0 204.67 L-51.13 299.85 L0 233.03 L0 308.85 Z" class="st13"/>
+ <text x="-284.62" y="16.6" transform="rotate(-90)" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Apply the equation to <tspan
+ x="-296.67" dy="1.2em" class="st15">retrieve the bit position in </tspan><tspan x="-270.22" dy="1.2em"
+ class="st15">the lookup</tspan>_table</text> </g>
+ <g id="shape50-116" v:mID="50" v:groupContext="shape" transform="translate(640.132,-104.709) rotate(44.1224)">
+ <title>Sheet.50</title>
+ <path d="M0 308.85 L54.13 308.85" class="st18"/>
+ </g>
+ <g id="shape51-122" v:mID="51" v:groupContext="shape" transform="translate(433.02,-122.267)">
+ <title>Sheet.51</title>
+ <desc>(Hash(key,seed1)+38123*hash(key,seed2))%16</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="144" cy="295.35" width="288" height="27"/>
+ <rect x="0" y="281.85" width="288" height="27" class="st2"/>
+ <text x="9.86" y="299.55" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(Hash(key,seed1)+38123*hash(key,seed2))%16</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i12.svg b/doc/guides/prog_guide/img/efd_i12.svg
new file mode 100644
index 0000000..a309d58
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i12.svg
@@ -0,0 +1,1008 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i13.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="10.2932in" height="5.27505in"
+ viewBox="0 0 741.108 379.804" xml:space="preserve" color-interpolation-filters="sRGB" class="st30">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st2 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st3 {fill:#004280;font-family:Arial;font-size:0.828804em}
+ .st4 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st5 {stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st6 {fill:#7030a0;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st7 {fill:#d0d6d9;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st8 {fill:#006fc5;stroke:#006fc5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.00749637}
+ .st9 {fill:#006fc5;stroke:#006fc5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st10 {fill:#d0d6d9;stroke:#d0d6d9;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st11 {fill:#d0d6d9;stroke:#d0d6d9;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st12 {fill:#004280;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st13 {fill:#00b050;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st14 {fill:#ff0000;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st15 {fill:#00b050;stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st16 {fill:#c00000;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st17 {fill:#000000;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st18 {fill:#7f6d00;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st19 {fill:#ff0000;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st20 {fill:#7e8d96;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st21 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st22 {fill:#000000;font-family:Arial;font-size:0.998566em}
+ .st23 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st24 {stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st25 {fill:#ffffff;font-family:Arial;font-size:0.998566em}
+ .st26 {fill:#ff6600;stroke:#ff6600;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.00749637}
+ .st27 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st28 {fill:#ff0000;stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st29 {fill:#ffffff;font-family:Arial;font-size:1.49785em}
+ .st30 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(304.703,-329.32)">
+ <title>Sheet.3</title>
+ <path d="M0 379.8 C-0 375.37 0.6 371.78 1.35 371.78 L205.05 371.78 C205.78 371.78 206.38 368.18 206.38 363.75 C206.38
+ 368.18 206.98 371.78 207.73 371.78 L411.43 371.78 C412.15 371.78 412.75 375.37 412.75 379.8" class="st1"/>
+ </g>
+ <g id="shape4-4" v:mID="4" v:groupContext="shape" transform="translate(219.943,-329.32)">
+ <title>Sheet.4</title>
+ <path d="M0 379.8 C0 375.64 0.57 372.25 1.26 372.25 L29.77 372.25 C30.48 372.25 31.03 368.88 31.03 364.71 C31.03 368.88
+ 31.6 372.25 32.29 372.25 L60.81 372.25 C61.51 372.25 62.07 375.64 62.07 379.8" class="st1"/>
+ </g>
+ <g id="shape5-7" v:mID="5" v:groupContext="shape" transform="translate(241.175,-343.9)">
+ <title>Sheet.5</title>
+ <desc>Bins</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="12.7158" cy="373.835" width="25.44" height="11.9384"/>
+ <path d="M25.43 367.87 L0 367.87 L0 379.8 L25.43 379.8 L25.43 367.87" class="st2"/>
+ <text x="3.04" y="376.82" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Bins</text> </g>
+ <g id="shape6-11" v:mID="6" v:groupContext="shape" transform="translate(496.212,-344.504)">
+ <title>Sheet.6</title>
+ <desc>Groups</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="20.3447" cy="373.835" width="40.69" height="11.9384"/>
+ <path d="M40.69 367.87 L0 367.87 L0 379.8 L40.69 379.8 L40.69 367.87" class="st2"/>
+ <text x="4.04" y="376.82" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Groups</text> </g>
+ <g id="shape7-15" v:mID="7" v:groupContext="shape" transform="translate(131.823,-260.299)">
+ <title>Sheet.7</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape8-17" v:mID="8" v:groupContext="shape" transform="translate(131.823,-260.299)">
+ <title>Sheet.8</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape9-20" v:mID="9" v:groupContext="shape" transform="translate(134.706,-310.738)">
+ <title>Sheet.9</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape10-24" v:mID="10" v:groupContext="shape" transform="translate(122.218,-329.32)">
+ <title>Sheet.10</title>
+ <path d="M0 379.8 C-0 375.64 0.57 372.25 1.26 372.25 L29.77 372.25 C30.47 372.25 31.03 368.88 31.03 364.71 C31.03 368.88
+ 31.6 372.25 32.29 372.25 L60.81 372.25 C61.51 372.25 62.07 375.64 62.07 379.8" class="st1"/>
+ </g>
+ <g id="shape11-27" v:mID="11" v:groupContext="shape" transform="translate(137.598,-343.9)">
+ <title>Sheet.11</title>
+ <desc>Chunks</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="20.9813" cy="373.835" width="41.97" height="11.9384"/>
+ <path d="M41.96 367.87 L0 367.87 L0 379.8 L41.96 379.8 L41.96 367.87" class="st2"/>
+ <text x="4.12" y="376.82" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Chunks</text> </g>
+ <g id="shape12-31" v:mID="12" v:groupContext="shape" transform="translate(131.823,-195.232)">
+ <title>Sheet.12</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape13-33" v:mID="13" v:groupContext="shape" transform="translate(131.823,-195.232)">
+ <title>Sheet.13</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape14-36" v:mID="14" v:groupContext="shape" transform="translate(134.706,-245.682)">
+ <title>Sheet.14</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape15-40" v:mID="15" v:groupContext="shape" transform="translate(131.823,-130.525)">
+ <title>Sheet.15</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st7"/>
+ </g>
+ <g id="shape16-42" v:mID="16" v:groupContext="shape" transform="translate(131.823,-130.525)">
+ <title>Sheet.16</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape17-45" v:mID="17" v:groupContext="shape" transform="translate(134.706,-180.952)">
+ <title>Sheet.17</title>
+ <desc>…</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.30974" cy="373.835" width="14.62" height="11.9384"/>
+ <path d="M14.62 367.87 L0 367.87 L0 379.8 L14.62 379.8 L14.62 367.87" class="st2"/>
+ <text x="2.34" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>…</text> </g>
+ <g id="shape18-49" v:mID="18" v:groupContext="shape" transform="translate(131.823,-65.4584)">
+ <title>Sheet.18</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape19-51" v:mID="19" v:groupContext="shape" transform="translate(131.823,-65.4584)">
+ <title>Sheet.19</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape20-54" v:mID="20" v:groupContext="shape" transform="translate(130.403,-115.896)">
+ <title>Sheet.20</title>
+ <desc>variable</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="24.7986" cy="373.835" width="49.6" height="11.9384"/>
+ <path d="M49.6 367.87 L0 367.87 L0 379.8 L49.6 379.8 L49.6 367.87" class="st2"/>
+ <text x="6" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>variable </text> </g>
+ <g id="shape21-58" v:mID="21" v:groupContext="shape" transform="translate(130.403,-103.913)">
+ <title>Sheet.21</title>
+ <desc># of</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="13.347" cy="373.835" width="26.7" height="11.9384"/>
+ <path d="M26.69 367.87 L0 367.87 L0 379.8 L26.69 379.8 L26.69 367.87" class="st2"/>
+ <text x="4.51" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/># of </text> </g>
+ <g id="shape22-62" v:mID="22" v:groupContext="shape" transform="translate(130.403,-91.93)">
+ <title>Sheet.22</title>
+ <desc>chunks</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.6122" cy="373.835" width="43.23" height="11.9384"/>
+ <path d="M43.22 367.87 L0 367.87 L0 379.8 L43.22 379.8 L43.22 367.87" class="st2"/>
+ <text x="4.2" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>chunks</text> </g>
+ <g id="shape23-66" v:mID="23" v:groupContext="shape" transform="translate(130.403,-79.9472)">
+ <title>Sheet.23</title>
+ <desc>(power</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.9251" cy="373.835" width="43.86" height="11.9384"/>
+ <path d="M43.85 367.87 L0 367.87 L0 379.8 L43.85 379.8 L43.85 367.87" class="st2"/>
+ <text x="5.62" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(power </text> </g>
+ <g id="shape24-70" v:mID="24" v:groupContext="shape" transform="translate(130.403,-67.9643)">
+ <title>Sheet.24</title>
+ <desc>of 2)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="13.6626" cy="373.835" width="27.33" height="11.9384"/>
+ <path d="M27.33 367.87 L0 367.87 L0 379.8 L27.33 379.8 L27.33 367.87" class="st2"/>
+ <text x="3.17" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>of 2)</text> </g>
+ <g id="shape25-74" v:mID="25" v:groupContext="shape" transform="translate(172.289,-260.838)">
+ <title>Sheet.25</title>
+ <path d="M1.43 379.8 L3.29 375.51 L1.85 374.9 L0 379.19 L1.43 379.8 L1.43 379.8 ZM3.9 374.08 L5.76 369.79 L4.32 369.18
+ L2.47 373.47 L3.9 374.08 L3.9 374.08 ZM6.37 368.36 L8.22 364.07 L6.79 363.45 L4.94 367.75 L6.37 368.36 L6.37
+ 368.36 ZM8.84 362.64 L10.69 358.35 L9.26 357.73 L7.41 362.02 L8.84 362.64 L8.84 362.64 ZM11.31 356.92 L13.16
+ 352.62 L11.73 352 L9.87 356.3 L11.31 356.92 L11.31 356.92 ZM13.78 351.19 L15.63 346.9 L14.2 346.28 L12.34
+ 350.57 L13.78 351.19 L13.78 351.19 ZM16.25 345.47 L18.1 341.17 L16.67 340.56 L14.81 344.85 L16.25 345.47
+ L16.25 345.47 ZM18.71 339.74 L20.57 335.45 L19.13 334.84 L17.28 339.13 L18.71 339.74 L18.71 339.74 ZM21.18
+ 334.02 L23.04 329.73 L21.6 329.12 L19.75 333.41 L21.18 334.02 L21.18 334.02 ZM23.65 328.3 L25.5 324.01 L24.07
+ 323.39 L22.22 327.68 L23.65 328.3 L23.65 328.3 ZM26.12 322.58 L27.97 318.28 L26.54 317.67 L24.69 321.96
+ L26.12 322.58 L26.12 322.58 ZM28.59 316.85 L29.44 314.87 L28.01 314.25 L27.16 316.24 L28.59 316.85 L28.59
+ 316.85 Z" class="st8"/>
+ </g>
+ <g id="shape26-76" v:mID="26" v:groupContext="shape" transform="translate(172.476,-20.463)">
+ <title>Sheet.26</title>
+ <path d="M1.55 203.84 L2.28 208.45 L0.74 208.7 L0 204.09 L1.55 203.84 L1.55 203.84 ZM2.52 209.99 L3.27 214.61 L1.73 214.86
+ L0.99 210.23 L2.52 209.99 L2.52 209.99 ZM3.51 216.15 L4.25 220.76 L2.7 221 L1.97 216.39 L3.51 216.15 L3.51
+ 216.15 ZM4.49 222.3 L5.24 226.92 L3.69 227.16 L2.96 222.54 L4.49 222.3 L4.49 222.3 ZM5.48 228.45 L6.21 233.07
+ L4.67 233.31 L3.93 228.7 L5.48 228.45 L5.48 228.45 ZM6.47 234.6 L7.2 239.22 L5.66 239.47 L4.92 234.86 L6.47
+ 234.6 L6.47 234.6 ZM7.44 240.76 L8.18 245.37 L6.65 245.63 L5.9 241 L7.44 240.76 L7.44 240.76 ZM8.43 246.91
+ L9.17 251.53 L7.62 251.77 L6.89 247.15 L8.43 246.91 L8.43 246.91 ZM9.41 253.07 L10.14 257.68 L8.61 257.92
+ L7.88 253.31 L9.41 253.07 L9.41 253.07 ZM10.4 259.21 L11.14 263.84 L9.59 264.08 L8.85 259.47 L10.4 259.21
+ L10.4 259.21 ZM11.38 265.37 L12.13 269.98 L10.58 270.24 L9.84 265.62 L11.38 265.37 L11.38 265.37 ZM12.37
+ 271.52 L13.1 276.14 L11.56 276.39 L10.82 271.76 L12.37 271.52 L12.37 271.52 ZM13.34 277.68 L14.09 282.29
+ L12.55 282.53 L11.81 277.92 L13.34 277.68 L13.34 277.68 ZM14.33 283.84 L15.07 288.45 L13.52 288.69 L12.79
+ 284.08 L14.33 283.84 L14.33 283.84 ZM15.32 289.99 L16.06 294.61 L14.51 294.85 L13.78 290.23 L15.32 289.99
+ L15.32 289.99 ZM16.3 296.13 L17.03 300.75 L15.5 301 L14.75 296.39 L16.3 296.13 L16.3 296.13 ZM17.29 302.29
+ L18.02 306.9 L16.48 307.16 L15.74 302.53 L17.29 302.29 L17.29 302.29 ZM18.26 308.45 L19 313.06 L17.47 313.3
+ L16.73 308.69 L18.26 308.45 L18.26 308.45 ZM19.25 314.6 L19.99 319.22 L18.44 319.46 L17.71 314.84 L19.25
+ 314.6 L19.25 314.6 ZM20.23 320.76 L20.96 325.37 L19.43 325.61 L18.7 321 L20.23 320.76 L20.23 320.76 ZM21.22
+ 326.9 L21.96 331.51 L20.41 331.77 L19.67 327.15 L21.22 326.9 L21.22 326.9 ZM22.2 333.06 L22.95 337.67 L21.4
+ 337.92 L20.66 333.31 L22.2 333.06 L22.2 333.06 ZM23.19 339.21 L23.92 343.83 L22.38 344.07 L21.64 339.45
+ L23.19 339.21 L23.19 339.21 ZM24.18 345.37 L24.91 349.98 L23.37 350.22 L22.63 345.61 L24.18 345.37 L24.18
+ 345.37 ZM25.15 351.52 L25.89 356.14 L24.36 356.38 L23.61 351.76 L25.15 351.52 L25.15 351.52 ZM26.14 357.67
+ L26.88 362.28 L25.33 362.53 L24.6 357.92 L26.14 357.67 L26.14 357.67 ZM27.12 363.82 L27.85 368.44 L26.32
+ 368.69 L25.59 364.08 L27.12 363.82 L27.12 363.82 ZM28.11 369.98 L28.84 374.59 L27.3 374.83 L26.56 370.22
+ L28.11 369.98 L28.11 369.98 ZM29.08 376.13 L29.64 379.55 L28.09 379.8 L27.55 376.37 L29.08 376.13 L29.08
+ 376.13 Z" class="st9"/>
+ </g>
+ <g id="shape27-78" v:mID="27" v:groupContext="shape" transform="translate(276.159,-233.368)">
+ <title>Sheet.27</title>
+ <path d="M0.45 294.85 L354.04 376.06 L353.59 378.04 L0 296.85 L0.45 294.85 L0.45 294.85 ZM353.5 373.84 L358.79 378.19
+ L352.12 379.8 L353.5 373.84 L353.5 373.84 Z" class="st10"/>
+ </g>
+ <g id="shape28-80" v:mID="28" v:groupContext="shape" transform="translate(275.859,-178.726)">
+ <title>Sheet.28</title>
+ <path d="M1.05 240.32 L231.44 376.33 L230.39 378.1 L0 242.09 L1.05 240.32 L1.05 240.32 ZM231.59 374.05 L235.31 379.8
+ L228.47 379.32 L231.59 374.05 L231.59 374.05 Z" class="st10"/>
+ </g>
+ <g id="shape29-82" v:mID="29" v:groupContext="shape" transform="translate(275.379,-87.6866)">
+ <title>Sheet.29</title>
+ <path d="M2 149.94 L50.05 374.61 L48.05 375.04 L0 150.38 L2 149.94 L2 149.94 ZM51.83 373.18 L50.12 379.8 L45.85 374.47
+ L51.83 373.18 L51.83 373.18 Z" class="st11"/>
+ </g>
+ <g id="shape30-84" v:mID="30" v:groupContext="shape" transform="translate(276.279,-177.108)">
+ <title>Sheet.30</title>
+ <path d="M0.21 353.74 L229.55 375.85 L229.34 377.89 L0 355.75 L0.21 353.74 L0.21 353.74 ZM228.71 373.72 L234.53 377.35
+ L228.14 379.8 L228.71 373.72 L228.71 373.72 Z" class="st10"/>
+ </g>
+ <g id="shape31-86" v:mID="31" v:groupContext="shape" transform="translate(275.919,-213.926)">
+ <title>Sheet.31</title>
+ <path d="M0.45 308.72 L312.65 376.06 L312.2 378.04 L0 310.72 L0.45 308.72 L0.45 308.72 ZM312.08 373.84 L317.43 378.13
+ L310.79 379.8 L312.08 373.84 L312.08 373.84 Z" class="st10"/>
+ </g>
+ <g id="shape32-88" v:mID="32" v:groupContext="shape" transform="translate(275.439,-143.377)">
+ <title>Sheet.32</title>
+ <path d="M1.4 238.41 L150.34 375.59 L148.96 377.09 L0 239.9 L1.4 238.41 L1.4 238.41 ZM150.98 373.41 L153.4 379.8 L146.83
+ 377.9 L150.98 373.41 L150.98 373.41 Z" class="st11"/>
+ </g>
+ <g id="shape33-90" v:mID="33" v:groupContext="shape" transform="translate(275.274,-108.821)">
+ <title>Sheet.33</title>
+ <path d="M1.73 236.53 L90.79 374.97 L89.08 376.07 L0 237.63 L1.73 236.53 L1.73 236.53 ZM91.96 373 L92.7 379.8 L86.82
+ 376.31 L91.96 373 L91.96 373 Z" class="st11"/>
+ </g>
+ <g id="shape34-92" v:mID="34" v:groupContext="shape" transform="translate(275.364,-124.069)">
+ <title>Sheet.34</title>
+ <path d="M1.55 251.66 L108.22 375.28 L106.67 376.61 L0 253 L1.55 251.66 L1.55 251.66 ZM109.1 373.18 L110.78 379.8 L104.46
+ 377.17 L109.1 373.18 L109.1 373.18 Z" class="st11"/>
+ </g>
+ <g id="shape35-94" v:mID="35" v:groupContext="shape" transform="translate(275.154,-87.7165)">
+ <title>Sheet.35</title>
+ <path d="M1.97 215.68 L49.85 374.64 L47.9 375.22 L0 216.27 L1.97 215.68 L1.97 215.68 ZM51.52 373.08 L50.35 379.8 L45.65
+ 374.83 L51.52 373.08 L51.52 373.08 Z" class="st11"/>
+ </g>
+ <g id="shape36-96" v:mID="36" v:groupContext="shape" transform="translate(276.009,-143.736)">
+ <title>Sheet.36</title>
+ <path d="M0.74 320.41 L147.92 376.36 L147.2 378.26 L0 322.32 L0.74 320.41 L0.74 320.41 ZM147.7 374.08 L152.34 379.11
+ L145.52 379.8 L147.7 374.08 L147.7 374.08 Z" class="st11"/>
+ </g>
+ <g id="shape37-98" v:mID="37" v:groupContext="shape" transform="translate(275.649,-108.821)">
+ <title>Sheet.37</title>
+ <path d="M1.46 285.74 L89.46 375.45 L88 376.87 L0 287.16 L1.46 285.74 L1.46 285.74 ZM90.21 373.29 L92.29 379.8 L85.82
+ 377.57 L90.21 373.29 L90.21 373.29 Z" class="st11"/>
+ </g>
+ <g id="shape38-100" v:mID="38" v:groupContext="shape" transform="translate(275.934,-108.686)">
+ <title>Sheet.38</title>
+ <path d="M0.89 335.24 L87.85 376.57 L86.97 378.41 L0 337.09 L0.89 335.24 L0.89 335.24 ZM87.81 374.29 L92.01 379.67 L85.16
+ 379.8 L87.81 374.29 L87.81 374.29 Z" class="st11"/>
+ </g>
+ <g id="shape39-102" v:mID="39" v:groupContext="shape" transform="translate(275.574,-89.454)">
+ <title>Sheet.39</title>
+ <path d="M1.61 316.29 L48.49 375.18 L46.88 376.45 L0 317.57 L1.61 316.29 L1.61 316.29 ZM49.45 373.11 L50.86 379.8 L44.65
+ 376.91 L49.45 373.11 L49.45 373.11 Z" class="st11"/>
+ </g>
+ <g id="shape40-104" v:mID="40" v:groupContext="shape" transform="translate(276.324,-141.744)">
+ <title>Sheet.40</title>
+ <path d="M0.11 368.21 L146.74 375.79 L146.62 377.83 L0 370.23 L0.11 368.21 L0.11 368.21 ZM145.82 373.71 L151.78 377.08
+ L145.51 379.8 L145.82 373.71 L145.82 373.71 Z" class="st11"/>
+ </g>
+ <g id="shape41-106" v:mID="41" v:groupContext="shape" transform="translate(230.508,-309.069)">
+ <title>Sheet.41</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st4"/>
+ </g>
+ <g id="shape42-108" v:mID="42" v:groupContext="shape" transform="translate(230.508,-309.069)">
+ <title>Sheet.42</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape43-111" v:mID="43" v:groupContext="shape" transform="translate(233.39,-309.868)">
+ <title>Sheet.43</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape44-115" v:mID="44" v:groupContext="shape" transform="translate(263.764,-309.869)">
+ <title>Sheet.44</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape45-119" v:mID="45" v:groupContext="shape" transform="translate(230.508,-292.413)">
+ <title>Sheet.45</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape46-121" v:mID="46" v:groupContext="shape" transform="translate(230.508,-292.413)">
+ <title>Sheet.46</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape47-124" v:mID="47" v:groupContext="shape" transform="translate(233.39,-293.221)">
+ <title>Sheet.47</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape48-128" v:mID="48" v:groupContext="shape" transform="translate(230.508,-275.757)">
+ <title>Sheet.48</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st4"/>
+ </g>
+ <g id="shape49-130" v:mID="49" v:groupContext="shape" transform="translate(230.508,-275.757)">
+ <title>Sheet.49</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape50-133" v:mID="50" v:groupContext="shape" transform="translate(233.39,-276.574)">
+ <title>Sheet.50</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape51-137" v:mID="51" v:groupContext="shape" transform="translate(252.478,-276.574)">
+ <title>Sheet.51</title>
+ <desc>3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>3</text> </g>
+ <g id="shape52-141" v:mID="52" v:groupContext="shape" transform="translate(258.001,-276.574)">
+ <title>Sheet.52</title>
+ <desc>+1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="8.11122" cy="373.835" width="16.23" height="11.9384"/>
+ <path d="M16.22 367.87 L0 367.87 L0 379.8 L16.22 379.8 L16.22 367.87" class="st2"/>
+ <text x="2.44" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>+1</text> </g>
+ <g id="shape53-145" v:mID="53" v:groupContext="shape" transform="translate(230.508,-259.7)">
+ <title>Sheet.53</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape54-147" v:mID="54" v:groupContext="shape" transform="translate(230.508,-259.7)">
+ <title>Sheet.54</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape55-150" v:mID="55" v:groupContext="shape" transform="translate(233.39,-260.497)">
+ <title>Sheet.55</title>
+ <desc>3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>3</text> </g>
+ <g id="shape56-154" v:mID="56" v:groupContext="shape" transform="translate(230.508,-243.164)">
+ <title>Sheet.56</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st4"/>
+ </g>
+ <g id="shape57-156" v:mID="57" v:groupContext="shape" transform="translate(230.508,-243.164)">
+ <title>Sheet.57</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape58-159" v:mID="58" v:groupContext="shape" transform="translate(233.39,-244.053)">
+ <title>Sheet.58</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape59-163" v:mID="59" v:groupContext="shape" transform="translate(263.764,-244.053)">
+ <title>Sheet.59</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape60-167" v:mID="60" v:groupContext="shape" transform="translate(230.508,-227.107)">
+ <title>Sheet.60</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape61-169" v:mID="61" v:groupContext="shape" transform="translate(230.508,-227.107)">
+ <title>Sheet.61</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape62-172" v:mID="62" v:groupContext="shape" transform="translate(233.39,-227.976)">
+ <title>Sheet.62</title>
+ <desc>5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>5</text> </g>
+ <g id="shape63-176" v:mID="63" v:groupContext="shape" transform="translate(230.508,-210.211)">
+ <title>Sheet.63</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape64-178" v:mID="64" v:groupContext="shape" transform="translate(230.508,-210.211)">
+ <title>Sheet.64</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape65-181" v:mID="65" v:groupContext="shape" transform="translate(233.39,-211.085)">
+ <title>Sheet.65</title>
+ <desc>6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>6</text> </g>
+ <g id="shape66-185" v:mID="66" v:groupContext="shape" transform="translate(230.508,-193.794)">
+ <title>Sheet.66</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st4"/>
+ </g>
+ <g id="shape67-187" v:mID="67" v:groupContext="shape" transform="translate(230.508,-193.794)">
+ <title>Sheet.67</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape68-190" v:mID="68" v:groupContext="shape" transform="translate(233.39,-194.681)">
+ <title>Sheet.68</title>
+ <desc>7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>7</text> </g>
+ <g id="shape69-194" v:mID="69" v:groupContext="shape" transform="translate(263.764,-194.681)">
+ <title>Sheet.69</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape70-198" v:mID="70" v:groupContext="shape" transform="translate(230.508,-177.258)">
+ <title>Sheet.70</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape71-200" v:mID="71" v:groupContext="shape" transform="translate(230.508,-177.258)">
+ <title>Sheet.71</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape72-203" v:mID="72" v:groupContext="shape" transform="translate(233.39,-178.117)">
+ <title>Sheet.72</title>
+ <desc>8</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>8</text> </g>
+ <g id="shape73-207" v:mID="73" v:groupContext="shape" transform="translate(230.508,-160.602)">
+ <title>Sheet.73</title>
+ <path d="M0 363.15 L0 379.8 L41.18 379.8 L41.18 363.15 L0 363.15 L0 363.15 Z" class="st7"/>
+ </g>
+ <g id="shape74-209" v:mID="74" v:groupContext="shape" transform="translate(230.508,-160.602)">
+ <title>Sheet.74</title>
+ <path d="M0 363.15 L41.18 363.15 L41.18 379.8 L0 379.8 L0 363.15" class="st5"/>
+ </g>
+ <g id="shape75-212" v:mID="75" v:groupContext="shape" transform="translate(233.39,-161.505)">
+ <title>Sheet.75</title>
+ <desc>9</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>9</text> </g>
+ <g id="shape76-216" v:mID="76" v:groupContext="shape" transform="translate(230.508,-143.946)">
+ <title>Sheet.76</title>
+ <path d="M0 363.15 L0 379.8 L41.18 379.8 L41.18 363.15 L0 363.15 L0 363.15 Z" class="st4"/>
+ </g>
+ <g id="shape77-218" v:mID="77" v:groupContext="shape" transform="translate(230.508,-143.946)">
+ <title>Sheet.77</title>
+ <path d="M0 363.15 L41.18 363.15 L41.18 379.8 L0 379.8 L0 363.15" class="st5"/>
+ </g>
+ <g id="shape78-221" v:mID="78" v:groupContext="shape" transform="translate(233.39,-144.841)">
+ <title>Sheet.78</title>
+ <desc>10</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>10</text> </g>
+ <g id="shape79-225" v:mID="79" v:groupContext="shape" transform="translate(263.764,-144.841)">
+ <title>Sheet.79</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape80-229" v:mID="80" v:groupContext="shape" transform="translate(230.508,-127.529)">
+ <title>Sheet.80</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape81-231" v:mID="81" v:groupContext="shape" transform="translate(230.508,-127.529)">
+ <title>Sheet.81</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape82-234" v:mID="82" v:groupContext="shape" transform="translate(233.39,-128.329)">
+ <title>Sheet.82</title>
+ <desc>11</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>11</text> </g>
+ <g id="shape83-238" v:mID="83" v:groupContext="shape" transform="translate(230.508,-110.754)">
+ <title>Sheet.83</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape84-240" v:mID="84" v:groupContext="shape" transform="translate(230.508,-110.754)">
+ <title>Sheet.84</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape85-243" v:mID="85" v:groupContext="shape" transform="translate(233.39,-111.64)">
+ <title>Sheet.85</title>
+ <desc>12</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>12</text> </g>
+ <g id="shape86-247" v:mID="86" v:groupContext="shape" transform="translate(230.508,-94.9362)">
+ <title>Sheet.86</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape87-249" v:mID="87" v:groupContext="shape" transform="translate(230.508,-94.9362)">
+ <title>Sheet.87</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape88-252" v:mID="88" v:groupContext="shape" transform="translate(233.39,-95.7375)">
+ <title>Sheet.88</title>
+ <desc>…</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.30974" cy="373.835" width="14.62" height="11.9384"/>
+ <path d="M14.62 367.87 L0 367.87 L0 379.8 L14.62 379.8 L14.62 367.87" class="st2"/>
+ <text x="2.34" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>…</text> </g>
+ <g id="shape89-256" v:mID="89" v:groupContext="shape" transform="translate(230.508,-78.999)">
+ <title>Sheet.89</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape90-258" v:mID="90" v:groupContext="shape" transform="translate(230.508,-78.999)">
+ <title>Sheet.90</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape91-261" v:mID="91" v:groupContext="shape" transform="translate(233.39,-79.8525)">
+ <title>Sheet.91</title>
+ <desc>255</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1326" cy="373.835" width="22.27" height="11.9384"/>
+ <path d="M22.27 367.87 L0 367.87 L0 379.8 L22.27 379.8 L22.27 367.87" class="st2"/>
+ <text x="2.84" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>255</text> </g>
+ <g id="shape92-265" v:mID="92" v:groupContext="shape" transform="translate(276.219,-250.503)">
+ <title>Sheet.92</title>
+ <path d="M0.33 311.98 L396.81 375.94 L396.48 377.95 L0 313.99 L0.33 311.98 L0.33 311.98 ZM396.12 373.75 L401.68 377.74
+ L395.16 379.8 L396.12 373.75 L396.12 373.75 Z" class="st15"/>
+ </g>
+ <g id="shape93-267" v:mID="93" v:groupContext="shape" transform="translate(275.859,-178.426)">
+ <title>Sheet.93</title>
+ <path d="M0.57 305.72 L230.93 376.21 L230.33 378.16 L0 307.67 L0.57 305.72 L0.57 305.72 ZM230.57 373.96 L235.52 378.67
+ L228.77 379.8 L230.57 373.96 L230.57 373.96 Z" class="st15"/>
+ </g>
+ <g id="shape94-269" v:mID="94" v:groupContext="shape" transform="translate(276.279,-151.285)">
+ <title>Sheet.94</title>
+ <path d="M0.21 379.8 L230.12 353.17 L229.88 351.14 L0 377.8 L0.21 379.8 L0.21 379.8 ZM229.34 355.3 L235.07 351.55 L228.65
+ 349.25 L229.34 355.3 L229.34 355.3 Z" class="st15"/>
+ </g>
+ <g id="shape95-271" v:mID="95" v:groupContext="shape" transform="translate(276.009,-232.679)">
+ <title>Sheet.95</title>
+ <path d="M0.27 327.47 L354.22 375.91 L353.95 377.92 L0 329.48 L0.27 327.47 L0.27 327.47 ZM353.5 373.75 L359.15 377.62
+ L352.66 379.8 L353.5 373.75 L353.5 373.75 Z" class="st10"/>
+ </g>
+ <g id="shape96-273" v:mID="96" v:groupContext="shape" transform="translate(276.279,-201.134)">
+ <title>Sheet.96</title>
+ <path d="M0.21 379.8 L353.86 348.14 L353.68 346.1 L0 377.77 L0.21 379.8 L0.21 379.8 ZM353.05 350.24 L358.88 346.64 L352.48
+ 344.16 L353.05 350.24 L353.05 350.24 Z" class="st15"/>
+ </g>
+ <g id="shape97-275" v:mID="97" v:groupContext="shape" transform="translate(346.482,-41.2531)">
+ <title>Sheet.97</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st7"/>
+ </g>
+ <g id="shape98-277" v:mID="98" v:groupContext="shape" transform="translate(346.482,-41.2531)">
+ <title>Sheet.98</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape99-280" v:mID="99" v:groupContext="shape" transform="translate(349.371,-91.6514)">
+ <title>Sheet.99</title>
+ <desc>…</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.30974" cy="373.835" width="14.62" height="11.9384"/>
+ <path d="M14.62 367.87 L0 367.87 L0 379.8 L14.62 379.8 L14.62 367.87" class="st2"/>
+ <text x="2.34" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>…</text> </g>
+ <g id="shape100-284" v:mID="100" v:groupContext="shape" transform="translate(470.019,-94.337)">
+ <title>Sheet.100</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st7"/>
+ </g>
+ <g id="shape101-286" v:mID="101" v:groupContext="shape" transform="translate(470.019,-94.337)">
+ <title>Sheet.101</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape102-289" v:mID="102" v:groupContext="shape" transform="translate(472.925,-144.778)">
+ <title>Sheet.102</title>
+ <desc>5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>5</text> </g>
+ <g id="shape103-293" v:mID="103" v:groupContext="shape" transform="translate(511.558,-113.749)">
+ <title>Sheet.103</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape104-295" v:mID="104" v:groupContext="shape" transform="translate(511.558,-113.749)">
+ <title>Sheet.104</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape105-298" v:mID="105" v:groupContext="shape" transform="translate(514.441,-164.138)">
+ <title>Sheet.105</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape106-302" v:mID="106" v:groupContext="shape" transform="translate(542.148,-164.138)">
+ <title>Sheet.106</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape107-306" v:mID="107" v:groupContext="shape" transform="translate(542.148,-152.155)">
+ <title>Sheet.107</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape108-310" v:mID="108" v:groupContext="shape" transform="translate(536.626,-140.172)">
+ <title>Sheet.108</title>
+ <desc>10</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>10</text> </g>
+ <g id="shape109-314" v:mID="109" v:groupContext="shape" transform="translate(514.201,-114.441)">
+ <title>Sheet.109</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape110-318" v:mID="110" v:groupContext="shape" transform="translate(519.723,-114.441)">
+ <title>Sheet.110</title>
+ <desc>+4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="8.11122" cy="373.835" width="16.23" height="11.9384"/>
+ <path d="M16.22 367.87 L0 367.87 L0 379.8 L16.22 379.8 L16.22 367.87" class="st2"/>
+ <text x="2.44" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>+4</text> </g>
+ <g id="shape111-322" v:mID="111" v:groupContext="shape" transform="translate(552.257,-130.525)">
+ <title>Sheet.111</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st7"/>
+ </g>
+ <g id="shape112-324" v:mID="112" v:groupContext="shape" transform="translate(552.257,-130.525)">
+ <title>Sheet.112</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape113-327" v:mID="113" v:groupContext="shape" transform="translate(555.203,-180.952)">
+ <title>Sheet.113</title>
+ <desc>3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>3</text> </g>
+ <g id="shape114-331" v:mID="114" v:groupContext="shape" transform="translate(634.615,-169.11)">
+ <title>Sheet.114</title>
+ <path d="M0 313.9 L0 379.8 L41.18 379.8 L41.18 313.9 L0 313.9 L0 313.9 Z" class="st4"/>
+ </g>
+ <g id="shape115-333" v:mID="115" v:groupContext="shape" transform="translate(634.615,-169.11)">
+ <title>Sheet.115</title>
+ <path d="M0 313.9 L41.18 313.9 L41.18 379.8 L0 379.8 L0 313.9" class="st5"/>
+ </g>
+ <g id="shape116-336" v:mID="116" v:groupContext="shape" transform="translate(637.526,-219.595)">
+ <title>Sheet.116</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape117-340" v:mID="117" v:groupContext="shape" transform="translate(665.234,-219.595)">
+ <title>Sheet.117</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape118-344" v:mID="118" v:groupContext="shape" transform="translate(665.2,-225.489)">
+ <title>Sheet.118</title>
+ <path d="M0 379.32 L0 379.8 L5.52 379.8 L5.52 379.32 L0 379.32 L0 379.32 Z" class="st19"/>
+ </g>
+ <g id="shape119-346" v:mID="119" v:groupContext="shape" transform="translate(665.234,-207.612)">
+ <title>Sheet.119</title>
+ <desc>7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>7</text> </g>
+ <g id="shape120-350" v:mID="120" v:groupContext="shape" transform="translate(637.286,-169.898)">
+ <title>Sheet.120</title>
+ <desc>5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>5</text> </g>
+ <g id="shape121-354" v:mID="121" v:groupContext="shape" transform="translate(642.809,-169.898)">
+ <title>Sheet.121</title>
+ <desc>-</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="3.49545" cy="373.835" width="7" height="11.9384"/>
+ <path d="M6.99 367.87 L0 367.87 L0 379.8 L6.99 379.8 L6.99 367.87" class="st2"/>
+ <text x="1.84" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>-</text> </g>
+ <g id="shape122-358" v:mID="122" v:groupContext="shape" transform="translate(646.17,-169.898)">
+ <title>Sheet.122</title>
+ <desc>3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>3</text> </g>
+ <g id="shape123-362" v:mID="123" v:groupContext="shape" transform="translate(676.275,-186.725)">
+ <title>Sheet.123</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape124-364" v:mID="124" v:groupContext="shape" transform="translate(676.275,-186.725)">
+ <title>Sheet.124</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape125-367" v:mID="125" v:groupContext="shape" transform="translate(679.141,-237.17)">
+ <title>Sheet.125</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape126-371" v:mID="126" v:groupContext="shape" transform="translate(706.849,-237.17)">
+ <title>Sheet.126</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape127-375" v:mID="127" v:groupContext="shape" transform="translate(678.901,-187.474)">
+ <title>Sheet.127</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape128-379" v:mID="128" v:groupContext="shape" transform="translate(304.943,-21.841)">
+ <title>Sheet.128</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape129-381" v:mID="129" v:groupContext="shape" transform="translate(304.943,-21.841)">
+ <title>Sheet.129</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape130-384" v:mID="130" v:groupContext="shape" transform="translate(307.855,-72.2917)">
+ <title>Sheet.130</title>
+ <desc>64</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>64</text> </g>
+ <g id="shape131-388" v:mID="131" v:groupContext="shape" transform="translate(330.041,-72.2917)">
+ <title>Sheet.131</title>
+ <desc>96</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>96</text> </g>
+ <g id="shape132-392" v:mID="132" v:groupContext="shape" transform="translate(307.616,-22.5952)">
+ <title>Sheet.132</title>
+ <desc>7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>7</text> </g>
+ <g id="shape133-396" v:mID="133" v:groupContext="shape" transform="translate(428.72,-77.4413)">
+ <title>Sheet.133</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape134-398" v:mID="134" v:groupContext="shape" transform="translate(428.72,-77.4413)">
+ <title>Sheet.134</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape135-401" v:mID="135" v:groupContext="shape" transform="translate(431.648,-127.825)">
+ <title>Sheet.135</title>
+ <desc>6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>6</text> </g>
+ <g id="shape136-405" v:mID="136" v:groupContext="shape" transform="translate(453.834,-127.825)">
+ <title>Sheet.136</title>
+ <desc>98</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>98</text> </g>
+ <g id="shape137-409" v:mID="137" v:groupContext="shape" transform="translate(431.409,-78.1289)">
+ <title>Sheet.137</title>
+ <desc>5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>5</text> </g>
+ <g id="shape138-413" v:mID="138" v:groupContext="shape" transform="translate(593.796,-149.818)">
+ <title>Sheet.138</title>
+ <path d="M0 313.9 L0 379.8 L41.18 379.8 L41.18 313.9 L0 313.9 L0 313.9 Z" class="st4"/>
+ </g>
+ <g id="shape139-415" v:mID="139" v:groupContext="shape" transform="translate(593.796,-149.818)">
+ <title>Sheet.139</title>
+ <path d="M0 313.9 L41.18 313.9 L41.18 379.8 L0 379.8 L0 313.9" class="st5"/>
+ </g>
+ <g id="shape140-418" v:mID="140" v:groupContext="shape" transform="translate(596.718,-200.312)">
+ <title>Sheet.140</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape141-422" v:mID="141" v:groupContext="shape" transform="translate(618.904,-200.312)">
+ <title>Sheet.141</title>
+ <desc>99</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>99</text> </g>
+ <g id="shape142-426" v:mID="142" v:groupContext="shape" transform="translate(596.478,-150.615)">
+ <title>Sheet.142</title>
+ <desc>9</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>9</text> </g>
+ <g id="shape143-430" v:mID="143" v:groupContext="shape" transform="translate(387.181,-58.0291)">
+ <title>Sheet.143</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape144-432" v:mID="144" v:groupContext="shape" transform="translate(387.181,-58.0291)">
+ <title>Sheet.144</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape145-435" v:mID="145" v:groupContext="shape" transform="translate(390.133,-108.466)">
+ <title>Sheet.145</title>
+ <desc>7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>7</text> </g>
+ <g id="shape146-439" v:mID="146" v:groupContext="shape" transform="translate(412.318,-108.466)">
+ <title>Sheet.146</title>
+ <desc>97</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>97</text> </g>
+ <g id="shape147-443" v:mID="147" v:groupContext="shape" transform="translate(389.893,-58.7692)">
+ <title>Sheet.147</title>
+ <desc>6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>6</text> </g>
+ <g id="shape148-447" v:mID="148" v:groupContext="shape" transform="translate(31.8163,-277.674)">
+ <title>Sheet.148</title>
+ <path d="M0 362.83 C0 360.95 1.52 359.43 3.41 359.43 L68.51 359.43 C70.4 359.43 71.91 360.95 71.91 362.83 L71.91 376.41
+ C71.91 378.28 70.4 379.8 68.51 379.8 L3.41 379.8 C1.52 379.8 0 378.28 0 376.41 L0 362.83 Z" class="st4"/>
+ </g>
+ <g id="shape149-449" v:mID="149" v:groupContext="shape" transform="translate(31.8163,-277.674)">
+ <title>Sheet.149</title>
+ <path d="M0 362.83 C0 360.95 1.52 359.43 3.41 359.43 L68.51 359.43 C70.4 359.43 71.91 360.95 71.91 362.83 L71.91 376.41
+ C71.91 378.28 70.4 379.8 68.51 379.8 L3.41 379.8 C1.52 379.8 0 378.28 0 376.41 L0 362.83 Z" class="st21"/>
+ </g>
+ <g id="shape150-451" v:mID="150" v:groupContext="shape" transform="translate(36,-278.851)">
+ <title>Sheet.150</title>
+ <desc>Insert key</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="35.613" cy="372.612" width="71.23" height="14.3829"/>
+ <path d="M71.23 365.42 L0 365.42 L0 379.8 L71.23 379.8 L71.23 365.42" class="st2"/>
+ <text x="9.64" y="376.21" class="st22" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Insert key </text> </g>
+ <g id="shape151-455" v:mID="151" v:groupContext="shape" transform="translate(47.7236,-257.004)">
+ <title>Sheet.151</title>
+ <path d="M0 369.44 L9.81 369.44 L9.81 359.07 L29.44 359.07 L29.44 369.44 L39.26 369.44 L19.63 379.8 L0 369.44 Z"
+ class="st23"/>
+ </g>
+ <g id="shape152-457" v:mID="152" v:groupContext="shape" transform="translate(31.8163,-236.094)">
+ <title>Sheet.152</title>
+ <path d="M0 362.73 C0 360.85 1.54 359.31 3.42 359.31 L68.49 359.31 C70.38 359.31 71.91 360.85 71.91 362.73 L71.91 376.39
+ C71.91 378.28 70.38 379.8 68.49 379.8 L3.42 379.8 C1.54 379.8 0 378.28 0 376.39 L0 362.73 Z" class="st4"/>
+ </g>
+ <g id="shape153-459" v:mID="153" v:groupContext="shape" transform="translate(31.8163,-236.094)">
+ <title>Sheet.153</title>
+ <path d="M0 362.73 C0 360.85 1.54 359.31 3.42 359.31 L68.49 359.31 C70.38 359.31 71.91 360.85 71.91 362.73 L71.91 376.39
+ C71.91 378.28 70.38 379.8 68.49 379.8 L3.42 379.8 C1.54 379.8 0 378.28 0 376.39 L0 362.73 Z" class="st21"/>
+ </g>
+ <g id="shape154-461" v:mID="154" v:groupContext="shape" transform="translate(54.6845,-237.332)">
+ <title>Sheet.154</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.8573" cy="372.612" width="33.72" height="14.3829"/>
+ <path d="M33.71 365.42 L0 365.42 L0 379.8 L33.71 379.8 L33.71 365.42" class="st2"/>
+ <text x="3.86" y="376.21" class="st22" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape155-465" v:mID="155" v:groupContext="shape" transform="translate(23.0522,-195.232)">
+ <title>Sheet.155</title>
+ <path d="M0 363.53 C0 361.73 1.46 360.27 3.26 360.27 L87.15 360.27 C88.95 360.27 90.4 361.73 90.4 363.53 L90.4 376.55
+ C90.4 378.35 88.95 379.8 87.15 379.8 L3.26 379.8 C1.46 379.8 0 378.35 0 376.55 L0 363.53 Z" class="st4"/>
+ </g>
+ <g id="shape156-467" v:mID="156" v:groupContext="shape" transform="translate(23.0522,-195.232)">
+ <title>Sheet.156</title>
+ <path d="M0 363.53 C0 361.73 1.46 360.27 3.26 360.27 L87.15 360.27 C88.95 360.27 90.4 361.73 90.4 363.53 L90.4 376.55
+ C90.4 378.35 88.95 379.8 87.15 379.8 L3.26 379.8 C1.46 379.8 0 378.35 0 376.55 L0 363.53 Z" class="st21"/>
+ </g>
+ <g id="shape157-469" v:mID="157" v:groupContext="shape" transform="translate(27,-196.017)">
+ <title>Sheet.157</title>
+ <desc>0x0102ABCD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.6644" cy="372.612" width="87.33" height="14.3829"/>
+ <path d="M87.33 365.42 L0 365.42 L0 379.8 L87.33 379.8 L87.33 365.42" class="st2"/>
+ <text x="7.36" y="376.21" class="st22" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102ABCD</text> </g>
+ <g id="shape158-473" v:mID="158" v:groupContext="shape" transform="translate(47.7236,-214.824)">
+ <title>Sheet.158</title>
+ <path d="M0 369.5 L9.81 369.5 L9.81 359.19 L29.44 359.19 L29.44 369.5 L39.26 369.5 L19.63 379.8 L0 369.5 Z"
+ class="st23"/>
+ </g>
+ <g id="shape159-475" v:mID="159" v:groupContext="shape" transform="translate(49.8539,-181.212)">
+ <title>Sheet.159</title>
+ <path d="M11.89 368.42 C11.89 371.57 11.47 374.11 10.94 374.11 L6.9 374.11 C6.37 374.11 5.94 376.67 5.94 379.8 C5.94
+ 376.67 5.52 374.11 5 374.11 L0.95 374.11 C0.43 374.11 0 371.57 0 368.42" class="st24"/>
+ </g>
+ <g id="shape160-478" v:mID="160" v:groupContext="shape" transform="translate(64.2606,-180.973)">
+ <title>Sheet.160</title>
+ <path d="M9.54 368.54 C9.54 371.66 9.21 374.17 8.79 374.17 L5.53 374.17 C5.11 374.17 4.77 376.7 4.77 379.8 C4.77 376.7
+ 4.43 374.17 4.02 374.17 L0.76 374.17 C0.34 374.17 0 371.66 0 368.54" class="st24"/>
+ </g>
+ <g id="shape161-481" v:mID="161" v:groupContext="shape" transform="translate(18.19,-60.9649)">
+ <title>Sheet.161</title>
+ <path d="M0 354.74 C0 351.97 2.25 349.73 5.03 349.73 L10.77 349.73 L30.27 267.14 L26.92 349.73 L59.58 349.73 C62.35 349.73
+ 64.59 351.97 64.59 354.74 L64.59 354.74 L64.59 362.26 L64.59 374.8 C64.59 377.57 62.35 379.8 59.58 379.8
+ L26.92 379.8 L10.77 379.8 L10.77 379.8 L5.03 379.8 C2.25 379.8 0 377.57 0 374.8 L0 362.26 L0 354.74 L0 354.74
+ Z" class="st23"/>
+ </g>
+ <g id="shape162-483" v:mID="162" v:groupContext="shape" transform="translate(28.141,-66.9569)">
+ <title>Sheet.162</title>
+ <desc>chunk id</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="27.5794" cy="372.612" width="55.16" height="14.3829"/>
+ <path d="M55.16 365.42 L0 365.42 L0 379.8 L55.16 379.8 L55.16 365.42" class="st2"/>
+ <text x="5.26" y="376.21" class="st25" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>chunk id</text> </g>
+ <g id="shape163-487" v:mID="163" v:groupContext="shape" transform="translate(50.8451,-112.132)">
+ <title>Sheet.163</title>
+ <path d="M0 354.64 C0 351.87 2.27 349.61 5.04 349.61 L10.74 349.61 L16.27 313.66 L26.86 349.61 L59.43 349.61 C62.22 349.61
+ 64.47 351.87 64.47 354.64 L64.47 354.64 L64.47 362.19 L64.47 374.77 C64.47 377.56 62.22 379.8 59.43 379.8
+ L26.86 379.8 L10.74 379.8 L10.74 379.8 L5.04 379.8 C2.27 379.8 0 377.56 0 374.77 L0 362.19 L0 354.64 L0
+ 354.64 Z" class="st23"/>
+ </g>
+ <g id="shape164-489" v:mID="164" v:groupContext="shape" transform="translate(68.8168,-118.181)">
+ <title>Sheet.164</title>
+ <desc>bin id</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="18.3881" cy="372.612" width="36.78" height="14.3829"/>
+ <path d="M36.78 365.42 L0 365.42 L0 379.8 L36.78 379.8 L36.78 365.42" class="st2"/>
+ <text x="4.06" y="376.21" class="st25" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bin id</text> </g>
+ <g id="shape165-493" v:mID="165" v:groupContext="shape" transform="translate(113.454,-225.085)">
+ <title>Sheet.165</title>
+ <path d="M0.01 375.68 L13.23 375.73 L13.22 377.77 L0 377.72 L0.01 375.68 L0.01 375.68 ZM12.22 373.69 L18.33 376.76 L12.2
+ 379.8 L12.22 373.69 L12.22 373.69 Z" class="st26"/>
+ </g>
+ <g id="shape166-495" v:mID="166" v:groupContext="shape" transform="translate(200.975,-280.969)">
+ <title>Sheet.166</title>
+ <path d="M0 375.73 L20.11 375.73 L20.11 377.77 L0 377.77 L0 375.73 L0 375.73 ZM19.09 373.69 L25.21 376.75 L19.09 379.8
+ L19.09 373.69 L19.09 373.69 Z" class="st26"/>
+ </g>
+ <g id="shape167-497" v:mID="167" v:groupContext="shape" transform="translate(275.739,-179.745)">
+ <title>Sheet.167</title>
+ <path d="M0.81 274.59 L231.38 376.48 L230.54 378.37 L0 276.48 L0.81 274.59 L0.81 274.59 ZM231.26 374.2 L235.64 379.47
+ L228.8 379.8 L231.26 374.2 L231.26 374.2 Z" class="st27"/>
+ </g>
+ <g id="shape168-499" v:mID="168" v:groupContext="shape" transform="translate(521.823,-96.8834)">
+ <title>Sheet.168</title>
+ <path d="M127.17 309.02 L127.17 378.79 C127.17 379.35 126.72 379.8 126.15 379.8 L3.06 379.8 C2.52 379.8 2.04 379.35 2.04
+ 378.79 L2.04 369.59 L4.08 369.59 L4.08 378.79 L3.06 377.77 L126.15 377.77 L125.13 378.79 L125.13 309.02
+ L127.17 309.02 ZM0 370.61 L3.06 364.5 L6.12 370.61 L0 370.61 Z" class="st28"/>
+ </g>
+ <g id="shape169-501" v:mID="169" v:groupContext="shape" transform="translate(478.603,-39.7553)">
+ <title>Sheet.169</title>
+ <path d="M0 347.57 C0 344.01 2.91 341.1 6.48 341.1 L237.86 341.1 C241.43 341.1 244.31 344.01 244.31 347.57 L244.31 373.36
+ C244.31 376.93 241.43 379.8 237.86 379.8 L6.48 379.8 C2.91 379.8 0 376.93 0 373.36 L0 347.57 Z"
+ class="st23"/>
+ </g>
+ <g id="shape170-503" v:mID="170" v:groupContext="shape" transform="translate(487.717,-45.5378)">
+ <title>Sheet.170</title>
+ <desc>Move bin from group 1 to 4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="126.387" cy="369.018" width="252.78" height="21.5726"/>
+ <path d="M252.77 358.23 L0 358.23 L0 379.8 L252.77 379.8 L252.77 358.23" class="st2"/>
+ <text x="18.98" y="374.41" class="st29" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Move bin from group 1 to 4</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i2.svg b/doc/guides/prog_guide/img/efd_i2.svg
new file mode 100644
index 0000000..a5f43f9
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i2.svg
@@ -0,0 +1,280 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i2.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="2.85156in" height="2.98777in"
+ viewBox="0 0 205.313 215.12" xml:space="preserve" color-interpolation-filters="sRGB" class="st18">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#deebf6;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st5 {fill:#ff0000;stroke:#c7c8c8;stroke-width:0.25}
+ .st6 {fill:none;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st7 {fill:none;stroke:#0070c0;stroke-width:1.5}
+ .st8 {marker-end:url(#mrkr5-91);stroke:#0070c0;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st9 {fill:#0070c0;fill-opacity:1;stroke:#0070c0;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st10 {fill:none;stroke:none;stroke-width:0.25}
+ .st11 {fill:#ff0000;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st12 {font-size:1em}
+ .st13 {marker-end:url(#mrkr5-101);stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st14 {fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st15 {fill:#5b9bd5;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st16 {marker-end:url(#mrkr5-110);stroke:#41719c;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st17 {fill:#41719c;fill-opacity:1;stroke:#41719c;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st18 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-91" class="st9" v:arrowType="5" v:arrowSize="2" v:setback="4.45" refX="-4.45" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ <marker id="mrkr5-101" class="st14" v:arrowType="5" v:arrowSize="2" v:setback="6.16" refX="-6.16" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ <marker id="mrkr5-110" class="st17" v:arrowType="5" v:arrowSize="2" v:setback="6.16" refX="-6.16" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(24.4044,-42.7174)">
+ <title>Circle</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow2-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 138.62 A76.5 76.5 0 0 1 153 138.62 A76.5 76.5 0 1 1 0 138.62 Z" class="st2"/>
+ </g>
+ <path d="M0 138.62 A76.5 76.5 0 0 1 153 138.62 A76.5 76.5 0 1 1 0 138.62 Z" class="st3"/>
+ </g>
+ <g id="shape3-6" v:mID="3" v:groupContext="shape" transform="translate(24.4044,-144.53)">
+ <title>Circle.3</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow3-7" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape4-11" v:mID="4" v:groupContext="shape" transform="translate(21.0294,-102.342)">
+ <title>Circle.4</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow4-12" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape5-16" v:mID="5" v:groupContext="shape" transform="translate(69.4044,-183.342)">
+ <title>Circle.5</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow5-17" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape6-21" v:mID="6" v:groupContext="shape" transform="translate(117.217,-183.342)">
+ <title>Circle.6</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow6-22" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st5"/>
+ </g>
+ <g id="shape7-26" v:mID="7" v:groupContext="shape" transform="translate(171.217,-104.03)">
+ <title>Circle.7</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow7-27" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st5"/>
+ </g>
+ <g id="shape8-31" v:mID="8" v:groupContext="shape" transform="translate(109.904,-38.2174)">
+ <title>Circle.8</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow8-32" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st5"/>
+ </g>
+ <g id="shape9-36" v:mID="9" v:groupContext="shape" transform="translate(21.0294,-124.842)">
+ <title>Circle.9</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow9-37" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st5"/>
+ </g>
+ <g id="shape10-41" v:mID="10" v:groupContext="shape" transform="translate(147.029,-168.717)">
+ <title>Circle.10</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow10-42" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape11-46" v:mID="11" v:groupContext="shape" transform="translate(138.029,-48.3424)">
+ <title>Circle.11</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow11-47" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape12-51" v:mID="12" v:groupContext="shape" transform="translate(160.529,-74.2174)">
+ <title>Circle.12</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow12-52" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape13-56" v:mID="13" v:groupContext="shape" transform="translate(40.7169,-57.3424)">
+ <title>Circle.13</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow13-57" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape14-61" v:mID="14" v:groupContext="shape" transform="translate(42.4044,-168.717)">
+ <title>Circle.14</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow14-62" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape15-66" v:mID="15" v:groupContext="shape" transform="translate(66.0294,-42.7174)">
+ <title>Circle.15</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow15-67" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape16-71" v:mID="16" v:groupContext="shape" transform="translate(25.5294,-79.8424)">
+ <title>Circle.16</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow16-72" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape17-76" v:mID="17" v:groupContext="shape" transform="translate(165.029,-143.405)">
+ <title>Circle.17</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow17-77" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape18-81" v:mID="18" v:groupContext="shape" transform="translate(276.618,4.50201) rotate(45)">
+ <title>Ellipse</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow18-82" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,1.63935,1.1506)" class="st1">
+ <path d="M0 187.01 A14.7383 28.1086 0 1 1 29.48 187.01 A14.7383 28.1086 0 1 1 0 187.01 Z" class="st6"/>
+ </g>
+ <path d="M0 187.01 A14.7383 28.1086 0 1 1 29.48 187.01 A14.7383 28.1086 0 1 1 0 187.01 Z" class="st7"/>
+ </g>
+ <g id="shape19-86" v:mID="19" v:groupContext="shape" transform="translate(251.273,355.436) rotate(156.038)">
+ <title>Sheet.19</title>
+ <path d="M-0 215.12 A73.4538 31.2572 85.43 0 1 40.92 208.96 L41.1 209.27" class="st8"/>
+ </g>
+ <g id="shape20-92" v:mID="20" v:groupContext="shape" transform="translate(62.705,-78.7174)">
+ <title>Sheet.20</title>
+ <desc>Target Hashed Value</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="42.6994" cy="203.87" width="85.4" height="22.5"/>
+ <rect x="0" y="192.62" width="85.3987" height="22.5" class="st10"/>
+ <text x="6.73" y="200.27" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target Hashed <tspan
+ x="28.48" dy="1.2em" class="st12">Value</tspan></text> </g>
+ <g id="shape21-96" v:mID="21" v:groupContext="shape" transform="translate(314.101,88.728) rotate(75.9638)">
+ <title>Sheet.21</title>
+ <path d="M0 215.12 L16.92 215.12" class="st13"/>
+ </g>
+ <g id="shape23-102" v:mID="23" v:groupContext="shape" transform="translate(60.4044,-138.342)">
+ <title>Sheet.23</title>
+ <desc>Keys</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="24.75" cy="203.87" width="49.5" height="22.5"/>
+ <rect x="0" y="192.62" width="49.5" height="22.5" class="st10"/>
+ <text x="13.21" y="207.47" class="st15" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Keys</text> </g>
+ <g id="shape24-105" v:mID="24" v:groupContext="shape" transform="translate(-125.293,114.034) rotate(-104.574)">
+ <title>Sheet.24</title>
+ <path d="M0 215.12 L22.9 215.12" class="st16"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i3.svg b/doc/guides/prog_guide/img/efd_i3.svg
new file mode 100644
index 0000000..ae22903
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i3.svg
@@ -0,0 +1,634 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i3.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="6.56036in" height="5.44284in"
+ viewBox="0 0 472.346 391.884" xml:space="preserve" color-interpolation-filters="sRGB" class="st22">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false"/>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st5 {marker-end:url(#mrkr5-24);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st6 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st7 {fill:none;stroke:#2e75b5;stroke-width:1}
+ .st8 {fill:#5b9bd5;font-family:Calibri;font-size:1.00001em}
+ .st9 {font-size:1em}
+ .st10 {fill:none;stroke:none;stroke-width:1}
+ .st11 {fill:#feffff;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st12 {fill:#5b9bd5;fill-opacity:0.25;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.25}
+ .st13 {fill:#4f87bb;stroke:#40709c;stroke-width:0.75}
+ .st14 {fill:#feffff;font-family:Calibri;font-size:0.75em}
+ .st15 {fill:none;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st16 {fill:none;stroke:#2e75b5;stroke-width:2.25}
+ .st17 {stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st18 {fill:#305497;stroke:#2e75b5;stroke-width:1}
+ .st19 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:none}
+ .st20 {fill:#92d050;fill-opacity:0.3;stroke:none;stroke-width:0.25}
+ .st21 {fill:#feffff;font-family:Calibri;font-size:1.16666em}
+ .st22 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-24" class="st6" v:arrowType="5" v:arrowSize="2" v:setback="6.16" refX="-6.16" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <v:layer v:name="Connector" v:index="0"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(111.25,-354.482)">
+ <title>Rectangle</title>
+ <desc>Packet Header</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="42.75" cy="382.884" width="85.5" height="18"/>
+ <g id="shadow2-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="85.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="85.5" height="18" class="st3"/>
+ <text x="13.24" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Packet Header</text> </g>
+ <g id="shape3-7" v:mID="3" v:groupContext="shape" transform="translate(192.25,-354.482)">
+ <title>Rectangle.3</title>
+ <desc>Payload</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="382.884" width="108" height="18"/>
+ <g id="shadow3-8" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="108" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="108" height="18" class="st3"/>
+ <text x="37.95" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Payload</text> </g>
+ <g id="shape4-13" v:mID="4" v:groupContext="shape" transform="translate(136,-311.232)">
+ <title>Rectangle.4</title>
+ <desc>Flow Key</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="382.884" width="54" height="18"/>
+ <g id="shadow4-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="54" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="54" height="18" class="st3"/>
+ <text x="8.87" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Flow Key</text> </g>
+ <g id="shape5-19" v:mID="5" v:groupContext="shape" transform="translate(465.501,-160.057) rotate(59.7436)">
+ <title>Sheet.5</title>
+ <path d="M0 391.88 L25.1 391.88" class="st5"/>
+ </g>
+ <g id="shape8-25" v:mID="8" v:groupContext="shape" transform="translate(219.25,-320.169)">
+ <title>Sheet.8</title>
+ <desc>Fields of the packet are used to form a flow Key</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="377.822" width="135" height="28.125"/>
+ <rect x="0" y="363.759" width="135" height="28.125" class="st7"/>
+ <text x="10.7" y="374.22" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Fields of the packet are <tspan
+ x="9.67" dy="1.2em" class="st9">used to form a flow Key</tspan></text> </g>
+ <g id="group13-29" transform="translate(120.25,-266.897)" v:mID="13" v:groupContext="group">
+ <title>Sheet.13</title>
+ <g id="shape11-30" v:mID="11" v:groupContext="shape" transform="translate(85.5,751.143) rotate(180)">
+ <title>Trapezoid</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow11-31" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,-0.345598,-1.97279)" class="st1">
+ <path d="M0 391.88 L85.5 391.88 L60.19 359.26 L25.31 359.26 L0 391.88 Z" class="st2"/>
+ </g>
+ <path d="M0 391.88 L85.5 391.88 L60.19 359.26 L25.31 359.26 L0 391.88 Z" class="st3"/>
+ </g>
+ <g id="shape12-35" v:mID="12" v:groupContext="shape" transform="translate(13.5,-6.525)">
+ <title>Sheet.12</title>
+ <desc>H(..)</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="381.689" width="54" height="20.3906"/>
+ <rect x="0" y="371.494" width="54" height="20.3906" class="st10"/>
+ <text x="16.27" y="385.29" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H(..)</text> </g>
+ </g>
+ <g id="shape14-38" v:mID="14" v:groupContext="shape" transform="translate(-229.872,96.3648) rotate(-90.0429)">
+ <title>Simple Arrow</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ArrowType" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <g id="shadow14-39" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,-1.97305,0.344122)" class="st1">
+ <path d="M0 391.88 L10.18 387.38 L10.18 389.63 L16.71 389.63 L16.71 391.88 L16.71 394.13 L10.18 394.13 L10.18 396.38
+ L0 391.88 Z" class="st12"/>
+ </g>
+ <path d="M0 391.88 L10.18 387.38 L10.18 389.63 L16.71 389.63 L16.71 391.88 L16.71 394.13 L10.18 394.13 L10.18 396.38
+ L0 391.88 Z" class="st13"/>
+ </g>
+ <g id="shape15-43" v:mID="15" v:groupContext="shape" transform="translate(212.5,-271.46)">
+ <title>Sheet.15</title>
+ <desc>Hash function is used to create a flow table index</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="377.822" width="135" height="28.125"/>
+ <rect x="0" y="363.759" width="135" height="28.125" class="st7"/>
+ <text x="9.05" y="374.22" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Hash function is used to <tspan
+ x="7.39" dy="1.2em" class="st9">create a flow table index</tspan></text> </g>
+ <g id="shape58-47" v:mID="58" v:groupContext="shape" transform="translate(199,-221.397)">
+ <title>Rectangle.58</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow58-48" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape59-53" v:mID="59" v:groupContext="shape" transform="translate(232.75,-221.397)">
+ <title>Rectangle.59</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow59-54" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape60-59" v:mID="60" v:groupContext="shape" transform="translate(280,-221.397)">
+ <title>Rectangle.60</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow60-60" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape61-65" v:mID="61" v:groupContext="shape" transform="translate(313.75,-221.397)">
+ <title>Rectangle.61</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow61-66" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape62-71" v:mID="62" v:groupContext="shape" transform="translate(361,-221.397)">
+ <title>Rectangle.62</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow62-72" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape63-76" v:mID="63" v:groupContext="shape" transform="translate(394.75,-221.397)">
+ <title>Rectangle.63</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow63-77" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape64-81" v:mID="64" v:groupContext="shape" transform="translate(199,-198.897)">
+ <title>Rectangle.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow64-82" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape65-86" v:mID="65" v:groupContext="shape" transform="translate(232.75,-198.897)">
+ <title>Rectangle.65</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow65-87" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape66-91" v:mID="66" v:groupContext="shape" transform="translate(280,-198.897)">
+ <title>Rectangle.66</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow66-92" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape67-96" v:mID="67" v:groupContext="shape" transform="translate(313.75,-198.897)">
+ <title>Rectangle.67</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow67-97" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape68-101" v:mID="68" v:groupContext="shape" transform="translate(361,-198.897)">
+ <title>Rectangle.68</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow68-102" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape69-106" v:mID="69" v:groupContext="shape" transform="translate(394.75,-198.897)">
+ <title>Rectangle.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow69-107" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape70-111" v:mID="70" v:groupContext="shape" transform="translate(199,-162.897)">
+ <title>Rectangle.70</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow70-112" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape71-117" v:mID="71" v:groupContext="shape" transform="translate(232.75,-162.897)">
+ <title>Rectangle.71</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow71-118" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape72-123" v:mID="72" v:groupContext="shape" transform="translate(280,-162.897)">
+ <title>Rectangle.72</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow72-124" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape73-129" v:mID="73" v:groupContext="shape" transform="translate(313.75,-162.897)">
+ <title>Rectangle.73</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow73-130" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape74-135" v:mID="74" v:groupContext="shape" transform="translate(361,-162.897)">
+ <title>Rectangle.74</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow74-136" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape75-141" v:mID="75" v:groupContext="shape" transform="translate(394.75,-162.897)">
+ <title>Rectangle.75</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow75-142" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape76-147" v:mID="76" v:groupContext="shape" transform="translate(199,-126.397)">
+ <title>Rectangle.76</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow76-148" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape77-152" v:mID="77" v:groupContext="shape" transform="translate(232.75,-126.397)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow77-153" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape78-157" v:mID="78" v:groupContext="shape" transform="translate(280,-126.397)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow78-158" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape79-162" v:mID="79" v:groupContext="shape" transform="translate(313.75,-126.397)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow79-163" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape80-167" v:mID="80" v:groupContext="shape" transform="translate(361,-126.397)">
+ <title>Rectangle.80</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow80-168" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="385.58" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape81-173" v:mID="81" v:groupContext="shape" transform="translate(394.75,-126.397)">
+ <title>Rectangle.81</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow81-174" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="385.58" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape82-179" v:mID="82" v:groupContext="shape" transform="translate(196.75,-117.397)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow82-180" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="256.384" width="245.25" height="135.5" class="st15"/>
+ </g>
+ <rect x="0" y="256.384" width="245.25" height="135.5" class="st16"/>
+ </g>
+ <g id="shape83-184" v:mID="83" v:groupContext="shape" transform="translate(554.884,123.862) rotate(90)">
+ <title>Sheet.83</title>
+ <path d="M0 391.88 L99 391.88" class="st17"/>
+ </g>
+ <g id="shape84-187" v:mID="84" v:groupContext="shape" transform="translate(208,-248.397)">
+ <title>Sheet.84</title>
+ <desc>Load Balancing Flow Table</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="91.75" cy="386.259" width="183.5" height="11.25"/>
+ <rect x="0" y="380.634" width="183.5" height="11.25" class="st18"/>
+ <text x="26.14" y="389.86" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Load Balancing Flow Table</text> </g>
+ <g id="shape85-190" v:mID="85" v:groupContext="shape" transform="translate(190,-157.835)">
+ <title>Rectangle.85</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow85-191" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="363.759" width="261" height="28.125" class="st19"/>
+ </g>
+ <rect x="0" y="363.759" width="261" height="28.125" class="st20"/>
+ </g>
+ <g id="shape86-195" v:mID="86" v:groupContext="shape" transform="translate(163,-169.022)">
+ <title>Sheet.86</title>
+ <path d="M0 391.88 L18.76 391.88" class="st5"/>
+ </g>
+ <g id="shape87-200" v:mID="87" v:groupContext="shape" transform="translate(19,-198.107)">
+ <title>Sheet.87</title>
+ <desc>Hash value used to index Flow table</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="377.822" width="135" height="28.125"/>
+ <rect x="0" y="363.759" width="135" height="28.125" class="st7"/>
+ <text x="6.79" y="374.22" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Hash value used to index <tspan
+ x="42.16" dy="1.2em" class="st9">Flow table</tspan></text> </g>
+ <g id="shape88-204" v:mID="88" v:groupContext="shape" transform="translate(551.381,21.2928) rotate(87.9001)">
+ <title>Sheet.88</title>
+ <path d="M0 391.88 L20.86 391.88" class="st5"/>
+ </g>
+ <g id="shape89-209" v:mID="89" v:groupContext="shape" transform="translate(494.785,297.309) rotate(131.987)">
+ <title>Sheet.89</title>
+ <path d="M0 391.88 L30.84 391.88" class="st5"/>
+ </g>
+ <g id="shape90-214" v:mID="90" v:groupContext="shape" transform="translate(228.25,-92.5847)">
+ <title>Rectangle.90</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow90-215" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape91-220" v:mID="91" v:groupContext="shape" transform="translate(340.75,-92.5847)">
+ <title>Rectangle.91</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow91-221" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="group96-226" transform="translate(253,-51.4597)" v:mID="96" v:groupContext="group">
+ <title>Sheet.96</title>
+ <g id="shape97-227" v:mID="97" v:groupContext="shape" transform="translate(85.5,751.143) rotate(180)">
+ <title>Trapezoid</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow97-228" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,-0.345598,-1.97279)" class="st1">
+ <path d="M0 391.88 L85.5 391.88 L60.19 359.26 L25.31 359.26 L0 391.88 Z" class="st2"/>
+ </g>
+ <path d="M0 391.88 L85.5 391.88 L60.19 359.26 L25.31 359.26 L0 391.88 Z" class="st3"/>
+ </g>
+ <g id="shape98-232" v:mID="98" v:groupContext="shape" transform="translate(13.5,-6.525)">
+ <title>Sheet.98</title>
+ <desc>Match</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="381.689" width="54" height="20.3906"/>
+ <rect x="0" y="371.494" width="54" height="20.3906" class="st10"/>
+ <text x="10.98" y="385.29" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Match</text> </g>
+ </g>
+ <g id="shape99-235" v:mID="99" v:groupContext="shape" transform="translate(532.137,0.00916548) rotate(54.6508)">
+ <title>Sheet.99</title>
+ <path d="M0 391.88 L93.23 391.88" class="st5"/>
+ </g>
+ <g id="shape100-240" v:mID="100" v:groupContext="shape" transform="translate(683.134,224.487) rotate(90)">
+ <title>Sheet.100</title>
+ <path d="M0 391.88 L77.15 391.88" class="st5"/>
+ </g>
+ <g id="shape101-245" v:mID="101" v:groupContext="shape" transform="translate(692.213,476.024) rotate(129.078)">
+ <title>Sheet.101</title>
+ <path d="M0 391.88 L95.37 391.88" class="st5"/>
+ </g>
+ <g id="shape102-250" v:mID="102" v:groupContext="shape" transform="translate(293.5,-97.0847)">
+ <title>Rectangle.102</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow102-251" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape103-256" v:mID="103" v:groupContext="shape" transform="translate(169.75,-55.9597)">
+ <title>Rectangle.103</title>
+ <desc>Flow Key</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="382.884" width="54" height="18"/>
+ <g id="shadow103-257" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="54" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="54" height="18" class="st3"/>
+ <text x="8.87" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Flow Key</text> </g>
+ <g id="shape104-262" v:mID="104" v:groupContext="shape" transform="translate(226,-64.9597)">
+ <title>Sheet.104</title>
+ <path d="M0 391.88 L34.34 391.88" class="st5"/>
+ </g>
+ <g id="shape105-267" v:mID="105" v:groupContext="shape" transform="translate(54,-82.4597)">
+ <title>Sheet.105</title>
+ <desc>Retrieved keys are matched with input key</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="377.822" width="135" height="28.125"/>
+ <rect x="0" y="363.759" width="135" height="28.125" class="st7"/>
+ <text x="22.51" y="374.22" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Retrieved keys are <tspan
+ x="9.83" dy="1.2em" class="st9">matched with input key</tspan></text> </g>
+ <g id="shape106-271" v:mID="106" v:groupContext="shape" transform="translate(271,-23.9597)">
+ <title>Rectangle.106</title>
+ <desc>Action</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="382.884" width="54" height="18"/>
+ <g id="shadow106-272" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="54" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="54" height="18" class="st3"/>
+ <text x="8.67" y="387.08" class="st21" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action</text> </g>
+ <g id="shape111-277" v:mID="111" v:groupContext="shape" transform="translate(-94.8716,350.902) rotate(-90.0429)">
+ <title>Simple Arrow.111</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ArrowType" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <g id="shadow111-278" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,-1.97305,0.344122)" class="st1">
+ <path d="M0 391.88 L10.18 387.38 L10.18 389.63 L16.71 389.63 L16.71 391.88 L16.71 394.13 L10.18 394.13 L10.18 396.38
+ L0 391.88 Z" class="st12"/>
+ </g>
+ <path d="M0 391.88 L10.18 387.38 L10.18 389.63 L16.71 389.63 L16.71 391.88 L16.71 394.13 L10.18 394.13 L10.18 396.38
+ L0 391.88 Z" class="st13"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i4.svg b/doc/guides/prog_guide/img/efd_i4.svg
new file mode 100644
index 0000000..5be5ccd
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i4.svg
@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i4.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="2.78993in" height="1.78151in"
+ viewBox="0 0 200.875 128.269" xml:space="preserve" color-interpolation-filters="sRGB" class="st19">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:none;stroke:none;stroke-width:0.25}
+ .st2 {fill:#5b9bd5;font-family:Calibri;font-size:0.75em}
+ .st3 {font-size:1em}
+ .st4 {fill:#5b9bd5;font-family:Calibri;font-size:0.75em;font-weight:bold}
+ .st5 {fill:#deebf6;stroke:none;stroke-width:0.25}
+ .st6 {stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st7 {stroke:#5b9bd5;stroke-dasharray:0.75,1.5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st8 {fill:#ff0000;font-size:1em}
+ .st9 {baseline-shift:-28.8834%;font-size:0.577667em}
+ .st10 {fill:#ff0000;font-family:Calibri;font-size:0.75em}
+ .st11 {fill:#5b9bd5;font-size:1em}
+ .st12 {visibility:visible}
+ .st13 {fill:#5b9bd5;fill-opacity:0.25;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.25}
+ .st14 {fill:url(#grad0-73);stroke:#40709c;stroke-width:0.75}
+ .st15 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st16 {fill:#00fefe;font-size:1em}
+ .st17 {fill:#00b050}
+ .st18 {stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st19 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Patterns_And_Gradients">
+ <linearGradient id="grad0-73" x1="0" y1="0" x2="1" y2="0" gradientTransform="rotate(250 0.5 0.5)">
+ <stop offset="0" stop-color="#4f87bb" stop-opacity="1"/>
+ <stop offset="0.48" stop-color="#4f87bb" stop-opacity="1"/>
+ <stop offset="0.82" stop-color="#5b9bd5" stop-opacity="1"/>
+ </linearGradient>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(18.25,-59.3478)">
+ <title>Sheet.2</title>
+ <desc>Key 1 Key 2 ... Key 28</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="18" cy="121.519" width="36" height="13.5"/>
+ <rect x="0" y="114.769" width="36" height="13.5" class="st1"/>
+ <text x="8.09" y="108.02" class="st2" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1<v:newlineChar/><tspan
+ x="8.09" dy="1.2em" class="st3">Key </tspan>2<v:newlineChar/><tspan x="14.59" dy="1.2em" class="st3">...<v:newlineChar/></tspan><tspan
+ x="5.81" dy="1.2em" class="st3">Key </tspan>28</text> </g>
+ <g id="shape9-7" v:mID="9" v:groupContext="shape" transform="translate(52,-91.9728)">
+ <title>Sheet.9</title>
+ <desc>Target Value</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="17.4375" cy="122.644" width="34.88" height="11.25"/>
+ <rect x="0" y="117.019" width="34.875" height="11.25" class="st1"/>
+ <text x="5.43" y="119.94" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target <tspan x="6.77"
+ dy="1.2em" class="st3">Value</tspan></text> </g>
+ <g id="shape11-11" v:mID="11" v:groupContext="shape" transform="translate(52,-42.4728)">
+ <title>Sheet.11</title>
+ <desc>0 1 0</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="17.4375" cy="105.769" width="34.88" height="45"/>
+ <rect x="0" y="83.2689" width="34.875" height="45" class="st5"/>
+ <text x="15.16" y="92.27" class="st2" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0<v:newlineChar/><tspan
+ x="15.16" dy="1.2em" class="st3">1<v:newlineChar/><v:newlineChar/></tspan><tspan x="15.16" dy="2.4em"
+ class="st3">0</tspan></text> </g>
+ <g id="shape8-16" v:mID="8" v:groupContext="shape" transform="translate(180.269,21.6711) rotate(90)">
+ <title>Sheet.8</title>
+ <path d="M0 128.27 L69.75 128.27" class="st6"/>
+ </g>
+ <g id="shape10-19" v:mID="10" v:groupContext="shape" transform="translate(215.144,21.6711) rotate(90)">
+ <title>Sheet.10</title>
+ <path d="M0 128.27 L69.75 128.27" class="st6"/>
+ </g>
+ <g id="shape4-22" v:mID="4" v:groupContext="shape" transform="translate(22.75,-77.3478)">
+ <title>Sheet.4</title>
+ <path d="M0 128.27 L157.5 128.27" class="st7"/>
+ </g>
+ <g id="shape5-25" v:mID="5" v:groupContext="shape" transform="translate(23.875,-66.0978)">
+ <title>Sheet.5</title>
+ <path d="M0 128.27 L158.62 128.27" class="st7"/>
+ </g>
+ <g id="shape6-28" v:mID="6" v:groupContext="shape" transform="translate(22.75,-54.8478)">
+ <title>Sheet.6</title>
+ <path d="M0 128.27 L159.75 128.27" class="st7"/>
+ </g>
+ <g id="shape7-31" v:mID="7" v:groupContext="shape" transform="translate(22.75,-87.4728)">
+ <title>Sheet.7</title>
+ <path d="M0 128.27 L155.25 128.27" class="st6"/>
+ </g>
+ <g id="shape12-34" v:mID="12" v:groupContext="shape" transform="translate(91.9375,-42.4728)">
+ <title>Sheet.12</title>
+ <desc>0 0 0</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="8.4375" cy="105.769" width="16.88" height="45"/>
+ <rect x="0" y="83.2689" width="16.875" height="45" class="st1"/>
+ <text x="6.16" y="92.27" class="st2" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0<v:newlineChar/><tspan
+ x="6.16" dy="1.2em" class="st8">0<v:newlineChar/><v:newlineChar/></tspan><tspan x="6.16" dy="2.4em"
+ class="st3">0</tspan></text> </g>
+ <g id="shape26-39" v:mID="26" v:groupContext="shape" transform="translate(86.875,-88.5978)">
+ <title>Sheet.26</title>
+ <desc>H1(x)</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="14.0625" cy="122.644" width="28.13" height="11.25"/>
+ <rect x="0" y="117.019" width="28.125" height="11.25" class="st1"/>
+ <text x="5.03" y="125.34" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st9" v:baseFontSize="8">1</tspan><tspan dy="0.164em" class="st3">(</tspan>x)</text> </g>
+ <g id="shape27-44" v:mID="27" v:groupContext="shape" transform="translate(115,-42.4728)">
+ <title>Sheet.27</title>
+ <desc>1 1 0</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="8.4375" cy="105.769" width="16.88" height="45"/>
+ <rect x="0" y="83.2689" width="16.875" height="45" class="st1"/>
+ <text x="6.16" y="92.27" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1<v:newlineChar/><tspan
+ x="6.16" dy="1.2em" class="st11">1<v:newlineChar/><v:newlineChar/></tspan><tspan x="6.16" dy="2.4em"
+ class="st11">0</tspan></text> </g>
+ <g id="shape28-49" v:mID="28" v:groupContext="shape" transform="translate(109.938,-88.5978)">
+ <title>Sheet.28</title>
+ <desc>H2(x)</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="14.0625" cy="122.644" width="28.13" height="11.25"/>
+ <rect x="0" y="117.019" width="28.125" height="11.25" class="st1"/>
+ <text x="5.03" y="125.34" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st9" v:baseFontSize="8">2</tspan><tspan dy="0.164em" class="st3">(</tspan>x)</text> </g>
+ <g id="shape29-54" v:mID="29" v:groupContext="shape" transform="translate(155.5,-42.4728)">
+ <title>Sheet.29</title>
+ <desc>0 1 0</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="8.4375" cy="105.769" width="16.88" height="45"/>
+ <rect x="0" y="83.2689" width="16.875" height="45" class="st1"/>
+ <text x="6.16" y="92.27" class="st2" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0<v:newlineChar/><tspan
+ x="6.16" dy="1.2em" class="st3">1<v:newlineChar/><v:newlineChar/></tspan><tspan x="6.16" dy="2.4em"
+ class="st3">0</tspan></text> </g>
+ <g id="shape30-59" v:mID="30" v:groupContext="shape" transform="translate(150.438,-88.5978)">
+ <title>Sheet.30</title>
+ <desc>Hm(x)</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="14.0625" cy="122.644" width="28.13" height="11.25"/>
+ <rect x="0" y="117.019" width="28.125" height="11.25" class="st1"/>
+ <text x="4.24" y="125.34" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st9" v:baseFontSize="8">m</tspan><tspan dy="0.164em" class="st3">(</tspan>x)</text> </g>
+ <g id="shape31-64" v:mID="31" v:groupContext="shape" transform="translate(130.188,-89.7228)">
+ <title>Sheet.31</title>
+ <desc>…..</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="14.0625" cy="122.644" width="28.13" height="11.25"/>
+ <rect x="0" y="117.019" width="28.125" height="11.25" class="st1"/>
+ <text x="8.46" y="125.34" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>…..</text> </g>
+ <g id="shape32-67" v:mID="32" v:groupContext="shape" transform="translate(34,-23.3478)">
+ <title>Sheet.32</title>
+ <desc>Store m for this group of keys</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="66.375" cy="122.644" width="132.75" height="11.25"/>
+ <g id="shadow32-68" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st12">
+ <rect x="0" y="117.019" width="132.75" height="11.25" class="st13"/>
+ </g>
+ <rect x="0" y="117.019" width="132.75" height="11.25" class="st14"/>
+ <text x="6.32" y="125.64" class="st15" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Store <tspan
+ class="st16">m</tspan> for this group of keys</text> </g>
+ <g id="shape36-76" v:mID="36" v:groupContext="shape" transform="translate(159.381,-100.964)">
+ <title>Sheet.36</title>
+ <path d="M3.45 125.81 L6.87 119.34 L7.99 120.16 L3.87 128.27 L0 124.35 L0.86 123.13 L3.45 125.81 Z" class="st17"/>
+ </g>
+ <g id="group44-79" transform="translate(97.5625,-100.086)" v:mID="44" v:groupContext="group">
+ <title>Sheet.44</title>
+ <g id="shape42-80" v:mID="42" v:groupContext="shape" transform="translate(85.4972,28.6255) rotate(41.8011)">
+ <title>Sheet.42</title>
+ <path d="M0 128.27 L6.04 128.27" class="st18"/>
+ </g>
+ <g id="shape43-83" v:mID="43" v:groupContext="shape" transform="translate(-87.9035,34.8564) rotate(-43.2597)">
+ <title>Sheet.43</title>
+ <path d="M0 128.27 L5.87 128.27" class="st18"/>
+ </g>
+ </g>
+ <g id="group45-86" transform="translate(120.625,-100.086)" v:mID="45" v:groupContext="group">
+ <title>Sheet.45</title>
+ <g id="shape46-87" v:mID="46" v:groupContext="shape" transform="translate(85.4972,28.6255) rotate(41.8011)">
+ <title>Sheet.46</title>
+ <path d="M0 128.27 L6.04 128.27" class="st18"/>
+ </g>
+ <g id="shape47-90" v:mID="47" v:groupContext="shape" transform="translate(-87.9035,34.8564) rotate(-43.2597)">
+ <title>Sheet.47</title>
+ <path d="M0 128.27 L5.87 128.27" class="st18"/>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i5.svg b/doc/guides/prog_guide/img/efd_i5.svg
new file mode 100644
index 0000000..b6540ba
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i5.svg
@@ -0,0 +1,183 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i5.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="8.34375in" height="2.86443in"
+ viewBox="0 0 600.75 206.239" xml:space="preserve" color-interpolation-filters="sRGB" class="st14">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:1.5em}
+ .st5 {fill:#feffff;font-family:Calibri;font-size:1.16666em}
+ .st6 {marker-end:url(#mrkr5-36);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st7 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st8 {stroke:#5b9bd5;stroke-dasharray:1.5,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st9 {fill:none;stroke:none;stroke-width:0.25}
+ .st10 {fill:#5b9bd5;font-family:Calibri;font-size:1.5em;font-weight:bold}
+ .st11 {baseline-shift:-32.4951%;font-size:0.649902em}
+ .st12 {fill:#deebf6;stroke:#0070c0;stroke-width:1}
+ .st13 {fill:#5b9bd5;font-family:Calibri;font-size:1.5em}
+ .st14 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-36" class="st7" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(93.0294,-158.5)">
+ <title>Rectangle</title>
+ <desc>All Keys</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="216" cy="192.739" width="432" height="27"/>
+ <g id="shadow2-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="179.239" width="432" height="27" class="st2"/>
+ </g>
+ <rect x="0" y="179.239" width="432" height="27" class="st3"/>
+ <text x="187.88" y="198.14" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>All Keys</text> </g>
+ <g id="shape3-7" v:mID="3" v:groupContext="shape" transform="translate(21.0294,-77.5)">
+ <title>Rectangle.3</title>
+ <desc>Group 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="188.239" width="108" height="36"/>
+ <g id="shadow3-8" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="170.239" width="108" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="170.239" width="108" height="36" class="st3"/>
+ <text x="30.97" y="192.44" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group 1</text> </g>
+ <g id="shape4-13" v:mID="4" v:groupContext="shape" transform="translate(156.029,-77.5)">
+ <title>Rectangle.4</title>
+ <desc>Group 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="188.239" width="108" height="36"/>
+ <g id="shadow4-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="170.239" width="108" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="170.239" width="108" height="36" class="st3"/>
+ <text x="30.97" y="192.44" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group 2</text> </g>
+ <g id="shape5-19" v:mID="5" v:groupContext="shape" transform="translate(291.029,-77.5)">
+ <title>Rectangle.5</title>
+ <desc>Group 3</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="188.239" width="108" height="36"/>
+ <g id="shadow5-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="170.239" width="108" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="170.239" width="108" height="36" class="st3"/>
+ <text x="30.97" y="192.44" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group 3</text> </g>
+ <g id="shape6-25" v:mID="6" v:groupContext="shape" transform="translate(471.029,-77.5)">
+ <title>Rectangle.6</title>
+ <desc>Group X</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="188.239" width="108" height="36"/>
+ <g id="shadow6-26" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="170.239" width="108" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="170.239" width="108" height="36" class="st3"/>
+ <text x="30.88" y="192.44" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group X</text> </g>
+ <g id="shape7-31" v:mID="7" v:groupContext="shape" transform="translate(359.05,247.819) rotate(165.964)">
+ <title>Sheet.7</title>
+ <path d="M0 206.24 L178.5 206.24" class="st6"/>
+ </g>
+ <g id="shape8-37" v:mID="8" v:groupContext="shape" transform="translate(428.903,215.562) rotate(144.462)">
+ <title>Sheet.8</title>
+ <path d="M0 206.24 L70.39 206.24" class="st6"/>
+ </g>
+ <g id="shape9-42" v:mID="9" v:groupContext="shape" transform="translate(470.075,-81.0976) rotate(51.3402)">
+ <title>Sheet.9</title>
+ <path d="M0 206.24 L50.59 206.24" class="st6"/>
+ </g>
+ <g id="shape10-47" v:mID="10" v:groupContext="shape" transform="translate(364.228,-150.976) rotate(15.5241)">
+ <title>Sheet.10</title>
+ <path d="M0 206.24 L161.1 206.24" class="st6"/>
+ </g>
+ <g id="shape11-52" v:mID="11" v:groupContext="shape" transform="translate(408.029,-95.5)">
+ <title>Sheet.11</title>
+ <path d="M0 206.24 L45 206.24" class="st8"/>
+ </g>
+ <g id="shape12-55" v:mID="12" v:groupContext="shape" transform="translate(48.0294,-50.5)">
+ <title>Sheet.12</title>
+ <desc>H7</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="192.739" width="45" height="27"/>
+ <rect x="0" y="179.239" width="45" height="27" class="st9"/>
+ <text x="13.86" y="198.14" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan
+ dy="-0.284em" class="st11" v:baseFontSize="18">7</tspan></text> </g>
+ <g id="shape13-59" v:mID="13" v:groupContext="shape" transform="translate(192.029,-50.5)">
+ <title>Sheet.13</title>
+ <desc>H267</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="192.739" width="45" height="27"/>
+ <rect x="0" y="179.239" width="45" height="27" class="st9"/>
+ <text x="7.93" y="198.14" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st11" v:baseFontSize="18">267</tspan></text> </g>
+ <g id="shape14-63" v:mID="14" v:groupContext="shape" transform="translate(318.029,-50.5)">
+ <title>Sheet.14</title>
+ <desc>H46</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="192.739" width="45" height="27"/>
+ <rect x="0" y="179.239" width="45" height="27" class="st9"/>
+ <text x="10.89" y="198.14" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan
+ dy="-0.284em" class="st11" v:baseFontSize="18">46</tspan></text> </g>
+ <g id="shape15-67" v:mID="15" v:groupContext="shape" transform="translate(502.529,-50.5)">
+ <title>Sheet.15</title>
+ <desc>H132</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="192.739" width="45" height="27"/>
+ <rect x="0" y="179.239" width="45" height="27" class="st9"/>
+ <text x="7.93" y="198.14" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st11" v:baseFontSize="18">132</tspan></text> </g>
+ <g id="shape16-71" v:mID="16" v:groupContext="shape" transform="translate(111.029,-19)">
+ <title>Sheet.16</title>
+ <desc>Store hash function index for each group of keys</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="189" cy="192.739" width="378" height="27"/>
+ <rect x="0" y="179.239" width="378" height="27" class="st12"/>
+ <text x="12.27" y="198.14" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Store hash function index for each group of keys</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i6.svg b/doc/guides/prog_guide/img/efd_i6.svg
new file mode 100644
index 0000000..9aee30b
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i6.svg
@@ -0,0 +1,1254 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i6.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="8.2496in" height="5.89673in"
+ viewBox="0 0 593.971 424.565" xml:space="preserve" color-interpolation-filters="sRGB" class="st27">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st5 {fill:#feffff;font-family:Calibri;font-size:0.75em}
+ .st6 {fill:none;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st7 {fill:none;stroke:#2e75b5;stroke-width:2.25}
+ .st8 {fill:#305497;stroke:#2e75b5;stroke-width:1}
+ .st9 {fill:#feffff;font-family:Calibri;font-size:0.833336em;font-weight:bold}
+ .st10 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2)}
+ .st11 {fill:#5b9bd5}
+ .st12 {stroke:#c7c8c8;stroke-width:0.25}
+ .st13 {fill:#acccea;stroke:#c7c8c8;stroke-width:0.25}
+ .st14 {fill:#feffff;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st15 {fill:#ed7d31;stroke:#c7c8c8;stroke-width:0.25}
+ .st16 {fill:#deebf6;stroke:#c7c8c8;stroke-width:0.25}
+ .st17 {marker-end:url(#mrkr5-212);stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st18 {fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st19 {fill:none;stroke:#2e75b5;stroke-width:1}
+ .st20 {fill:#5b9bd5;font-family:Calibri;font-size:1.00001em}
+ .st21 {fill:none;stroke:none;stroke-width:0.25}
+ .st22 {font-size:1em}
+ .st23 {fill:#ffffff}
+ .st24 {stroke:#5b9bd5;stroke-dasharray:1.5,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st25 {marker-end:url(#mrkr5-444);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st26 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st27 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-212" class="st18" v:arrowType="5" v:arrowSize="2" v:setback="5.8" refX="-5.8" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ <marker id="mrkr5-444" class="st26" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(319.501,-335.688)">
+ <title>Rectangle.58</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow3-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape4-7" v:mID="4" v:groupContext="shape" transform="translate(353.251,-335.688)">
+ <title>Rectangle.59</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow4-8" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape5-13" v:mID="5" v:groupContext="shape" transform="translate(400.501,-335.688)">
+ <title>Rectangle.60</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow5-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape6-19" v:mID="6" v:groupContext="shape" transform="translate(434.251,-335.688)">
+ <title>Rectangle.61</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow6-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape7-25" v:mID="7" v:groupContext="shape" transform="translate(481.501,-335.688)">
+ <title>Rectangle.62</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow7-26" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape8-30" v:mID="8" v:groupContext="shape" transform="translate(515.251,-335.688)">
+ <title>Rectangle.63</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow8-31" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape9-35" v:mID="9" v:groupContext="shape" transform="translate(319.501,-313.188)">
+ <title>Rectangle.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow9-36" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape10-40" v:mID="10" v:groupContext="shape" transform="translate(353.251,-313.188)">
+ <title>Rectangle.65</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow10-41" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape11-45" v:mID="11" v:groupContext="shape" transform="translate(400.501,-313.188)">
+ <title>Rectangle.66</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow11-46" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape12-50" v:mID="12" v:groupContext="shape" transform="translate(434.251,-313.188)">
+ <title>Rectangle.67</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow12-51" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape13-55" v:mID="13" v:groupContext="shape" transform="translate(481.501,-313.188)">
+ <title>Rectangle.68</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow13-56" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape14-60" v:mID="14" v:groupContext="shape" transform="translate(515.251,-313.188)">
+ <title>Rectangle.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow14-61" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape15-65" v:mID="15" v:groupContext="shape" transform="translate(319.501,-277.188)">
+ <title>Rectangle.70</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow15-66" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape16-71" v:mID="16" v:groupContext="shape" transform="translate(353.251,-277.188)">
+ <title>Rectangle.71</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow16-72" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape17-77" v:mID="17" v:groupContext="shape" transform="translate(400.501,-277.188)">
+ <title>Rectangle.72</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow17-78" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape18-83" v:mID="18" v:groupContext="shape" transform="translate(434.251,-277.188)">
+ <title>Rectangle.73</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow18-84" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape19-89" v:mID="19" v:groupContext="shape" transform="translate(481.501,-277.188)">
+ <title>Rectangle.74</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow19-90" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape20-95" v:mID="20" v:groupContext="shape" transform="translate(515.251,-277.188)">
+ <title>Rectangle.75</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow20-96" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape21-101" v:mID="21" v:groupContext="shape" transform="translate(319.501,-240.687)">
+ <title>Rectangle.76</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow21-102" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape22-106" v:mID="22" v:groupContext="shape" transform="translate(353.251,-240.687)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow22-107" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape23-111" v:mID="23" v:groupContext="shape" transform="translate(400.501,-240.687)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow23-112" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape24-116" v:mID="24" v:groupContext="shape" transform="translate(434.251,-240.687)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow24-117" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape25-121" v:mID="25" v:groupContext="shape" transform="translate(481.501,-240.687)">
+ <title>Rectangle.80</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow25-122" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape26-127" v:mID="26" v:groupContext="shape" transform="translate(515.251,-240.687)">
+ <title>Rectangle.81</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow26-128" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape27-133" v:mID="27" v:groupContext="shape" transform="translate(317.251,-231.687)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow27-134" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st6"/>
+ </g>
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st7"/>
+ </g>
+ <g id="shape28-138" v:mID="28" v:groupContext="shape" transform="translate(328.501,-362.688)">
+ <title>Sheet.28</title>
+ <desc>Local Table for N Specific Flows Serviced at Node 1</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="110.423" cy="418.94" width="220.85" height="11.25"/>
+ <rect x="0" y="413.315" width="220.846" height="11.25" class="st8"/>
+ <text x="5.77" y="421.94" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Local Table for N Specific Flows Serviced at Node 1</text> </g>
+ <g id="group34-141" transform="translate(66.0294,-165.569)" v:mID="34" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Device)"/>
+ <v:cp v:nameU="SubShapeType" v:lbl="SubShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Load balancer)"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Load balancer</title>
+ <g id="shape35-142" v:mID="35" v:groupContext="shape" transform="translate(0,-7.33146)">
+ <title>Sheet.35</title>
+ <g id="shadow35-143" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23 Z" class="st10"/>
+ <path d="M0 377.86 L72 377.86" class="st6"/>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23" class="st6"/>
+ </g>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23 Z" class="st11"/>
+ <path d="M0 377.86 L72 377.86" class="st12"/>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23" class="st12"/>
+ </g>
+ <g id="shape36-152" v:mID="36" v:groupContext="shape" transform="translate(8.03054,-12.9324)">
+ <title>Sheet.36</title>
+ <g id="shadow36-153" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M45.34 421.81 L41.2 422.66 L44.12 424.56 L49.68 423.16 L48.75 417.51 L45.8 415.59 L46.69 419.68 L36.97
+ 413.34 L35.6 415.45 L45.34 421.81 ZM50.83 405.36 L39.2 405.36 L39.2 407.88 L50.8 407.88 L47.82 410.83
+ L51.34 410.83 L55.21 406.61 L51.32 402.39 L47.83 402.39 L50.83 405.36 ZM46.49 392.01 L36.75 398.37
+ L38.13 400.48 L47.84 394.14 L46.96 398.23 L49.91 396.31 L50.84 390.66 L45.28 389.26 L42.36 391.16
+ L46.49 392.01 ZM27.71 397.16 C22.66 397.16 18.58 401.25 18.58 406.29 C18.58 411.33 22.66 415.42
+ 27.71 415.42 C32.75 415.42 36.84 411.33 36.84 406.29 C36.84 401.25 32.75 397.16 27.71 397.16 ZM27.71
+ 400.04 C31.15 400.04 33.96 402.84 33.96 406.29 C33.96 409.74 31.15 412.54 27.71 412.54 C24.26 412.54
+ 21.46 409.74 21.46 406.29 C21.46 402.84 24.26 400.04 27.71 400.04 ZM11.64 405.04 L0 405.04 L0 407.56
+ L11.6 407.56 L8.62 410.51 L12.14 410.51 L16.01 406.29 L12.12 402.07 L8.64 402.07 L11.64 405.04 Z"
+ class="st2"/>
+ </g>
+ <path d="M45.34 421.81 L41.2 422.66 L44.12 424.56 L49.68 423.16 L48.75 417.51 L45.8 415.59 L46.69 419.68 L36.97 413.34
+ L35.6 415.45 L45.34 421.81 ZM50.83 405.36 L39.2 405.36 L39.2 407.88 L50.8 407.88 L47.82 410.83 L51.34
+ 410.83 L55.21 406.61 L51.32 402.39 L47.83 402.39 L50.83 405.36 ZM46.49 392.01 L36.75 398.37 L38.13 400.48
+ L47.84 394.14 L46.96 398.23 L49.91 396.31 L50.84 390.66 L45.28 389.26 L42.36 391.16 L46.49 392.01 ZM27.71
+ 397.16 C22.66 397.16 18.58 401.25 18.58 406.29 C18.58 411.33 22.66 415.42 27.71 415.42 C32.75 415.42
+ 36.84 411.33 36.84 406.29 C36.84 401.25 32.75 397.16 27.71 397.16 ZM27.71 400.04 C31.15 400.04 33.96
+ 402.84 33.96 406.29 C33.96 409.74 31.15 412.54 27.71 412.54 C24.26 412.54 21.46 409.74 21.46 406.29
+ C21.46 402.84 24.26 400.04 27.71 400.04 ZM11.64 405.04 L0 405.04 L0 407.56 L11.6 407.56 L8.62 410.51
+ L12.14 410.51 L16.01 406.29 L12.12 402.07 L8.64 402.07 L11.64 405.04 Z" class="st13"/>
+ </g>
+ </g>
+ <g id="shape37-157" v:mID="37" v:groupContext="shape" transform="translate(21.0294,-45.4375)">
+ <title>Rectangle</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow37-158" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="336.433" width="135" height="88.1315" class="st2"/>
+ </g>
+ <rect x="0" y="336.433" width="135" height="88.1315" class="st3"/>
+ </g>
+ <g id="shape38-162" v:mID="38" v:groupContext="shape" transform="translate(34.693,-126.438)">
+ <title>Sheet.38</title>
+ <desc>EFD Table</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="49.3364" cy="415.565" width="98.68" height="18"/>
+ <rect x="0" y="406.565" width="98.6728" height="18" class="st8"/>
+ <text x="24.87" y="419.17" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>EFD Table</text> </g>
+ <g id="shape39-165" v:mID="39" v:groupContext="shape" transform="translate(30.0294,-99.4375)">
+ <title>Rectangle.39</title>
+ <desc>Group_id</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="26.9182" cy="415.565" width="53.84" height="18"/>
+ <g id="shadow39-166" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st15"/>
+ <text x="7.87" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group_id</text> </g>
+ <g id="shape40-171" v:mID="40" v:groupContext="shape" transform="translate(93.193,-99.4375)">
+ <title>Rectangle.40</title>
+ <desc>Hash index</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="26.9182" cy="415.565" width="53.84" height="18"/>
+ <g id="shadow40-172" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st15"/>
+ <text x="4.64" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Hash index</text> </g>
+ <g id="shape41-177" v:mID="41" v:groupContext="shape" transform="translate(30.193,-82.4275)">
+ <title>Rectangle.41</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow41-178" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape42-182" v:mID="42" v:groupContext="shape" transform="translate(30.193,-66.8125)">
+ <title>Rectangle.42</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow42-183" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape43-187" v:mID="43" v:groupContext="shape" transform="translate(30.1112,-52.1875)">
+ <title>Rectangle.43</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow43-188" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape44-192" v:mID="44" v:groupContext="shape" transform="translate(93.0294,-81.4375)">
+ <title>Rectangle.44</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow44-193" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape45-197" v:mID="45" v:groupContext="shape" transform="translate(93.193,-66.8125)">
+ <title>Rectangle.45</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow45-198" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape46-202" v:mID="46" v:groupContext="shape" transform="translate(93.193,-52.1875)">
+ <title>Rectangle.46</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow46-203" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape47-207" v:mID="47" v:groupContext="shape" transform="translate(374.924,544.022) rotate(135)">
+ <title>Sheet.47</title>
+ <path d="M-0 417.75 A40.674 18.0151 -156.2 0 0 40.24 422.15 L40.49 421.89" class="st17"/>
+ </g>
+ <g id="shape48-213" v:mID="48" v:groupContext="shape" transform="translate(21.0294,-19)">
+ <title>Sheet.48</title>
+ <desc>Supports X*N Flows</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="415.565" width="135" height="18"/>
+ <rect x="0" y="406.565" width="135" height="18" class="st19"/>
+ <text x="19.05" y="419.17" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Supports X*N Flows</text> </g>
+ <g id="shape49-216" v:mID="49" v:groupContext="shape" transform="translate(48.0294,-229.938)">
+ <title>Sheet.49</title>
+ <desc>Frontend Server or Load Balancer</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="400.94" width="108" height="47.25"/>
+ <rect x="0" y="377.315" width="108" height="47.25" class="st21"/>
+ <text x="14.56" y="397.34" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Frontend Server<v:newlineChar/><tspan
+ x="13.16" dy="1.2em" class="st22">or Load Balancer </tspan> </text> </g>
+ <g id="group51-220" transform="translate(223.876,-310.938)" v:mID="51" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server</title>
+ <g id="shape52-221" v:mID="52" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.52</title>
+ <g id="shadow52-222" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape53-226" v:mID="53" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.53</title>
+ <g id="shadow53-227" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape54-231" v:mID="54" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.54</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow54-232" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape59-239" v:mID="59" v:groupContext="shape" transform="translate(277.876,-373.938)">
+ <title>Sheet.59</title>
+ <path d="M-0 424.56 A111.108 53.2538 42.31 0 1 93.83 421.21 L94.14 421.41" class="st17"/>
+ </g>
+ <g id="shape60-244" v:mID="60" v:groupContext="shape" transform="translate(205.876,-283.938)">
+ <title>Sheet.60</title>
+ <desc>Backend Server 1</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.93" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server 1</text> </g>
+ <g id="group61-247" transform="translate(223.876,-207.438)" v:mID="61" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server.61</title>
+ <g id="shape62-248" v:mID="62" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.62</title>
+ <g id="shadow62-249" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape63-253" v:mID="63" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.63</title>
+ <g id="shadow63-254" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape64-258" v:mID="64" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow64-259" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape65-266" v:mID="65" v:groupContext="shape" transform="translate(205.876,-180.437)">
+ <title>Sheet.65</title>
+ <desc>Backend Server 2</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.93" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server 2</text> </g>
+ <g id="group66-269" transform="translate(219.029,-58.9375)" v:mID="66" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server.66</title>
+ <g id="shape67-270" v:mID="67" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.67</title>
+ <g id="shadow67-271" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape68-275" v:mID="68" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.68</title>
+ <g id="shadow68-276" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape69-280" v:mID="69" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow69-281" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape70-288" v:mID="70" v:groupContext="shape" transform="translate(201.029,-26.056)">
+ <title>Sheet.70</title>
+ <desc>Backend Server X</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.86" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server X</text> </g>
+ <g id="shape71-291" v:mID="71" v:groupContext="shape" transform="translate(684.44,239.627) rotate(90)">
+ <title>Sheet.71</title>
+ <path d="M0 424.56 L45 424.56" class="st24"/>
+ </g>
+ <g id="shape72-294" v:mID="72" v:groupContext="shape" transform="translate(6.85967,-22.443) rotate(-38.1076)">
+ <title>Sheet.72</title>
+ <path d="M-0 424.56 A96.1331 44.4001 55.03 0 1 68.24 420.56 L68.51 420.79" class="st17"/>
+ </g>
+ <g id="shape73-299" v:mID="73" v:groupContext="shape" transform="translate(328.501,-135.937)">
+ <title>Rectangle.73</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow73-300" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape74-305" v:mID="74" v:groupContext="shape" transform="translate(362.251,-135.937)">
+ <title>Rectangle.74</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow74-306" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape75-311" v:mID="75" v:groupContext="shape" transform="translate(409.501,-135.937)">
+ <title>Rectangle.75</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow75-312" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape76-317" v:mID="76" v:groupContext="shape" transform="translate(443.251,-135.937)">
+ <title>Rectangle.76</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow76-318" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape77-323" v:mID="77" v:groupContext="shape" transform="translate(490.501,-135.937)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow77-324" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape78-328" v:mID="78" v:groupContext="shape" transform="translate(524.251,-135.937)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow78-329" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape79-333" v:mID="79" v:groupContext="shape" transform="translate(328.501,-113.437)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow79-334" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape80-338" v:mID="80" v:groupContext="shape" transform="translate(362.251,-113.437)">
+ <title>Rectangle.80</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow80-339" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape81-343" v:mID="81" v:groupContext="shape" transform="translate(409.501,-113.437)">
+ <title>Rectangle.81</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow81-344" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape82-348" v:mID="82" v:groupContext="shape" transform="translate(443.251,-113.437)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow82-349" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape83-353" v:mID="83" v:groupContext="shape" transform="translate(490.501,-113.437)">
+ <title>Rectangle.83</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow83-354" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape84-358" v:mID="84" v:groupContext="shape" transform="translate(524.251,-113.437)">
+ <title>Rectangle.84</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow84-359" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape85-363" v:mID="85" v:groupContext="shape" transform="translate(328.501,-77.4375)">
+ <title>Rectangle.85</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow85-364" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape86-369" v:mID="86" v:groupContext="shape" transform="translate(362.251,-77.4375)">
+ <title>Rectangle.86</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow86-370" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape87-375" v:mID="87" v:groupContext="shape" transform="translate(409.501,-77.4375)">
+ <title>Rectangle.87</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow87-376" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape88-381" v:mID="88" v:groupContext="shape" transform="translate(443.251,-77.4375)">
+ <title>Rectangle.88</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow88-382" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape89-387" v:mID="89" v:groupContext="shape" transform="translate(490.501,-77.4375)">
+ <title>Rectangle.89</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow89-388" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape90-393" v:mID="90" v:groupContext="shape" transform="translate(524.251,-77.4375)">
+ <title>Rectangle.90</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow90-394" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape91-399" v:mID="91" v:groupContext="shape" transform="translate(328.501,-40.9375)">
+ <title>Rectangle.91</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow91-400" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape92-404" v:mID="92" v:groupContext="shape" transform="translate(362.251,-40.9375)">
+ <title>Rectangle.92</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow92-405" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape93-409" v:mID="93" v:groupContext="shape" transform="translate(409.501,-40.9375)">
+ <title>Rectangle.93</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow93-410" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape94-414" v:mID="94" v:groupContext="shape" transform="translate(443.251,-40.9375)">
+ <title>Rectangle.94</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow94-415" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape95-419" v:mID="95" v:groupContext="shape" transform="translate(490.501,-40.9375)">
+ <title>Rectangle.95</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow95-420" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape96-425" v:mID="96" v:groupContext="shape" transform="translate(524.251,-40.9375)">
+ <title>Rectangle.96</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow96-426" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape97-431" v:mID="97" v:groupContext="shape" transform="translate(326.251,-31.9375)">
+ <title>Rectangle.97</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow97-432" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st6"/>
+ </g>
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st7"/>
+ </g>
+ <g id="shape98-436" v:mID="98" v:groupContext="shape" transform="translate(337.501,-162.938)">
+ <title>Sheet.98</title>
+ <desc>Local Table for N Specific Flows Serviced at Node X</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="110.423" cy="418.94" width="220.85" height="11.25"/>
+ <rect x="0" y="413.315" width="220.846" height="11.25" class="st8"/>
+ <text x="5.55" y="421.94" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Local Table for N Specific Flows Serviced at Node X</text> </g>
+ <g id="shape99-439" v:mID="99" v:groupContext="shape" transform="translate(-204.342,-29.4449) rotate(-53.7462)">
+ <title>Sheet.99</title>
+ <path d="M0 424.56 L160.37 424.56" class="st25"/>
+ </g>
+ <g id="shape100-445" v:mID="100" v:groupContext="shape" transform="translate(-37.6568,-164.882) rotate(-24.444)">
+ <title>Sheet.100</title>
+ <path d="M0 424.56 L101.71 424.56" class="st25"/>
+ </g>
+ <g id="shape101-450" v:mID="101" v:groupContext="shape" transform="translate(464.049,-50.8578) rotate(50.099)">
+ <title>Sheet.101</title>
+ <path d="M0 424.56 L139.8 424.56" class="st25"/>
+ </g>
+ <g id="shape102-455" v:mID="102" v:groupContext="shape" transform="translate(372.376,-207.438)">
+ <title>Sheet.102</title>
+ <desc>Supports N Flows</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="415.565" width="135" height="18"/>
+ <rect x="0" y="406.565" width="135" height="18" class="st19"/>
+ <text x="25.15" y="419.17" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Supports N Flows</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i7.svg b/doc/guides/prog_guide/img/efd_i7.svg
new file mode 100644
index 0000000..98f8000
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i7.svg
@@ -0,0 +1,790 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i8.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="10.6168in" height="4.81965in"
+ viewBox="0 0 764.409 347.015" xml:space="preserve" color-interpolation-filters="sRGB" class="st27">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st4 {fill:#000000;font-family:Intel Clear;font-size:0.998566em}
+ .st5 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st6 {stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st7 {stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st8 {stroke:#ca8f02;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st9 {stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st10 {fill:#c00000;font-family:Intel Clear;font-size:0.828804em;font-weight:bold}
+ .st11 {fill:#7f6d00;font-family:Intel Clear;font-size:0.828804em;font-weight:bold}
+ .st12 {fill:#00b050;stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st13 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st14 {fill:#00b050;stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st15 {fill:#ca8f02;stroke:#ca8f02;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st16 {fill:#004280;font-family:Intel Clear;font-size:0.828804em}
+ .st17 {fill:#ffffff;font-family:Intel Clear;font-size:0.998566em}
+ .st18 {fill:#ffffff;font-family:Intel Clear;font-size:1.49785em}
+ .st19 {visibility:visible}
+ .st20 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st21 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st22 {fill:#feffff;font-family:Symbol;font-size:1.16666em}
+ .st23 {font-size:1em}
+ .st24 {font-family:Calibri;font-size:1em}
+ .st25 {fill:none;stroke:none;stroke-width:0.25}
+ .st26 {fill:#ffffff;font-family:Calibri;font-size:1.00001em}
+ .st27 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(27.7836,-307.505)">
+ <title>Sheet.3</title>
+ <path d="M0 329.94 C-0 328.06 1.54 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.06 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.54 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape4-3" v:mID="4" v:groupContext="shape" transform="translate(27.7836,-307.505)">
+ <title>Sheet.4</title>
+ <path d="M0 329.94 C-0 328.06 1.54 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.06 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.54 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape5-5" v:mID="5" v:groupContext="shape" transform="translate(50.1544,-309.121)">
+ <title>Sheet.5</title>
+ <desc>Key1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1</text> </g>
+ <g id="shape6-9" v:mID="6" v:groupContext="shape" transform="translate(43.6909,-286.954)">
+ <title>Sheet.6</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape7-11" v:mID="7" v:groupContext="shape" transform="translate(27.7836,-266.044)">
+ <title>Sheet.7</title>
+ <path d="M0 330.04 C0 328.16 1.52 326.64 3.41 326.64 L68.51 326.64 C70.4 326.64 71.91 328.16 71.91 330.04 L71.91 343.62
+ C71.91 345.49 70.4 347.02 68.51 347.02 L3.41 347.02 C1.52 347.02 0 345.49 0 343.62 L0 330.04 Z"
+ class="st1"/>
+ </g>
+ <g id="shape8-13" v:mID="8" v:groupContext="shape" transform="translate(27.7836,-266.044)">
+ <title>Sheet.8</title>
+ <path d="M0 330.04 C0 328.16 1.52 326.64 3.41 326.64 L68.51 326.64 C70.4 326.64 71.91 328.16 71.91 330.04 L71.91 343.62
+ C71.91 345.49 70.4 347.02 68.51 347.02 L3.41 347.02 C1.52 347.02 0 345.49 0 343.62 L0 330.04 Z"
+ class="st2"/>
+ </g>
+ <g id="shape9-15" v:mID="9" v:groupContext="shape" transform="translate(50.7572,-267.602)">
+ <title>Sheet.9</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape10-19" v:mID="10" v:groupContext="shape" transform="translate(19.0195,-225.183)">
+ <title>Sheet.10</title>
+ <path d="M0 330.74 C0 328.94 1.46 327.48 3.26 327.48 L87.15 327.48 C88.95 327.48 90.4 328.94 90.4 330.74 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.26 347.02 C1.46 347.02 0 345.56 0 343.76 L0 330.74 Z"
+ class="st1"/>
+ </g>
+ <g id="shape11-21" v:mID="11" v:groupContext="shape" transform="translate(19.0195,-225.183)">
+ <title>Sheet.11</title>
+ <path d="M0 330.74 C0 328.94 1.46 327.48 3.26 327.48 L87.15 327.48 C88.95 327.48 90.4 328.94 90.4 330.74 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.26 347.02 C1.46 347.02 0 345.56 0 343.76 L0 330.74 Z"
+ class="st2"/>
+ </g>
+ <g id="shape12-23" v:mID="12" v:groupContext="shape" transform="translate(28.0373,-226.287)">
+ <title>Sheet.12</title>
+ <desc>0x0102ABCD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.3615" cy="339.824" width="86.73" height="14.3829"/>
+ <path d="M86.72 332.63 L0 332.63 L0 347.02 L86.72 347.02 L86.72 332.63" class="st3"/>
+ <text x="7.12" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102ABCD</text> </g>
+ <g id="shape13-27" v:mID="13" v:groupContext="shape" transform="translate(43.6909,-244.775)">
+ <title>Sheet.13</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape14-29" v:mID="14" v:groupContext="shape" transform="translate(40.7496,-210.444)">
+ <title>Sheet.14</title>
+ <path d="M26.29 334.91 C26.29 338.26 25.84 340.96 25.29 340.96 L14.16 340.96 C13.6 340.96 13.15 343.67 13.15 347.02 C13.15
+ 343.67 12.7 340.96 12.14 340.96 L1.01 340.96 C0.46 340.96 0 338.26 0 334.91" class="st6"/>
+ </g>
+ <g id="shape15-32" v:mID="15" v:groupContext="shape" transform="translate(125.629,-307.625)">
+ <title>Sheet.15</title>
+ <path d="M0 330.04 C0 328.16 1.52 326.64 3.41 326.64 L68.63 326.64 C70.51 326.64 72.03 328.16 72.03 330.04 L72.03 343.62
+ C72.03 345.49 70.51 347.02 68.63 347.02 L3.41 347.02 C1.52 347.02 0 345.49 0 343.62 L0 330.04 Z"
+ class="st1"/>
+ </g>
+ <g id="shape16-34" v:mID="16" v:groupContext="shape" transform="translate(125.629,-307.625)">
+ <title>Sheet.16</title>
+ <path d="M0 330.04 C0 328.16 1.52 326.64 3.41 326.64 L68.63 326.64 C70.51 326.64 72.03 328.16 72.03 330.04 L72.03 343.62
+ C72.03 345.49 70.51 347.02 68.63 347.02 L3.41 347.02 C1.52 347.02 0 345.49 0 343.62 L0 330.04 Z"
+ class="st2"/>
+ </g>
+ <g id="shape17-36" v:mID="17" v:groupContext="shape" transform="translate(148.034,-309.155)">
+ <title>Sheet.17</title>
+ <desc>Key2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key2</text> </g>
+ <g id="shape18-40" v:mID="18" v:groupContext="shape" transform="translate(141.536,-286.954)">
+ <title>Sheet.18</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape19-42" v:mID="19" v:groupContext="shape" transform="translate(125.629,-266.044)">
+ <title>Sheet.19</title>
+ <path d="M0 329.94 C0 328.06 1.54 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.06 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.54 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st1"/>
+ </g>
+ <g id="shape20-44" v:mID="20" v:groupContext="shape" transform="translate(125.629,-266.044)">
+ <title>Sheet.20</title>
+ <path d="M0 329.94 C0 328.06 1.54 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.06 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.54 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st2"/>
+ </g>
+ <g id="shape21-46" v:mID="21" v:groupContext="shape" transform="translate(148.636,-267.636)">
+ <title>Sheet.21</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape22-50" v:mID="22" v:groupContext="shape" transform="translate(116.865,-225.183)">
+ <title>Sheet.22</title>
+ <path d="M0 330.74 C0 328.94 1.46 327.48 3.26 327.48 L87.15 327.48 C88.95 327.48 90.4 328.94 90.4 330.74 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.26 347.02 C1.46 347.02 0 345.56 0 343.76 L0 330.74 Z"
+ class="st1"/>
+ </g>
+ <g id="shape23-52" v:mID="23" v:groupContext="shape" transform="translate(116.865,-225.183)">
+ <title>Sheet.23</title>
+ <path d="M0 330.74 C0 328.94 1.46 327.48 3.26 327.48 L87.15 327.48 C88.95 327.48 90.4 328.94 90.4 330.74 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.26 347.02 C1.46 347.02 0 345.56 0 343.76 L0 330.74 Z"
+ class="st2"/>
+ </g>
+ <g id="shape24-54" v:mID="24" v:groupContext="shape" transform="translate(125.917,-226.322)">
+ <title>Sheet.24</title>
+ <desc>0x0103CDAB</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.3615" cy="339.824" width="86.73" height="14.3829"/>
+ <path d="M86.72 332.63 L0 332.63 L0 347.02 L86.72 347.02 L86.72 332.63" class="st3"/>
+ <text x="7.12" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0103CDAB</text> </g>
+ <g id="shape25-58" v:mID="25" v:groupContext="shape" transform="translate(141.536,-244.775)">
+ <title>Sheet.25</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape26-60" v:mID="26" v:groupContext="shape" transform="translate(138.595,-210.444)">
+ <title>Sheet.26</title>
+ <path d="M26.29 334.91 C26.29 338.26 25.84 340.96 25.29 340.96 L14.16 340.96 C13.6 340.96 13.15 343.67 13.15 347.02 C13.15
+ 343.67 12.7 340.96 12.14 340.96 L1.01 340.96 C0.46 340.96 0 338.26 0 334.91" class="st7"/>
+ </g>
+ <g id="shape27-63" v:mID="27" v:groupContext="shape" transform="translate(221.793,-307.625)">
+ <title>Sheet.27</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.64 326.64 C70.52 326.64 72.03 328.17 72.03 330.04 L72.03 343.63
+ C72.03 345.5 70.52 347.02 68.64 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st1"/>
+ </g>
+ <g id="shape28-65" v:mID="28" v:groupContext="shape" transform="translate(221.793,-307.625)">
+ <title>Sheet.28</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.64 326.64 C70.52 326.64 72.03 328.17 72.03 330.04 L72.03 343.63
+ C72.03 345.5 70.52 347.02 68.64 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st2"/>
+ </g>
+ <g id="shape29-67" v:mID="29" v:groupContext="shape" transform="translate(244.237,-309.155)">
+ <title>Sheet.29</title>
+ <desc>Key3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3</text> </g>
+ <g id="shape30-71" v:mID="30" v:groupContext="shape" transform="translate(237.701,-286.954)">
+ <title>Sheet.30</title>
+ <path d="M0 336.65 L9.84 336.65 L9.84 326.28 L29.53 326.28 L29.53 336.65 L39.38 336.65 L19.69 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape31-73" v:mID="31" v:groupContext="shape" transform="translate(221.793,-266.044)">
+ <title>Sheet.31</title>
+ <path d="M0 329.94 C-0 328.07 1.55 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.07 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st1"/>
+ </g>
+ <g id="shape32-75" v:mID="32" v:groupContext="shape" transform="translate(221.793,-266.044)">
+ <title>Sheet.32</title>
+ <path d="M0 329.94 C-0 328.07 1.55 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.07 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st2"/>
+ </g>
+ <g id="shape33-77" v:mID="33" v:groupContext="shape" transform="translate(244.84,-267.636)">
+ <title>Sheet.33</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape34-81" v:mID="34" v:groupContext="shape" transform="translate(213.029,-225.183)">
+ <title>Sheet.34</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.27 327.48 C89.07 327.48 90.52 328.95 90.52 330.75 L90.52 343.76
+ C90.52 345.56 89.07 347.02 87.27 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape35-83" v:mID="35" v:groupContext="shape" transform="translate(213.029,-225.183)">
+ <title>Sheet.35</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.27 327.48 C89.07 327.48 90.52 328.95 90.52 330.75 L90.52 343.76
+ C90.52 345.56 89.07 347.02 87.27 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape36-85" v:mID="36" v:groupContext="shape" transform="translate(222.002,-226.322)">
+ <title>Sheet.36</title>
+ <desc>0x0102BAAD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.4787" cy="339.824" width="86.96" height="14.3829"/>
+ <path d="M86.96 332.63 L0 332.63 L0 347.02 L86.96 347.02 L86.96 332.63" class="st3"/>
+ <text x="7.13" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102BAAD</text> </g>
+ <g id="shape37-89" v:mID="37" v:groupContext="shape" transform="translate(237.701,-244.775)">
+ <title>Sheet.37</title>
+ <path d="M0 336.71 L9.84 336.71 L9.84 326.4 L29.53 326.4 L29.53 336.71 L39.38 336.71 L19.69 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape38-91" v:mID="38" v:groupContext="shape" transform="translate(234.759,-210.444)">
+ <title>Sheet.38</title>
+ <path d="M26.41 334.91 C26.41 338.26 25.96 340.96 25.41 340.96 L14.22 340.96 C13.66 340.96 13.21 343.67 13.21 347.02
+ C13.21 343.67 12.76 340.96 12.2 340.96 L1.01 340.96 C0.46 340.96 0 338.26 0 334.91" class="st6"/>
+ </g>
+ <g id="shape39-94" v:mID="39" v:groupContext="shape" transform="translate(319.759,-307.625)">
+ <title>Sheet.39</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.52 326.64 C70.4 326.64 71.91 328.17 71.91 330.04 L71.91 343.63
+ C71.91 345.5 70.4 347.02 68.52 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st1"/>
+ </g>
+ <g id="shape40-96" v:mID="40" v:groupContext="shape" transform="translate(319.759,-307.625)">
+ <title>Sheet.40</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.52 326.64 C70.4 326.64 71.91 328.17 71.91 330.04 L71.91 343.63
+ C71.91 345.5 70.4 347.02 68.52 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st2"/>
+ </g>
+ <g id="shape41-98" v:mID="41" v:groupContext="shape" transform="translate(342.125,-309.155)">
+ <title>Sheet.41</title>
+ <desc>Key4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4</text> </g>
+ <g id="shape42-102" v:mID="42" v:groupContext="shape" transform="translate(335.666,-286.954)">
+ <title>Sheet.42</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape43-104" v:mID="43" v:groupContext="shape" transform="translate(319.759,-266.044)">
+ <title>Sheet.43</title>
+ <path d="M0 329.94 C0 328.07 1.55 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.07 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape44-106" v:mID="44" v:groupContext="shape" transform="translate(319.759,-266.044)">
+ <title>Sheet.44</title>
+ <path d="M0 329.94 C0 328.07 1.55 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.07 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape45-108" v:mID="45" v:groupContext="shape" transform="translate(342.728,-267.636)">
+ <title>Sheet.45</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape46-112" v:mID="46" v:groupContext="shape" transform="translate(310.995,-225.183)">
+ <title>Sheet.46</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.15 327.48 C88.95 327.48 90.4 328.95 90.4 330.75 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape47-114" v:mID="47" v:groupContext="shape" transform="translate(310.995,-225.183)">
+ <title>Sheet.47</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.15 327.48 C88.95 327.48 90.4 328.95 90.4 330.75 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape48-116" v:mID="48" v:groupContext="shape" transform="translate(321.689,-226.322)">
+ <title>Sheet.48</title>
+ <desc>0x0104BEEF</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="41.4183" cy="339.824" width="82.84" height="14.3829"/>
+ <path d="M82.84 332.63 L0 332.63 L0 347.02 L82.84 347.02 L82.84 332.63" class="st3"/>
+ <text x="6.87" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0104BEEF</text> </g>
+ <g id="shape49-120" v:mID="49" v:groupContext="shape" transform="translate(335.666,-244.775)">
+ <title>Sheet.49</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape50-122" v:mID="50" v:groupContext="shape" transform="translate(332.725,-210.444)">
+ <title>Sheet.50</title>
+ <path d="M26.29 334.91 C26.29 338.27 25.84 340.96 25.29 340.96 L14.17 340.96 C13.61 340.96 13.15 343.67 13.15 347.02
+ C13.15 343.67 12.7 340.96 12.14 340.96 L1.02 340.96 C0.47 340.96 0 338.27 0 334.91" class="st6"/>
+ </g>
+ <g id="shape51-125" v:mID="51" v:groupContext="shape" transform="translate(416.884,-307.625)">
+ <title>Sheet.51</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.52 326.64 C70.4 326.64 71.91 328.17 71.91 330.04 L71.91 343.63
+ C71.91 345.5 70.4 347.02 68.52 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st1"/>
+ </g>
+ <g id="shape52-127" v:mID="52" v:groupContext="shape" transform="translate(416.884,-307.625)">
+ <title>Sheet.52</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.52 326.64 C70.4 326.64 71.91 328.17 71.91 330.04 L71.91 343.63
+ C71.91 345.5 70.4 347.02 68.52 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st2"/>
+ </g>
+ <g id="shape53-129" v:mID="53" v:groupContext="shape" transform="translate(439.255,-309.155)">
+ <title>Sheet.53</title>
+ <desc>Key5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key5</text> </g>
+ <g id="shape54-133" v:mID="54" v:groupContext="shape" transform="translate(432.791,-286.954)">
+ <title>Sheet.54</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape55-135" v:mID="55" v:groupContext="shape" transform="translate(416.884,-266.044)">
+ <title>Sheet.55</title>
+ <path d="M0 329.94 C0 328.07 1.55 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.07 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape56-137" v:mID="56" v:groupContext="shape" transform="translate(416.884,-266.044)">
+ <title>Sheet.56</title>
+ <path d="M0 329.94 C0 328.07 1.55 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.07 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape57-139" v:mID="57" v:groupContext="shape" transform="translate(439.858,-267.636)">
+ <title>Sheet.57</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape58-143" v:mID="58" v:groupContext="shape" transform="translate(408.12,-225.183)">
+ <title>Sheet.58</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.15 327.48 C88.95 327.48 90.4 328.95 90.4 330.75 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape59-145" v:mID="59" v:groupContext="shape" transform="translate(408.12,-225.183)">
+ <title>Sheet.59</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.15 327.48 C88.95 327.48 90.4 328.95 90.4 330.75 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape60-147" v:mID="60" v:groupContext="shape" transform="translate(416.778,-226.322)">
+ <title>Sheet.60</title>
+ <desc>0x0103DABD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.7817" cy="339.824" width="87.57" height="14.3829"/>
+ <path d="M87.56 332.63 L0 332.63 L0 347.02 L87.56 347.02 L87.56 332.63" class="st3"/>
+ <text x="7.17" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0103DABD</text> </g>
+ <g id="shape61-151" v:mID="61" v:groupContext="shape" transform="translate(432.791,-244.775)">
+ <title>Sheet.61</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape62-153" v:mID="62" v:groupContext="shape" transform="translate(429.85,-210.444)">
+ <title>Sheet.62</title>
+ <path d="M26.29 334.91 C26.29 338.27 25.84 340.96 25.29 340.96 L14.17 340.96 C13.61 340.96 13.15 343.67 13.15 347.02
+ C13.15 343.67 12.7 340.96 12.14 340.96 L1.02 340.96 C0.47 340.96 0 338.27 0 334.91" class="st7"/>
+ </g>
+ <g id="shape63-156" v:mID="63" v:groupContext="shape" transform="translate(514.489,-307.625)">
+ <title>Sheet.63</title>
+ <path d="M0 330.06 C-0 328.17 1.53 326.64 3.42 326.64 L68.64 326.64 C70.53 326.64 72.03 328.17 72.03 330.06 L72.03 343.63
+ C72.03 345.52 70.53 347.02 68.64 347.02 L3.42 347.02 C1.53 347.02 0 345.52 0 343.63 L0 330.06 Z"
+ class="st1"/>
+ </g>
+ <g id="shape64-158" v:mID="64" v:groupContext="shape" transform="translate(514.489,-307.625)">
+ <title>Sheet.64</title>
+ <path d="M0 330.06 C-0 328.17 1.53 326.64 3.42 326.64 L68.64 326.64 C70.53 326.64 72.03 328.17 72.03 330.06 L72.03 343.63
+ C72.03 345.52 70.53 347.02 68.64 347.02 L3.42 347.02 C1.53 347.02 0 345.52 0 343.63 L0 330.06 Z"
+ class="st2"/>
+ </g>
+ <g id="shape65-160" v:mID="65" v:groupContext="shape" transform="translate(536.883,-309.19)">
+ <title>Sheet.65</title>
+ <desc>Key6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key6</text> </g>
+ <g id="shape66-164" v:mID="66" v:groupContext="shape" transform="translate(530.396,-287.074)">
+ <title>Sheet.66</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape67-166" v:mID="67" v:groupContext="shape" transform="translate(514.489,-266.044)">
+ <title>Sheet.67</title>
+ <path d="M0 329.94 C0 328.08 1.56 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.08 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.56 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st1"/>
+ </g>
+ <g id="shape68-168" v:mID="68" v:groupContext="shape" transform="translate(514.489,-266.044)">
+ <title>Sheet.68</title>
+ <path d="M0 329.94 C0 328.08 1.56 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.08 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.56 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st2"/>
+ </g>
+ <g id="shape69-170" v:mID="69" v:groupContext="shape" transform="translate(537.486,-267.671)">
+ <title>Sheet.69</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape70-174" v:mID="70" v:groupContext="shape" transform="translate(505.725,-225.183)">
+ <title>Sheet.70</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.16 327.48 C88.96 327.48 90.4 328.95 90.4 330.75 L90.4 343.78
+ C90.4 345.58 88.96 347.02 87.16 347.02 L3.27 347.02 C1.47 347.02 0 345.58 0 343.78 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape71-176" v:mID="71" v:groupContext="shape" transform="translate(505.725,-225.183)">
+ <title>Sheet.71</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.16 327.48 C88.96 327.48 90.4 328.95 90.4 330.75 L90.4 343.78
+ C90.4 345.58 88.96 347.02 87.16 347.02 L3.27 347.02 C1.47 347.02 0 345.58 0 343.78 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape72-178" v:mID="72" v:groupContext="shape" transform="translate(514.766,-226.356)">
+ <title>Sheet.72</title>
+ <desc>0x0102ADCB</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.3615" cy="339.824" width="86.73" height="14.3829"/>
+ <path d="M86.72 332.63 L0 332.63 L0 347.02 L86.72 347.02 L86.72 332.63" class="st3"/>
+ <text x="7.12" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102ADCB</text> </g>
+ <g id="shape73-182" v:mID="73" v:groupContext="shape" transform="translate(530.396,-244.775)">
+ <title>Sheet.73</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape74-184" v:mID="74" v:groupContext="shape" transform="translate(527.455,-210.564)">
+ <title>Sheet.74</title>
+ <path d="M26.29 335.03 C26.29 338.36 25.87 341.02 25.3 341.02 L14.17 341.02 C13.6 341.02 13.15 343.72 13.15 347.02 C13.15
+ 343.72 12.73 341.02 12.16 341.02 L1.02 341.02 C0.45 341.02 0 338.36 0 335.03" class="st6"/>
+ </g>
+ <g id="shape75-187" v:mID="75" v:groupContext="shape" transform="translate(610.653,-307.505)">
+ <title>Sheet.75</title>
+ <path d="M0 329.94 C0 328.08 1.56 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.08 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.56 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st1"/>
+ </g>
+ <g id="shape76-189" v:mID="76" v:groupContext="shape" transform="translate(610.653,-307.505)">
+ <title>Sheet.76</title>
+ <path d="M0 329.94 C0 328.08 1.56 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.08 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.56 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st2"/>
+ </g>
+ <g id="shape77-191" v:mID="77" v:groupContext="shape" transform="translate(633.086,-309.121)">
+ <title>Sheet.77</title>
+ <desc>Key7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7</text> </g>
+ <g id="shape78-195" v:mID="78" v:groupContext="shape" transform="translate(626.561,-286.954)">
+ <title>Sheet.78</title>
+ <path d="M0 336.65 L9.84 336.65 L9.84 326.28 L29.53 326.28 L29.53 336.65 L39.38 336.65 L19.69 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape79-197" v:mID="79" v:groupContext="shape" transform="translate(610.653,-266.044)">
+ <title>Sheet.79</title>
+ <path d="M0 330.06 C-0 328.17 1.53 326.64 3.42 326.64 L68.64 326.64 C70.53 326.64 72.03 328.17 72.03 330.06 L72.03 343.63
+ C72.03 345.52 70.53 347.02 68.64 347.02 L3.42 347.02 C1.53 347.02 0 345.52 0 343.63 L0 330.06 Z"
+ class="st1"/>
+ </g>
+ <g id="shape80-199" v:mID="80" v:groupContext="shape" transform="translate(610.653,-266.044)">
+ <title>Sheet.80</title>
+ <path d="M0 330.06 C-0 328.17 1.53 326.64 3.42 326.64 L68.64 326.64 C70.53 326.64 72.03 328.17 72.03 330.06 L72.03 343.63
+ C72.03 345.52 70.53 347.02 68.64 347.02 L3.42 347.02 C1.53 347.02 0 345.52 0 343.63 L0 330.06 Z"
+ class="st2"/>
+ </g>
+ <g id="shape81-201" v:mID="81" v:groupContext="shape" transform="translate(633.689,-267.602)">
+ <title>Sheet.81</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape82-205" v:mID="82" v:groupContext="shape" transform="translate(601.889,-225.183)">
+ <title>Sheet.82</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.28 327.48 C89.08 327.48 90.52 328.95 90.52 330.75 L90.52 343.78
+ C90.52 345.58 89.08 347.02 87.28 347.02 L3.27 347.02 C1.47 347.02 0 345.58 0 343.78 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape83-207" v:mID="83" v:groupContext="shape" transform="translate(601.889,-225.183)">
+ <title>Sheet.83</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.28 327.48 C89.08 327.48 90.52 328.95 90.52 330.75 L90.52 343.78
+ C90.52 345.58 89.08 347.02 87.28 347.02 L3.27 347.02 C1.47 347.02 0 345.58 0 343.78 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape84-209" v:mID="84" v:groupContext="shape" transform="translate(610.969,-226.287)">
+ <title>Sheet.84</title>
+ <desc>0x0104DBCA</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.3615" cy="339.824" width="86.73" height="14.3829"/>
+ <path d="M86.72 332.63 L0 332.63 L0 347.02 L86.72 347.02 L86.72 332.63" class="st3"/>
+ <text x="7.12" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0104DBCA</text> </g>
+ <g id="shape85-213" v:mID="85" v:groupContext="shape" transform="translate(626.561,-244.775)">
+ <title>Sheet.85</title>
+ <path d="M0 336.71 L9.84 336.71 L9.84 326.4 L29.53 326.4 L29.53 336.71 L39.38 336.71 L19.69 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape86-215" v:mID="86" v:groupContext="shape" transform="translate(623.619,-210.444)">
+ <title>Sheet.86</title>
+ <path d="M26.41 334.91 C26.41 338.27 25.96 340.96 25.42 340.96 L14.23 340.96 C13.69 340.96 13.21 343.69 13.21 347.02
+ C13.21 343.69 12.76 340.96 12.22 340.96 L1.02 340.96 C0.48 340.96 0 338.27 0 334.91" class="st8"/>
+ </g>
+ <g id="shape87-218" v:mID="87" v:groupContext="shape" transform="translate(242.323,-81.6288)">
+ <title>Sheet.87</title>
+ <path d="M0 281.23 L0 347.02 L41.18 347.02 L41.18 281.23 L0 281.23 L0 281.23 Z" class="st1"/>
+ </g>
+ <g id="shape88-220" v:mID="88" v:groupContext="shape" transform="translate(247.009,-81.6288)">
+ <title>Sheet.88</title>
+ <path d="M0 281.23 L41.18 281.23 L41.18 347.02 L0 347.02 L0 281.23" class="st9"/>
+ </g>
+ <g id="shape89-223" v:mID="89" v:groupContext="shape" transform="translate(245.254,-132.398)">
+ <title>Sheet.89</title>
+ <desc>0x0102</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.3211" cy="341.046" width="42.65" height="11.9384"/>
+ <path d="M42.64 335.08 L0 335.08 L0 347.02 L42.64 347.02 L42.64 335.08" class="st3"/>
+ <text x="4" y="344.03" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102</text> </g>
+ <g id="shape90-227" v:mID="90" v:groupContext="shape" transform="translate(245.015,-82.7016)">
+ <title>Sheet.90</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.79425" cy="341.046" width="9.59" height="11.9384"/>
+ <path d="M9.59 335.08 L0 335.08 L0 347.02 L9.59 347.02 L9.59 335.08" class="st3"/>
+ <text x="1.84" y="344.03" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape91-231" v:mID="91" v:groupContext="shape" transform="translate(336.326,-81.6288)">
+ <title>Sheet.91</title>
+ <path d="M0 281.23 L0 347.02 L41.18 347.02 L41.18 281.23 L0 281.23 L0 281.23 Z" class="st1"/>
+ </g>
+ <g id="shape92-233" v:mID="92" v:groupContext="shape" transform="translate(339.598,-81.6288)">
+ <title>Sheet.92</title>
+ <path d="M0 281.23 L41.18 281.23 L41.18 347.02 L0 347.02 L0 281.23" class="st9"/>
+ </g>
+ <g id="shape93-236" v:mID="93" v:groupContext="shape" transform="translate(339.264,-132.398)">
+ <title>Sheet.93</title>
+ <desc>0x0103</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.3211" cy="341.046" width="42.65" height="11.9384"/>
+ <path d="M42.64 335.08 L0 335.08 L0 347.02 L42.64 347.02 L42.64 335.08" class="st3"/>
+ <text x="4" y="344.03" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0103</text> </g>
+ <g id="shape94-240" v:mID="94" v:groupContext="shape" transform="translate(339.024,-82.7016)">
+ <title>Sheet.94</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.79425" cy="341.046" width="9.59" height="11.9384"/>
+ <path d="M9.59 335.08 L0 335.08 L0 347.02 L9.59 347.02 L9.59 335.08" class="st3"/>
+ <text x="1.84" y="344.03" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape95-244" v:mID="95" v:groupContext="shape" transform="translate(438.598,-81.5089)">
+ <title>Sheet.95</title>
+ <path d="M0 281.23 L0 347.02 L41.18 347.02 L41.18 281.23 L0 281.23 L0 281.23 Z" class="st1"/>
+ </g>
+ <g id="shape96-246" v:mID="96" v:groupContext="shape" transform="translate(438.598,-81.5089)">
+ <title>Sheet.96</title>
+ <path d="M0 281.23 L41.18 281.23 L41.18 347.02 L0 347.02 L0 281.23" class="st9"/>
+ </g>
+ <g id="shape97-249" v:mID="97" v:groupContext="shape" transform="translate(437.81,-132.27)">
+ <title>Sheet.97</title>
+ <desc>0x0104</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.3211" cy="341.046" width="42.65" height="11.9384"/>
+ <path d="M42.64 335.08 L0 335.08 L0 347.02 L42.64 347.02 L42.64 335.08" class="st3"/>
+ <text x="4" y="344.03" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0104</text> </g>
+ <g id="shape98-253" v:mID="98" v:groupContext="shape" transform="translate(437.57,-82.5735)">
+ <title>Sheet.98</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.79425" cy="341.046" width="9.59" height="11.9384"/>
+ <path d="M9.59 335.08 L0 335.08 L0 347.02 L9.59 347.02 L9.59 335.08" class="st3"/>
+ <text x="1.84" y="344.03" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape99-257" v:mID="99" v:groupContext="shape" transform="translate(53.5505,-147.924)">
+ <title>Sheet.99</title>
+ <path d="M0.59 283.52 L206.27 343.39 L205.7 345.34 L0 285.48 L0.59 283.52 L0.59 283.52 ZM205.85 341.14 L210.88 345.79
+ L204.14 347.02 L205.85 341.14 L205.85 341.14 Z" class="st12"/>
+ </g>
+ <g id="shape100-259" v:mID="100" v:groupContext="shape" transform="translate(151.516,-147.924)">
+ <title>Sheet.100</title>
+ <path d="M0.59 283.52 L202.41 343.41 L201.83 345.35 L0 285.48 L0.59 283.52 L0.59 283.52 ZM202.01 341.16 L207.01 345.83
+ L200.27 347.02 L202.01 341.16 L202.01 341.16 Z" class="st13"/>
+ </g>
+ <g id="shape101-261" v:mID="101" v:groupContext="shape" transform="translate(246.975,-147.37)">
+ <title>Sheet.101</title>
+ <path d="M2 283.72 L15.77 341.83 L13.79 342.3 L0 284.18 L2 283.72 L2 283.72 ZM17.53 340.36 L15.97 347.02 L11.57 341.77
+ L17.53 340.36 L17.53 340.36 Z" class="st12"/>
+ </g>
+ <g id="shape102-263" v:mID="102" v:groupContext="shape" transform="translate(262.972,-147.37)">
+ <title>Sheet.102</title>
+ <path d="M82.31 283.13 L3.45 343.12 L4.68 344.74 L83.54 284.76 L82.31 283.13 L82.31 283.13 ZM3.02 340.89 L0 347.02 L6.74
+ 345.74 L3.02 340.89 L3.02 340.89 Z" class="st12"/>
+ </g>
+ <g id="shape103-265" v:mID="103" v:groupContext="shape" transform="translate(358.537,-149.107)">
+ <title>Sheet.103</title>
+ <path d="M83.92 284.85 L3.53 343.2 L4.73 344.84 L85.12 286.5 L83.92 284.85 L83.92 284.85 ZM3.15 340.95 L0 347.02 L6.75
+ 345.89 L3.15 340.95 L3.15 340.95 Z" class="st13"/>
+ </g>
+ <g id="shape104-267" v:mID="104" v:groupContext="shape" transform="translate(264.413,-147.534)">
+ <title>Sheet.104</title>
+ <path d="M275.95 283 L4.77 343.27 L5.22 345.25 L276.37 285 L275.95 283 L275.95 283 ZM5.31 341.05 L0 345.37 L6.66 347.02
+ L5.31 341.05 L5.31 341.05 Z" class="st14"/>
+ </g>
+ <g id="shape105-269" v:mID="105" v:groupContext="shape" transform="translate(456.982,-148.103)">
+ <title>Sheet.105</title>
+ <path d="M179.48 283.72 L4.5 343.48 L5.16 345.43 L180.14 285.66 L179.48 283.72 L179.48 283.72 ZM4.8 341.23 L0 346.12
+ L6.81 347.02 L4.8 341.23 L4.8 341.23 Z" class="st15"/>
+ </g>
+ <g id="shape106-271" v:mID="106" v:groupContext="shape" transform="translate(335.628,-18)">
+ <title>Sheet.106</title>
+ <path d="M0 309.64 C0 305.52 2.99 302.16 6.65 302.16 L14.2 302.16 L8.01 284.85 L35.48 302.16 L78.47 302.16 C82.15 302.16
+ 85.12 305.52 85.12 309.64 L85.12 309.64 L85.12 320.85 L85.12 339.54 C85.12 343.68 82.15 347.02 78.47 347.02
+ L35.48 347.02 L14.2 347.02 L14.2 347.02 L6.65 347.02 C2.99 347.02 0 343.68 0 339.54 L0 320.85 L0 309.64
+ L0 309.64 Z" class="st5"/>
+ </g>
+ <g id="shape109-273" v:mID="109" v:groupContext="shape" transform="translate(157.564,-62.4234)">
+ <title>Sheet.109</title>
+ <path d="M16.21 347.02 C11.74 347.02 8.1 346.42 8.1 345.67 L8.1 303.49 C8.1 302.75 4.49 302.14 0 302.14 C4.49 302.14
+ 8.1 301.54 8.1 300.79 L8.1 258.61 C8.1 257.88 11.74 257.26 16.21 257.26" class="st7"/>
+ </g>
+ <g id="shape110-276" v:mID="110" v:groupContext="shape" transform="translate(113.844,-100.157)">
+ <title>Sheet.110</title>
+ <desc>Groups</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="20.2175" cy="341.046" width="40.44" height="11.9384"/>
+ <path d="M40.44 335.08 L0 335.08 L0 347.02 L40.44 347.02 L40.44 335.08" class="st3"/>
+ <text x="3.85" y="344.03" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Groups</text> </g>
+ <g id="shape111-280" v:mID="111" v:groupContext="shape" transform="translate(196.718,-76.2186)">
+ <title>Sheet.111</title>
+ <path d="M0 331.97 C0 330.32 2.27 328.96 5.04 328.96 L37.61 328.96 L60.43 284.85 L53.72 328.96 L59.43 328.96 C62.22 328.96
+ 64.47 330.32 64.47 331.97 L64.47 331.97 L64.47 336.48 L64.47 344.01 C64.47 345.67 62.22 347.02 59.43 347.02
+ L53.72 347.02 L37.61 347.02 L37.61 347.02 L5.04 347.02 C2.27 347.02 0 345.67 0 344.01 L0 336.48 L0 331.97
+ L0 331.97 Z" class="st5"/>
+ </g>
+ <g id="shape112-282" v:mID="112" v:groupContext="shape" transform="translate(196.65,-80.2991)">
+ <title>Sheet.112</title>
+ <desc>group id</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="27.7691" cy="339.824" width="55.54" height="14.3829"/>
+ <path d="M55.54 332.63 L0 332.63 L0 347.02 L55.54 347.02 L55.54 332.63" class="st3"/>
+ <text x="5.09" y="343.42" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>group id</text> </g>
+ <g id="shape114-286" v:mID="114" v:groupContext="shape" transform="translate(506.433,-128.007)">
+ <title>Sheet.114</title>
+ <desc>-</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="6.63728" cy="336.229" width="13.28" height="21.5726"/>
+ <path d="M13.27 325.44 L0 325.44 L0 347.02 L13.27 347.02 L13.27 325.44" class="st3"/>
+ <text x="3.06" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>-</text> </g>
+ <g id="shape115-290" v:mID="115" v:groupContext="shape" transform="translate(529.004,-128.007)">
+ <title>Sheet.115</title>
+ <desc>Keys separated into</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="97.1729" cy="336.229" width="194.35" height="21.5726"/>
+ <path d="M194.35 325.44 L0 325.44 L0 347.02 L194.35 347.02 L194.35 325.44" class="st3"/>
+ <text x="17.06" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Keys separated into </text> </g>
+ <g id="shape116-294" v:mID="116" v:groupContext="shape" transform="translate(529.004,-106.438)">
+ <title>Sheet.116</title>
+ <desc>groups based on</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="83.1587" cy="336.229" width="166.32" height="21.5726"/>
+ <path d="M166.32 325.44 L0 325.44 L0 347.02 L166.32 347.02 L166.32 325.44" class="st3"/>
+ <text x="15.23" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>groups based on </text> </g>
+ <g id="shape117-298" v:mID="117" v:groupContext="shape" transform="translate(529.004,-84.869)">
+ <title>Sheet.117</title>
+ <desc>some bits from hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="97.731" cy="336.229" width="195.47" height="21.5726"/>
+ <path d="M195.46 325.44 L0 325.44 L0 347.02 L195.46 347.02 L195.46 325.44" class="st3"/>
+ <text x="14.94" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>some bits from hash</text> </g>
+ <g id="shape118-302" v:mID="118" v:groupContext="shape" transform="translate(506.433,-63.2999)">
+ <title>Sheet.118</title>
+ <desc>-</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="6.63728" cy="336.229" width="13.28" height="21.5726"/>
+ <path d="M13.27 325.44 L0 325.44 L0 347.02 L13.27 347.02 L13.27 325.44" class="st3"/>
+ <text x="3.06" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>-</text> </g>
+ <g id="shape119-306" v:mID="119" v:groupContext="shape" transform="translate(529.004,-63.2999)">
+ <title>Sheet.119</title>
+ <desc>Groups contain a</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="84.2539" cy="336.229" width="168.51" height="21.5726"/>
+ <path d="M168.51 325.44 L0 325.44 L0 347.02 L168.51 347.02 L168.51 325.44" class="st3"/>
+ <text x="15.38" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Groups contain a </text> </g>
+ <g id="shape120-310" v:mID="120" v:groupContext="shape" transform="translate(529.004,-41.7308)">
+ <title>Sheet.120</title>
+ <desc>small number of</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="81.4635" cy="336.229" width="162.93" height="21.5726"/>
+ <path d="M162.93 325.44 L0 325.44 L0 347.02 L162.93 347.02 L162.93 325.44" class="st3"/>
+ <text x="15.01" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>small number of </text> </g>
+ <g id="shape121-314" v:mID="121" v:groupContext="shape" transform="translate(529.004,-20.1617)">
+ <title>Sheet.121</title>
+ <desc>keys (<28)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.4481" cy="336.229" width="100.9" height="21.5726"/>
+ <path d="M100.9 325.44 L0 325.44 L0 347.02 L100.9 347.02 L100.9 325.44" class="st3"/>
+ <text x="8.77" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>keys (<28)</text> </g>
+ <g id="shape122-318" v:mID="122" v:groupContext="shape" transform="translate(19.1996,-146.276)">
+ <title>Sheet.122</title>
+ <path d="M0 310.17 C-0 306.1 3.62 302.8 8.07 302.8 L14.46 302.8 L29.68 282.28 L36.14 302.8 L78.65 302.8 C83.11 302.8
+ 86.72 306.1 86.72 310.17 L86.72 310.17 L86.72 321.22 L86.72 339.65 C86.72 343.72 83.11 347.02 78.65 347.02
+ L36.14 347.02 L14.46 347.02 L14.46 347.02 L8.07 347.02 C3.62 347.02 0 343.72 0 339.65 L0 321.22 L0 310.17
+ L0 310.17 Z" class="st5"/>
+ </g>
+ <g id="shape123-320" v:mID="123" v:groupContext="shape" transform="translate(41.9777,-174.053)">
+ <title>Sheet.123</title>
+ <desc>Group</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="22.8289" cy="339.824" width="45.66" height="14.3829"/>
+ <path d="M45.66 332.63 L0 332.63 L0 347.02 L45.66 347.02 L45.66 332.63" class="st3"/>
+ <text x="5.9" y="343.42" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group </text> </g>
+ <g id="shape124-324" v:mID="124" v:groupContext="shape" transform="translate(34.4142,-159.674)">
+ <title>Sheet.124</title>
+ <desc>Identifier</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="31.5173" cy="339.824" width="63.04" height="14.3829"/>
+ <path d="M63.03 332.63 L0 332.63 L0 347.02 L63.03 347.02 L63.03 332.63" class="st3"/>
+ <text x="7.04" y="343.42" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Identifier </text> </g>
+ <g id="shape125-328" v:mID="125" v:groupContext="shape" transform="translate(28.7716,-145.295)">
+ <title>Sheet.125</title>
+ <desc>(simplified)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="36.2165" cy="339.824" width="72.44" height="14.3829"/>
+ <path d="M72.43 332.63 L0 332.63 L0 347.02 L72.43 347.02 L72.43 332.63" class="st3"/>
+ <text x="6.19" y="343.42" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(simplified)</text> </g>
+ <g id="shape127-332" v:mID="127" v:groupContext="shape" transform="translate(517.688,-71.2991)">
+ <title>Sheet.127</title>
+ <desc>Keys separated into groups based on some bits from hash. Grou...</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="112.5" cy="302.139" width="225" height="89.7513"/>
+ <g id="shadow127-333" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st19">
+ <rect x="0" y="257.264" width="225" height="89.7513" class="st20"/>
+ </g>
+ <rect x="0" y="257.264" width="225" height="89.7513" class="st21"/>
+ <text x="4" y="281.09" class="st22" v:langID="1033"><v:paragraph v:indentFirst="-18" v:indentLeft="18" v:bullet="1"/><v:tabList/><tspan
+ class="st23" v:isBullet="true">·</tspan> <tspan class="st24">Keys separated into groups based </tspan><tspan
+ x="22" dy="1.204em" class="st24">on some bits from hash</tspan><tspan class="st24">.<v:newlineChar/></tspan><tspan
+ x="4" dy="1.211em" class="st23" v:isBullet="true">·</tspan> <tspan class="st24">Groups contain a small number of </tspan><tspan
+ x="22" dy="1.204em" class="st24">keys </tspan><tspan class="st24">(</tspan><tspan class="st24"><</tspan><tspan
+ class="st24">28</tspan><tspan class="st24">)</tspan></text> </g>
+ <g id="shape129-349" v:mID="129" v:groupContext="shape" transform="translate(336.326,-26.2991)">
+ <title>Sheet.129</title>
+ <desc>Total # of keys in group so far</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="39.6784" cy="333.515" width="79.36" height="27"/>
+ <rect x="0" y="320.015" width="79.3567" height="27" class="st25"/>
+ <text x="4.5" y="329.92" class="st26" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Total # of keys <tspan
+ x="4.39" dy="1.2em" class="st23">in group so far</tspan></text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i8.svg b/doc/guides/prog_guide/img/efd_i8.svg
new file mode 100644
index 0000000..d0fd463
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i8.svg
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i9.svg Page-2 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="4.98372in" height="2.08442in"
+ viewBox="0 0 358.828 150.078" xml:space="preserve" color-interpolation-filters="sRGB" class="st8">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st4 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st5 {fill:#000000;font-family:Intel Clear;font-size:0.998566em}
+ .st6 {fill:#c00000;font-family:Intel Clear;font-size:0.828804em;font-weight:bold}
+ .st7 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st8 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="4" v:index="2" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-2</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape4-1" v:mID="4" v:groupContext="shape" transform="translate(206.306,-19.0195)">
+ <title>Sheet.4</title>
+ <path d="M0 38.04 L0 150.08 L133.5 150.08 L133.5 38.04 L0 38.04 L0 38.04 Z" class="st1"/>
+ </g>
+ <g id="shape5-3" v:mID="5" v:groupContext="shape" transform="translate(206.306,-19.0195)">
+ <title>Sheet.5</title>
+ <path d="M0 38.04 L133.5 38.04 L133.5 150.08 L0 150.08 L0 38.04" class="st2"/>
+ </g>
+ <g id="shape6-6" v:mID="6" v:groupContext="shape" transform="translate(215.55,-70.7853)">
+ <title>Sheet.6</title>
+ <path d="M0 121.92 C0 118.82 2.54 116.29 5.64 116.29 L110.69 116.29 C113.81 116.29 116.33 118.82 116.33 121.92 L116.33
+ 144.45 C116.33 147.56 113.81 150.08 110.69 150.08 L5.64 150.08 C2.54 150.08 0 147.56 0 144.45 L0 121.92
+ Z" class="st1"/>
+ </g>
+ <g id="shape7-8" v:mID="7" v:groupContext="shape" transform="translate(215.55,-70.7853)">
+ <title>Sheet.7</title>
+ <path d="M0 121.92 C0 118.82 2.54 116.29 5.64 116.29 L110.69 116.29 C113.81 116.29 116.33 118.82 116.33 121.92 L116.33
+ 144.45 C116.33 147.56 113.81 150.08 110.69 150.08 L5.64 150.08 C2.54 150.08 0 147.56 0 144.45 L0 121.92
+ Z" class="st3"/>
+ </g>
+ <g id="shape8-10" v:mID="8" v:groupContext="shape" transform="translate(242.756,-86.1914)">
+ <title>Sheet.8</title>
+ <desc>hash_index</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="36.9951" cy="142.887" width="74" height="14.3829"/>
+ <path d="M73.99 135.7 L0 135.7 L0 150.08 L73.99 150.08 L73.99 135.7" class="st4"/>
+ <text x="6.29" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash_index</text> </g>
+ <g id="shape9-14" v:mID="9" v:groupContext="shape" transform="translate(229.67,-71.812)">
+ <title>Sheet.9</title>
+ <desc>(integer, 16 bits)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="52.0635" cy="142.887" width="104.13" height="14.3829"/>
+ <path d="M104.13 135.7 L0 135.7 L0 150.08 L104.13 150.08 L104.13 135.7" class="st4"/>
+ <text x="8.25" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(integer, 16 bits)</text> </g>
+ <g id="shape10-18" v:mID="10" v:groupContext="shape" transform="translate(215.55,-27.1678)">
+ <title>Sheet.10</title>
+ <path d="M0 121.92 C0 118.82 2.54 116.29 5.64 116.29 L110.69 116.29 C113.81 116.29 116.33 118.82 116.33 121.92 L116.33
+ 144.45 C116.33 147.56 113.81 150.08 110.69 150.08 L5.64 150.08 C2.54 150.08 0 147.56 0 144.45 L0 121.92
+ Z" class="st1"/>
+ </g>
+ <g id="shape11-20" v:mID="11" v:groupContext="shape" transform="translate(215.55,-27.1678)">
+ <title>Sheet.11</title>
+ <path d="M0 121.92 C0 118.82 2.54 116.29 5.64 116.29 L110.69 116.29 C113.81 116.29 116.33 118.82 116.33 121.92 L116.33
+ 144.45 C116.33 147.56 113.81 150.08 110.69 150.08 L5.64 150.08 C2.54 150.08 0 147.56 0 144.45 L0 121.92
+ Z" class="st3"/>
+ </g>
+ <g id="shape12-22" v:mID="12" v:groupContext="shape" transform="translate(237.836,-42.6033)">
+ <title>Sheet.12</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="42.693" cy="142.887" width="85.39" height="14.3829"/>
+ <path d="M85.39 135.7 L0 135.7 L0 150.08 L85.39 150.08 L85.39 135.7" class="st4"/>
+ <text x="7.03" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape13-26" v:mID="13" v:groupContext="shape" transform="translate(251.643,-28.2239)">
+ <title>Sheet.13</title>
+ <desc>(16 bits)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="26.9562" cy="142.887" width="53.92" height="14.3829"/>
+ <path d="M53.91 135.7 L0 135.7 L0 150.08 L53.91 150.08 L53.91 135.7" class="st4"/>
+ <text x="4.98" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(16 bits)</text> </g>
+ <g id="shape14-30" v:mID="14" v:groupContext="shape" transform="translate(213.473,-114.303)">
+ <title>Sheet.14</title>
+ <desc>Group ID: 0x0102</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="47.976" cy="144.109" width="95.96" height="11.9384"/>
+ <path d="M95.95 138.14 L0 138.14 L0 150.08 L95.95 150.08 L95.95 138.14" class="st4"/>
+ <text x="7.47" y="147.09" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group ID: 0x0102</text> </g>
+ <g id="shape15-34" v:mID="15" v:groupContext="shape" transform="translate(19.0195,-99.4242)">
+ <title>Sheet.15</title>
+ <path d="M0 129.31 C0 127.02 1.87 125.15 4.16 125.15 L109.18 125.15 C111.47 125.15 113.33 127.02 113.33 129.31 L113.33
+ 145.93 C113.33 148.22 111.47 150.08 109.18 150.08 L4.16 150.08 C1.87 150.08 0 148.22 0 145.93 L0 129.31
+ Z" class="st1"/>
+ </g>
+ <g id="shape16-36" v:mID="16" v:groupContext="shape" transform="translate(19.0195,-99.4242)">
+ <title>Sheet.16</title>
+ <path d="M0 129.31 C0 127.02 1.87 125.15 4.16 125.15 L109.18 125.15 C111.47 125.15 113.33 127.02 113.33 129.31 L113.33
+ 145.93 C113.33 148.22 111.47 150.08 109.18 150.08 L4.16 150.08 C1.87 150.08 0 148.22 0 145.93 L0 129.31
+ Z" class="st3"/>
+ </g>
+ <g id="shape17-38" v:mID="17" v:groupContext="shape" transform="translate(33.9485,-103.285)">
+ <title>Sheet.17</title>
+ <desc>Key1: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="49.6862" cy="142.887" width="99.38" height="14.3829"/>
+ <path d="M99.37 135.7 L0 135.7 L0 150.08 L99.37 150.08 L99.37 135.7" class="st4"/>
+ <text x="7.94" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1: Value = 0</text> </g>
+ <g id="shape18-42" v:mID="18" v:groupContext="shape" transform="translate(19.0195,-74.6198)">
+ <title>Sheet.18</title>
+ <path d="M0 129.31 C0 127.02 1.87 125.15 4.16 125.15 L109.18 125.15 C111.47 125.15 113.33 127.02 113.33 129.31 L113.33
+ 145.93 C113.33 148.22 111.47 150.08 109.18 150.08 L4.16 150.08 C1.87 150.08 0 148.22 0 145.93 L0 129.31
+ Z" class="st1"/>
+ </g>
+ <g id="shape19-44" v:mID="19" v:groupContext="shape" transform="translate(19.0195,-74.6198)">
+ <title>Sheet.19</title>
+ <path d="M0 129.31 C0 127.02 1.87 125.15 4.16 125.15 L109.18 125.15 C111.47 125.15 113.33 127.02 113.33 129.31 L113.33
+ 145.93 C113.33 148.22 111.47 150.08 109.18 150.08 L4.16 150.08 C1.87 150.08 0 148.22 0 145.93 L0 129.31
+ Z" class="st3"/>
+ </g>
+ <g id="shape20-46" v:mID="20" v:groupContext="shape" transform="translate(33.9485,-78.4626)">
+ <title>Sheet.20</title>
+ <desc>Key3: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="49.6862" cy="142.887" width="99.38" height="14.3829"/>
+ <path d="M99.37 135.7 L0 135.7 L0 150.08 L99.37 150.08 L99.37 135.7" class="st4"/>
+ <text x="7.94" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3: Value = 1</text> </g>
+ <g id="shape21-50" v:mID="21" v:groupContext="shape" transform="translate(19.0195,-49.5757)">
+ <title>Sheet.21</title>
+ <path d="M0 129.21 C0 126.91 1.88 125.03 4.19 125.03 L109.15 125.03 C111.46 125.03 113.33 126.91 113.33 129.21 L113.33
+ 145.91 C113.33 148.21 111.46 150.08 109.15 150.08 L4.19 150.08 C1.88 150.08 0 148.21 0 145.91 L0 129.21
+ Z" class="st1"/>
+ </g>
+ <g id="shape22-52" v:mID="22" v:groupContext="shape" transform="translate(19.0195,-49.5757)">
+ <title>Sheet.22</title>
+ <path d="M0 129.21 C0 126.91 1.88 125.03 4.19 125.03 L109.15 125.03 C111.46 125.03 113.33 126.91 113.33 129.21 L113.33
+ 145.91 C113.33 148.21 111.46 150.08 109.15 150.08 L4.19 150.08 C1.88 150.08 0 148.21 0 145.91 L0 129.21
+ Z" class="st3"/>
+ </g>
+ <g id="shape23-54" v:mID="23" v:groupContext="shape" transform="translate(33.9485,-53.4903)">
+ <title>Sheet.23</title>
+ <desc>Key4: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="49.6862" cy="142.887" width="99.38" height="14.3829"/>
+ <path d="M99.37 135.7 L0 135.7 L0 150.08 L99.37 150.08 L99.37 135.7" class="st4"/>
+ <text x="7.94" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4: Value = 0</text> </g>
+ <g id="shape24-58" v:mID="24" v:groupContext="shape" transform="translate(19.0195,-25.0109)">
+ <title>Sheet.24</title>
+ <path d="M0 129.21 C0 126.91 1.88 125.03 4.19 125.03 L109.15 125.03 C111.46 125.03 113.33 126.91 113.33 129.21 L113.33
+ 145.91 C113.33 148.21 111.46 150.08 109.15 150.08 L4.19 150.08 C1.88 150.08 0 148.21 0 145.91 L0 129.21
+ Z" class="st1"/>
+ </g>
+ <g id="shape25-60" v:mID="25" v:groupContext="shape" transform="translate(19.0195,-25.0109)">
+ <title>Sheet.25</title>
+ <path d="M0 129.21 C0 126.91 1.88 125.03 4.19 125.03 L109.15 125.03 C111.46 125.03 113.33 126.91 113.33 129.21 L113.33
+ 145.91 C113.33 148.21 111.46 150.08 109.15 150.08 L4.19 150.08 C1.88 150.08 0 148.21 0 145.91 L0 129.21
+ Z" class="st3"/>
+ </g>
+ <g id="shape26-62" v:mID="26" v:groupContext="shape" transform="translate(33.9485,-28.927)">
+ <title>Sheet.26</title>
+ <desc>Key7: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="49.6862" cy="142.887" width="99.38" height="14.3829"/>
+ <path d="M99.37 135.7 L0 135.7 L0 150.08 L99.37 150.08 L99.37 135.7" class="st4"/>
+ <text x="7.94" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7: Value = 1</text> </g>
+ <g id="shape27-66" v:mID="27" v:groupContext="shape" transform="translate(141.536,-51.5529)">
+ <title>Sheet.27</title>
+ <path d="M0 115.39 L22.75 115.39 L22.75 103.82 L45.5 126.95 L22.75 150.08 L22.75 138.51 L0 138.51 L0 115.39 Z"
+ class="st7"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i9.svg b/doc/guides/prog_guide/img/efd_i9.svg
new file mode 100644
index 0000000..b2e385d
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i9.svg
@@ -0,0 +1,390 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i10.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="9.8125in" height="3.76365in"
+ viewBox="0 0 706.5 270.983" xml:space="preserve" color-interpolation-filters="sRGB" class="st9">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st4 {fill:#000000;font-family:Arial;font-size:0.998566em}
+ .st5 {fill:#000000;font-family:Arial;font-size:0.918686em}
+ .st6 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st7 {fill:#ffffff;font-family:Arial;font-size:0.998566em}
+ .st8 {fill:#ffffff;font-family:Arial;font-size:1.49785em}
+ .st9 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape68-1" v:mID="68" v:groupContext="shape" transform="translate(196.523,-158.978)">
+ <title>Sheet.68</title>
+ <path d="M0 250.22 C0 247.95 1.89 246.06 4.17 246.06 L317.25 246.06 C319.53 246.06 321.39 247.95 321.39 250.22 L321.39
+ 266.85 C321.39 269.13 319.53 270.98 317.25 270.98 L4.17 270.98 C1.89 270.98 0 269.13 0 266.85 L0 250.22
+ Z" class="st1"/>
+ </g>
+ <g id="shape69-3" v:mID="69" v:groupContext="shape" transform="translate(196.523,-158.978)">
+ <title>Sheet.69</title>
+ <path d="M0 250.22 C0 247.95 1.89 246.06 4.17 246.06 L317.25 246.06 C319.53 246.06 321.39 247.95 321.39 250.22 L321.39
+ 266.85 C321.39 269.13 319.53 270.98 317.25 270.98 L4.17 270.98 C1.89 270.98 0 269.13 0 266.85 L0 250.22
+ Z" class="st2"/>
+ </g>
+ <g id="shape70-5" v:mID="70" v:groupContext="shape" transform="translate(186.139,-162.437)">
+ <title>Sheet.70</title>
+ <desc>(hash(key, seed1) + hash_index *</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="106.671" cy="263.792" width="213.35" height="14.3829"/>
+ <path d="M213.34 256.6 L0 256.6 L0 270.98 L213.34 270.98 L213.34 256.6" class="st3"/>
+ <text x="17.24" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(hash(key, seed1) + hash_index * </text> </g>
+ <g id="shape71-9" v:mID="71" v:groupContext="shape" transform="translate(381.48,-162.845)">
+ <title>Sheet.71</title>
+ <desc>hash(key</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="27.4843" cy="264.367" width="54.97" height="13.2327"/>
+ <path d="M54.97 257.75 L0 257.75 L0 270.98 L54.97 270.98 L54.97 257.75" class="st3"/>
+ <text x="5.12" y="267.67" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash(key</text> </g>
+ <g id="shape72-13" v:mID="72" v:groupContext="shape" transform="translate(424.755,-162.437)">
+ <title>Sheet.72</title>
+ <desc>, seed2)) % 16</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="46.7254" cy="263.792" width="93.46" height="14.3829"/>
+ <path d="M93.45 256.6 L0 256.6 L0 270.98 L93.45 270.98 L93.45 256.6" class="st3"/>
+ <text x="7.76" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>, seed2)) % 16</text> </g>
+ <g id="shape73-17" v:mID="73" v:groupContext="shape" transform="translate(524.094,-148.373)">
+ <title>Sheet.73</title>
+ <path d="M0 236.29 L22.75 236.29 L22.75 224.73 L45.5 247.86 L22.75 270.98 L22.75 259.42 L0 259.42 L0 236.29 Z"
+ class="st6"/>
+ </g>
+ <g id="shape74-19" v:mID="74" v:groupContext="shape" transform="translate(574.148,-217.574)">
+ <title>Sheet.74</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st1"/>
+ </g>
+ <g id="shape75-21" v:mID="75" v:groupContext="shape" transform="translate(574.148,-217.574)">
+ <title>Sheet.75</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st2"/>
+ </g>
+ <g id="shape76-23" v:mID="76" v:groupContext="shape" transform="translate(584.296,-231.499)">
+ <title>Sheet.76</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="40.993" cy="263.792" width="81.99" height="14.3829"/>
+ <path d="M81.99 256.6 L0 256.6 L0 270.98 L81.99 270.98 L81.99 256.6" class="st3"/>
+ <text x="7.01" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape77-27" v:mID="77" v:groupContext="shape" transform="translate(655.369,-231.499)">
+ <title>Sheet.77</title>
+ <desc>bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1076" cy="263.792" width="22.22" height="14.3829"/>
+ <path d="M22.22 256.6 L0 256.6 L0 270.98 L22.22 270.98 L22.22 256.6" class="st3"/>
+ <text x="4.78" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit </text> </g>
+ <g id="shape78-31" v:mID="78" v:groupContext="shape" transform="translate(588.858,-217.12)">
+ <title>Sheet.78</title>
+ <desc>index for key1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="44.8113" cy="263.792" width="89.63" height="14.3829"/>
+ <path d="M89.62 256.6 L0 256.6 L0 270.98 L89.62 270.98 L89.62 256.6" class="st3"/>
+ <text x="7.51" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>index for key1</text> </g>
+ <g id="shape79-35" v:mID="79" v:groupContext="shape" transform="translate(573.548,-178.869)">
+ <title>Sheet.79</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st1"/>
+ </g>
+ <g id="shape80-37" v:mID="80" v:groupContext="shape" transform="translate(573.548,-178.869)">
+ <title>Sheet.80</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st2"/>
+ </g>
+ <g id="shape81-39" v:mID="81" v:groupContext="shape" transform="translate(584.296,-192.768)">
+ <title>Sheet.81</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="40.993" cy="263.792" width="81.99" height="14.3829"/>
+ <path d="M81.99 256.6 L0 256.6 L0 270.98 L81.99 270.98 L81.99 256.6" class="st3"/>
+ <text x="7.01" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape82-43" v:mID="82" v:groupContext="shape" transform="translate(655.369,-192.768)">
+ <title>Sheet.82</title>
+ <desc>bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1076" cy="263.792" width="22.22" height="14.3829"/>
+ <path d="M22.22 256.6 L0 256.6 L0 270.98 L22.22 270.98 L22.22 256.6" class="st3"/>
+ <text x="4.78" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit </text> </g>
+ <g id="shape83-47" v:mID="83" v:groupContext="shape" transform="translate(588.858,-178.388)">
+ <title>Sheet.83</title>
+ <desc>index for key3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="44.8113" cy="263.792" width="89.63" height="14.3829"/>
+ <path d="M89.62 256.6 L0 256.6 L0 270.98 L89.62 270.98 L89.62 256.6" class="st3"/>
+ <text x="7.51" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>index for key3</text> </g>
+ <g id="shape84-51" v:mID="84" v:groupContext="shape" transform="translate(574.148,-139.326)">
+ <title>Sheet.84</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st1"/>
+ </g>
+ <g id="shape85-53" v:mID="85" v:groupContext="shape" transform="translate(574.148,-139.326)">
+ <title>Sheet.85</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st2"/>
+ </g>
+ <g id="shape86-55" v:mID="86" v:groupContext="shape" transform="translate(584.296,-153.227)">
+ <title>Sheet.86</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="40.993" cy="263.792" width="81.99" height="14.3829"/>
+ <path d="M81.99 256.6 L0 256.6 L0 270.98 L81.99 270.98 L81.99 256.6" class="st3"/>
+ <text x="7.01" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape87-59" v:mID="87" v:groupContext="shape" transform="translate(655.369,-153.227)">
+ <title>Sheet.87</title>
+ <desc>bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1076" cy="263.792" width="22.22" height="14.3829"/>
+ <path d="M22.22 256.6 L0 256.6 L0 270.98 L22.22 270.98 L22.22 256.6" class="st3"/>
+ <text x="4.78" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit </text> </g>
+ <g id="shape88-63" v:mID="88" v:groupContext="shape" transform="translate(588.858,-138.848)">
+ <title>Sheet.88</title>
+ <desc>index for key4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="44.8113" cy="263.792" width="89.63" height="14.3829"/>
+ <path d="M89.62 256.6 L0 256.6 L0 270.98 L89.62 270.98 L89.62 256.6" class="st3"/>
+ <text x="7.51" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>index for key4</text> </g>
+ <g id="shape89-67" v:mID="89" v:groupContext="shape" transform="translate(574.148,-100.622)">
+ <title>Sheet.89</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st1"/>
+ </g>
+ <g id="shape90-69" v:mID="90" v:groupContext="shape" transform="translate(574.148,-100.622)">
+ <title>Sheet.90</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st2"/>
+ </g>
+ <g id="shape91-71" v:mID="91" v:groupContext="shape" transform="translate(584.296,-114.496)">
+ <title>Sheet.91</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="40.993" cy="263.792" width="81.99" height="14.3829"/>
+ <path d="M81.99 256.6 L0 256.6 L0 270.98 L81.99 270.98 L81.99 256.6" class="st3"/>
+ <text x="7.01" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape92-75" v:mID="92" v:groupContext="shape" transform="translate(655.369,-114.496)">
+ <title>Sheet.92</title>
+ <desc>bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1076" cy="263.792" width="22.22" height="14.3829"/>
+ <path d="M22.22 256.6 L0 256.6 L0 270.98 L22.22 270.98 L22.22 256.6" class="st3"/>
+ <text x="4.78" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit </text> </g>
+ <g id="shape93-79" v:mID="93" v:groupContext="shape" transform="translate(588.858,-100.117)">
+ <title>Sheet.93</title>
+ <desc>index for key7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="44.8113" cy="263.792" width="89.63" height="14.3829"/>
+ <path d="M89.62 256.6 L0 256.6 L0 270.98 L89.62 270.98 L89.62 256.6" class="st3"/>
+ <text x="7.51" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>index for key7</text> </g>
+ <g id="shape94-83" v:mID="94" v:groupContext="shape" transform="translate(205.227,-191.137)">
+ <title>Sheet.94</title>
+ <path d="M0 217.76 C0 213 3.87 209.14 8.64 209.14 L14.53 209.14 L14.53 209.14 L36.32 209.14 L78.52 209.14 C83.3 209.14
+ 87.16 213 87.16 217.76 L87.16 239.33 L87.16 239.33 L87.16 252.27 L87.16 252.27 C87.16 257.05 83.3 260.9
+ 78.52 260.9 L36.32 260.9 L18.46 270.98 L14.53 260.9 L8.64 260.9 C3.87 260.9 0 257.05 0 252.27 L0 239.33
+ L0 239.33 L0 217.76 Z" class="st6"/>
+ </g>
+ <g id="shape95-85" v:mID="95" v:groupContext="shape" transform="translate(214.98,-225.215)">
+ <title>Sheet.95</title>
+ <desc>CRC32 (32</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="38.2947" cy="263.792" width="76.59" height="14.3829"/>
+ <path d="M76.59 256.6 L0 256.6 L0 270.98 L76.59 270.98 L76.59 256.6" class="st3"/>
+ <text x="8.33" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>CRC32 (32 </text> </g>
+ <g id="shape96-89" v:mID="96" v:groupContext="shape" transform="translate(222.123,-210.835)">
+ <title>Sheet.96</title>
+ <desc>bit output)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="32.5584" cy="263.792" width="65.12" height="14.3829"/>
+ <path d="M65.12 256.6 L0 256.6 L0 270.98 L65.12 270.98 L65.12 256.6" class="st3"/>
+ <text x="5.91" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit output)</text> </g>
+ <g id="shape97-93" v:mID="97" v:groupContext="shape" transform="translate(305.473,-188.366)">
+ <title>Sheet.97</title>
+ <path d="M0 226.84 C0 223.28 2.9 220.39 6.47 220.39 L21.37 220.39 L21.37 220.39 L53.42 220.39 L121.77 220.39 C125.34
+ 220.39 128.22 223.28 128.22 226.84 L128.22 242.97 L128.22 242.97 L128.22 252.65 L128.22 252.65 C128.22 256.21
+ 125.34 259.09 121.77 259.09 L53.42 259.09 L38.73 270.98 L21.37 259.09 L6.47 259.09 C2.9 259.09 0 256.21
+ 0 252.65 L0 242.97 L0 242.97 L0 226.84 Z" class="st6"/>
+ </g>
+ <g id="shape98-95" v:mID="98" v:groupContext="shape" transform="translate(318.48,-217.733)">
+ <title>Sheet.98</title>
+ <desc>Goal: Find a valid</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="57.4478" cy="263.792" width="114.9" height="14.3829"/>
+ <path d="M114.9 256.6 L0 256.6 L0 270.98 L114.9 270.98 L114.9 256.6" class="st3"/>
+ <text x="10.82" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Goal: Find a valid </text> </g>
+ <g id="shape99-99" v:mID="99" v:groupContext="shape" transform="translate(339.077,-203.354)">
+ <title>Sheet.99</title>
+ <desc>hash_index</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="37.1611" cy="263.792" width="74.33" height="14.3829"/>
+ <path d="M74.32 256.6 L0 256.6 L0 270.98 L74.32 270.98 L74.32 256.6" class="st3"/>
+ <text x="6.51" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash_index</text> </g>
+ <g id="shape100-103" v:mID="100" v:groupContext="shape" transform="translate(438.135,-185.939)">
+ <title>Sheet.100</title>
+ <path d="M0 217.36 C0 213.8 2.91 210.89 6.48 210.89 L21.37 210.89 L21.37 210.89 L53.42 210.89 L121.77 210.89 C125.34
+ 210.89 128.22 213.8 128.22 217.36 L128.22 233.48 L128.22 233.48 L128.22 243.15 L128.22 243.15 C128.22 246.72
+ 125.34 249.59 121.77 249.59 L53.42 249.59 L54.75 270.98 L21.37 249.59 L6.48 249.59 C2.91 249.59 0 246.72
+ 0 243.15 L0 233.48 L0 233.48 L0 217.36 Z" class="st6"/>
+ </g>
+ <g id="shape101-105" v:mID="101" v:groupContext="shape" transform="translate(448.763,-224.802)">
+ <title>Sheet.101</title>
+ <desc>Lookup Table has</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.6085" cy="263.792" width="117.22" height="14.3829"/>
+ <path d="M117.22 256.6 L0 256.6 L0 270.98 L117.22 270.98 L117.22 256.6" class="st3"/>
+ <text x="10.98" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Lookup Table has </text> </g>
+ <g id="shape102-109" v:mID="102" v:groupContext="shape" transform="translate(484.549,-210.423)">
+ <title>Sheet.102</title>
+ <desc>16 bits</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="22.2166" cy="263.792" width="44.44" height="14.3829"/>
+ <path d="M44.43 256.6 L0 256.6 L0 270.98 L44.43 270.98 L44.43 256.6" class="st3"/>
+ <text x="4.56" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>16 bits</text> </g>
+ <g id="shape103-113" v:mID="103" v:groupContext="shape" transform="translate(369.583,-90.8555)">
+ <title>Sheet.103</title>
+ <path d="M0 227.76 C0 222.98 3.89 219.1 8.67 219.1 L14.53 219.1 L34.47 205.09 L36.32 219.1 L78.5 219.1 C83.29 219.1 87.16
+ 222.98 87.16 227.76 L87.16 227.76 L87.16 240.73 L87.16 262.34 C87.16 267.12 83.29 270.98 78.5 270.98 L36.32
+ 270.98 L14.53 270.98 L14.53 270.98 L8.67 270.98 C3.89 270.98 0 267.12 0 262.34 L0 240.73 L0 227.76 L0 227.76
+ Z" class="st6"/>
+ </g>
+ <g id="shape104-115" v:mID="104" v:groupContext="shape" transform="translate(383.264,-114.932)">
+ <title>Sheet.104</title>
+ <desc>CRC32 (32</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="38.2947" cy="263.792" width="76.59" height="14.3829"/>
+ <path d="M76.59 256.6 L0 256.6 L0 270.98 L76.59 270.98 L76.59 256.6" class="st3"/>
+ <text x="8.33" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>CRC32 (32 </text> </g>
+ <g id="shape105-119" v:mID="105" v:groupContext="shape" transform="translate(386.505,-100.553)">
+ <title>Sheet.105</title>
+ <desc>bit output)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="32.5584" cy="263.792" width="65.12" height="14.3829"/>
+ <path d="M65.12 256.6 L0 256.6 L0 270.98 L65.12 270.98 L65.12 256.6" class="st3"/>
+ <text x="5.91" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit output)</text> </g>
+ <g id="shape106-123" v:mID="106" v:groupContext="shape" transform="translate(313.397,-18)">
+ <title>Sheet.106</title>
+ <path d="M0 226.35 C0 221.43 4.02 217.42 8.94 217.42 L347.02 217.42 C351.97 217.42 355.96 221.43 355.96 226.35 L355.96
+ 262.06 C355.96 267 351.97 270.98 347.02 270.98 L8.94 270.98 C4.02 270.98 0 267 0 262.06 L0 226.35 Z"
+ class="st6"/>
+ </g>
+ <g id="shape107-125" v:mID="107" v:groupContext="shape" transform="translate(313.98,-41.963)">
+ <title>Sheet.107</title>
+ <desc>Goal is to find a hash_index that produces</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="177.75" cy="260.197" width="355.5" height="21.5726"/>
+ <path d="M355.5 249.41 L0 249.41 L0 270.98 L355.5 270.98 L355.5 249.41" class="st3"/>
+ <text x="9.88" y="265.59" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Goal is to find a hash_index that produces </text> </g>
+ <g id="shape108-129" v:mID="108" v:groupContext="shape" transform="translate(318.48,-20.3939)">
+ <title>Sheet.108</title>
+ <desc>a lookup_table with no contradictions</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="175.5" cy="260.197" width="351" height="21.5726"/>
+ <path d="M351 249.41 L0 249.41 L0 270.98 L351 270.98 L351 249.41" class="st3"/>
+ <text x="28.12" y="265.59" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>a lookup_table with no contradictions</text> </g>
+ <g id="shape109-133" v:mID="109" v:groupContext="shape" transform="translate(18,-196.244)">
+ <title>Sheet.109</title>
+ <path d="M0 250.22 C0 247.92 1.87 246.06 4.16 246.06 L109.18 246.06 C111.47 246.06 113.33 247.92 113.33 250.22 L113.33
+ 266.83 C113.33 269.13 111.47 270.98 109.18 270.98 L4.16 270.98 C1.87 270.98 0 269.13 0 266.83 L0 250.22
+ Z" class="st1"/>
+ </g>
+ <g id="shape110-135" v:mID="110" v:groupContext="shape" transform="translate(29.8201,-196.244)">
+ <title>Sheet.110</title>
+ <path d="M0 250.22 C-0 247.92 1.67 246.06 3.73 246.06 L97.79 246.06 C99.85 246.06 101.51 247.92 101.51 250.22 L101.51
+ 266.83 C101.51 269.13 99.85 270.98 97.79 270.98 L3.73 270.98 C1.67 270.98 0 269.13 0 266.83 L0 250.22 Z"
+ class="st2"/>
+ </g>
+ <g id="shape111-137" v:mID="111" v:groupContext="shape" transform="translate(32.5663,-199.746)">
+ <title>Sheet.111</title>
+ <desc>Key1: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.7562" cy="263.792" width="101.52" height="14.3829"/>
+ <path d="M101.51 256.6 L0 256.6 L0 270.98 L101.51 270.98 L101.51 256.6" class="st3"/>
+ <text x="8.29" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1: Value = 0</text> </g>
+ <g id="shape112-141" v:mID="112" v:groupContext="shape" transform="translate(18,-171.44)">
+ <title>Sheet.112</title>
+ <path d="M0 250.22 C0 247.92 1.87 246.06 4.16 246.06 L109.18 246.06 C111.47 246.06 113.33 247.92 113.33 250.22 L113.33
+ 266.83 C113.33 269.13 111.47 270.98 109.18 270.98 L4.16 270.98 C1.87 270.98 0 269.13 0 266.83 L0 250.22
+ Z" class="st1"/>
+ </g>
+ <g id="shape113-143" v:mID="113" v:groupContext="shape" transform="translate(29.8201,-171.44)">
+ <title>Sheet.113</title>
+ <path d="M0 250.22 C0 247.92 1.67 246.06 3.73 246.06 L97.79 246.06 C99.85 246.06 101.51 247.92 101.51 250.22 L101.51
+ 266.83 C101.51 269.13 99.85 270.98 97.79 270.98 L3.73 270.98 C1.67 270.98 0 269.13 0 266.83 L0 250.22 Z"
+ class="st2"/>
+ </g>
+ <g id="shape114-145" v:mID="114" v:groupContext="shape" transform="translate(32.5663,-174.923)">
+ <title>Sheet.114</title>
+ <desc>Key3: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.7562" cy="263.792" width="101.52" height="14.3829"/>
+ <path d="M101.51 256.6 L0 256.6 L0 270.98 L101.51 270.98 L101.51 256.6" class="st3"/>
+ <text x="8.29" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3: Value = 1</text> </g>
+ <g id="shape115-149" v:mID="115" v:groupContext="shape" transform="translate(18,-146.396)">
+ <title>Sheet.115</title>
+ <path d="M0 250.12 C0 247.81 1.88 245.94 4.19 245.94 L109.15 245.94 C111.46 245.94 113.33 247.81 113.33 250.12 L113.33
+ 266.81 C113.33 269.12 111.46 270.98 109.15 270.98 L4.19 270.98 C1.88 270.98 0 269.12 0 266.81 L0 250.12
+ Z" class="st1"/>
+ </g>
+ <g id="shape116-151" v:mID="116" v:groupContext="shape" transform="translate(29.8201,-146.396)">
+ <title>Sheet.116</title>
+ <path d="M0 250.12 C0 247.81 1.68 245.94 3.75 245.94 L97.77 245.94 C99.84 245.94 101.51 247.81 101.51 250.12 L101.51
+ 266.81 C101.51 269.12 99.84 270.98 97.77 270.98 L3.75 270.98 C1.68 270.98 0 269.12 0 266.81 L0 250.12 Z"
+ class="st2"/>
+ </g>
+ <g id="shape117-153" v:mID="117" v:groupContext="shape" transform="translate(32.5663,-149.951)">
+ <title>Sheet.117</title>
+ <desc>Key4: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.7562" cy="263.792" width="101.52" height="14.3829"/>
+ <path d="M101.51 256.6 L0 256.6 L0 270.98 L101.51 270.98 L101.51 256.6" class="st3"/>
+ <text x="8.29" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4: Value = 0</text> </g>
+ <g id="shape118-157" v:mID="118" v:groupContext="shape" transform="translate(18,-121.831)">
+ <title>Sheet.118</title>
+ <path d="M0 250.12 C0 247.81 1.88 245.94 4.19 245.94 L109.15 245.94 C111.46 245.94 113.33 247.81 113.33 250.12 L113.33
+ 266.81 C113.33 269.12 111.46 270.98 109.15 270.98 L4.19 270.98 C1.88 270.98 0 269.12 0 266.81 L0 250.12
+ Z" class="st1"/>
+ </g>
+ <g id="shape119-159" v:mID="119" v:groupContext="shape" transform="translate(29.8201,-121.831)">
+ <title>Sheet.119</title>
+ <path d="M0 250.12 C0 247.81 1.68 245.94 3.75 245.94 L97.77 245.94 C99.84 245.94 101.51 247.81 101.51 250.12 L101.51
+ 266.81 C101.51 269.12 99.84 270.98 97.77 270.98 L3.75 270.98 C1.68 270.98 0 269.12 0 266.81 L0 250.12 Z"
+ class="st2"/>
+ </g>
+ <g id="shape120-161" v:mID="120" v:groupContext="shape" transform="translate(32.5663,-125.388)">
+ <title>Sheet.120</title>
+ <desc>Key7: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.7562" cy="263.792" width="101.52" height="14.3829"/>
+ <path d="M101.51 256.6 L0 256.6 L0 270.98 L101.51 270.98 L101.51 256.6" class="st3"/>
+ <text x="8.29" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7: Value = 1</text> </g>
+ <g id="shape121-165" v:mID="121" v:groupContext="shape" transform="translate(140.517,-148.373)">
+ <title>Sheet.121</title>
+ <path d="M0 236.29 L22.75 236.29 L22.75 224.73 L45.5 247.86 L22.75 270.98 L22.75 259.42 L0 259.42 L0 236.29 Z"
+ class="st6"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/index.rst b/doc/guides/prog_guide/index.rst
index ed7f770..7f825cb 100644
--- a/doc/guides/prog_guide/index.rst
+++ b/doc/guides/prog_guide/index.rst
@@ -47,6 +47,7 @@ Programmer's Guide
link_bonding_poll_mode_drv_lib
timer_lib
hash_lib
+ efd_lib
lpm_lib
lpm6_lib
packet_distrib_lib
@@ -167,6 +168,28 @@ Programmer's Guide
:numref:`figure_figure39` :ref:`figure_figure39`
+:numref:`figure_efd1` :ref:`figure_efd1`
+
+:numref:`figure_efd2` :ref:`figure_efd2`
+
+:numref:`figure_efd3` :ref:`figure_efd3`
+
+:numref:`figure_efd4` :ref:`figure_efd4`
+
+:numref:`figure_efd5` :ref:`figure_efd5`
+
+:numref:`figure_efd6` :ref:`figure_efd6`
+
+:numref:`figure_efd7` :ref:`figure_efd7`
+
+:numref:`figure_efd8` :ref:`figure_efd8`
+
+:numref:`figure_efd9` :ref:`figure_efd9`
+
+:numref:`figure_efd10` :ref:`figure_efd10`
+
+:numref:`figure_efd11` :ref:`figure_efd11`
+
**Tables**
diff --git a/doc/guides/rel_notes/release_17_02.rst b/doc/guides/rel_notes/release_17_02.rst
index 5023038..4c94513 100644
--- a/doc/guides/rel_notes/release_17_02.rst
+++ b/doc/guides/rel_notes/release_17_02.rst
@@ -64,6 +64,9 @@ New Features
is much smaller than a hash-based flow table and therefore, it can better fit for
CPU cache, being able to scale to millions of flow keys.
+ See the :ref:`Elastic Flow Distributor Library <Efd_Library>` documentation in
+ the Programmers Guide document, for more information.
+
Resolved Issues
---------------
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v3 5/5] doc: add flow distributor guide
2017-01-12 22:15 ` [dpdk-dev] [PATCH v3 " Pablo de Lara
` (3 preceding siblings ...)
2017-01-12 22:15 ` [dpdk-dev] [PATCH v3 4/5] doc: add EFD library section in Programmers guide Pablo de Lara
@ 2017-01-12 22:16 ` Pablo de Lara
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 0/5] Elastic Flow Distributor Pablo de Lara
5 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-12 22:16 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Sameh Gobriel
Signed-off-by: Sameh Gobriel <sameh.gobriel@intel.com>
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 1 +
doc/guides/sample_app_ug/flow_distributor.rst | 494 ++++++++
doc/guides/sample_app_ug/img/flow_distributor.svg | 1254 +++++++++++++++++++++
doc/guides/sample_app_ug/index.rst | 1 +
4 files changed, 1750 insertions(+)
create mode 100644 doc/guides/sample_app_ug/flow_distributor.rst
create mode 100644 doc/guides/sample_app_ug/img/flow_distributor.svg
diff --git a/MAINTAINERS b/MAINTAINERS
index 66e9466..0d3b247 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -535,6 +535,7 @@ F: lib/librte_efd/
F: doc/guides/prog_guide/efd_lib.rst
F: app/test/test_efd*
F: examples/flow_distributor/
+F: doc/guides/sample_app_ug/flow_distributor.rst
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
diff --git a/doc/guides/sample_app_ug/flow_distributor.rst b/doc/guides/sample_app_ug/flow_distributor.rst
new file mode 100644
index 0000000..a8cd5f4
--- /dev/null
+++ b/doc/guides/sample_app_ug/flow_distributor.rst
@@ -0,0 +1,494 @@
+.. BSD LICENSE
+ Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of Intel Corporation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Flow Distributor Sample Application
+===================================
+
+This sample application demonstrates the use of EFD library as a flow-level
+load balancer, for more information about the EFD Library please refer to the
+DPDK programmer's guide.
+
+This sample application is a variant of the
+:ref:`client-server sample application <multi_process_app>`
+where a specific target node is specified for every and each flow
+(not in a round-robin fashion as the original load balancing sample application).
+
+Overview
+--------
+
+The architecture of the EFD flow-based load balancer sample application is
+presented in the following figure.
+
+.. _figure_efd_sample_app_overview:
+
+.. figure:: img/flow_distributor.*
+
+ Using EFD as a Flow-Level Load Balancer
+
+As shown in :numref:`_figure_efd_sample_app_overview`,
+the sample application consists of a front-end node (distributor)
+using the EFD library to create a load-balancing table for flows,
+for each flow a target backend worker node is specified. The EFD table does not
+store the flow key (unlike a regular hash table), and hence, it can
+individually load-balance millions of flows (number of targets * maximum number
+of flows fit in a flow table per target) while still fitting in CPU cache.
+
+It should be noted that although they are referred to as nodes, the frontend
+distributor and worker nodes are processes running on the same platform.
+
+Front-end Distributor
+~~~~~~~~~~~~~~~~~~~~~
+
+Upon initializing, the frontend distributor node (process) creates a flow
+distributor table (based on the EFD library) which is populated with flow
+information and its intended target node.
+
+The sample application assigns a specific target node_id (process) for each of
+the IP destination addresses as follows:
+
+.. code-block:: c
+
+ node_id = i % num_nodes; /* Target node id is generated */
+ ip_dst = rte_cpu_to_be_32(i); /* Specific ip destination address is
+ assigned to this target node */
+
+then the pair of <key,target> is inserted into the flow distribution table.
+
+The main loop of the the distributor node receives a burst of packets, then for
+each packet, a flow key (IP destination address) is extracted. The flow
+distributor table is looked up and the target node id is returned. Packets are
+then enqueued to the specified target node id.
+
+It should be noted that flow distributor table is not a membership test table.
+I.e. if the key has already been inserted the target node id will be correct,
+but for new keys the flow distributor table will return a value (which can be
+valid).
+
+Backend Worker Nodes
+~~~~~~~~~~~~~~~~~~~~
+
+Upon initializing, the worker node (process) creates a flow table (a regular
+hash table that stores the key default size 1M flows) which is populated with
+only the flow information that is serviced at this node. This flow key is
+essential to point out new keys that have not been inserted before.
+
+The worker node's main loop is simply receiving packets then doing a hash table
+lookup. If a match occurs then statistics are updated for flows serviced by
+this node. If no match is found in the local hash table then this indicates
+that this is a new flow, which is dropped.
+
+
+Compiling the Application
+-------------------------
+
+The sequence of steps used to build the application is:
+
+#. Export the required environment variables:
+
+ .. code-block:: console
+
+ export RTE_SDK=/path/to/rte_sdk
+ export RTE_TARGET=x86_64-native-linuxapp-gcc
+
+#. Build the application executable file:
+
+ .. code-block:: console
+
+ cd ${RTE_SDK}/examples/flow_distributor/
+ make
+
+ For more details on how to build the DPDK libraries and sample
+ applications,
+ please refer to the *DPDK Getting Started Guide.*
+
+
+Running the Application
+-----------------------
+
+The application has two binaries to be run: the front-end distributor
+and the back-end node.
+
+The frontend distributor (distributor) has the following command line options::
+
+ ./distributor [EAL options] -- -p PORTMASK -n NUM_NODES -f NUM_FLOWS
+
+Where,
+
+* ``-p PORTMASK:`` Hexadecimal bitmask of ports to configure
+* ``-n NUM_NODES:`` Number of back-end nodes that will be used
+* ``-f NUM_FLOWS:`` Number of flows to be added in the EFD table (1 million, by default)
+
+The back-end node (node) has the following command line options::
+
+ ./node [EAL options] -- -n NODE_ID
+
+Where,
+
+* ``-n NODE_ID:`` Node ID, which cannot be equal or higher than NUM_MODES
+
+
+First, the distributor app must be launched, with the number of nodes that will be run.
+Once it has been started, the node instances can be run, with different NODE_ID.
+These instances have to be run as secondary processes, with ``--proc-type=secondary``
+in the EAL options, which will attach to the primary process memory, and therefore,
+they can access the queues created by the primary process to distribute packets.
+
+To successfully run the application, the command line used to start the
+application has to be in sync with the traffic flows configured on the traffic
+generator side.
+
+For examples of application command lines and traffic generator flows, please
+refer to the DPDK Test Report. For more details on how to set up and run the
+sample applications provided with DPDK package, please refer to the
+:ref:`DPDK Getting Started Guide for Linux <linux_gsg>` and
+:ref:`DPDK Getting Started Guide for FreeBSD <freebsd_gsg>`.
+
+
+Explanation
+-----------
+
+As described in previous sections, there are two processes in this example.
+
+The first process, the front-end distributor, creates and populates the EFD table,
+which is used to distribute packets to nodes, which the number of flows
+specified in the command line (1 million, by default).
+
+
+.. code-block:: c
+
+ static void
+ create_flow_distributor_table(void)
+ {
+ uint8_t socket_id = rte_socket_id();
+
+ /* create table */
+ efd_table = rte_efd_create("flow table", num_flows * 2, sizeof(uint32_t),
+ 1 << socket_id, socket_id);
+
+ if (efd_table == NULL)
+ rte_exit(EXIT_FAILURE, "Problem creating the flow table\n");
+ }
+
+ static void
+ populate_flow_distributor_table(void)
+ {
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint8_t socket_id = rte_socket_id();
+ uint64_t node_id;
+
+ /* Add flows in table */
+ for (i = 0; i < num_flows; i++) {
+ node_id = i % num_nodes;
+
+ ip_dst = rte_cpu_to_be_32(i);
+ ret = rte_efd_update(efd_table, socket_id,
+ (void *)&ip_dst, (efd_value_t)node_id);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u in "
+ "flow distributor table\n", i);
+ }
+
+ printf("EFD table: Adding 0x%x keys\n", num_flows);
+ }
+
+After initialization, packets are received from the enabled ports, and the IPv4
+address from the packets is used as a key to look up in the EFD table,
+which tells the node where the packet has to be distributed.
+
+.. code-block:: c
+
+ static void
+ process_packets(uint32_t port_num __rte_unused, struct rte_mbuf *pkts[],
+ uint16_t rx_count, unsigned int socket_id)
+ {
+ uint16_t i;
+ uint8_t node;
+ efd_value_t data[EFD_BURST_MAX];
+ const void *key_ptrs[EFD_BURST_MAX];
+
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[EFD_BURST_MAX];
+
+ for (i = 0; i < rx_count; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(pkts[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = (void *)&ipv4_dst_ip[i];
+ }
+
+ rte_efd_lookup_bulk(efd_table, socket_id, rx_count,
+ (const void **) key_ptrs, data);
+ for (i = 0; i < rx_count; i++) {
+ node = (uint8_t) ((uintptr_t)data[i]);
+
+ if (node >= num_nodes) {
+ /*
+ * Node is out of range, which means that
+ * flow has not been inserted
+ */
+ flow_dist_stats.drop++;
+ rte_pktmbuf_free(pkts[i]);
+ } else {
+ flow_dist_stats.distributed++;
+ enqueue_rx_packet(node, pkts[i]);
+ }
+ }
+
+ for (i = 0; i < num_nodes; i++)
+ flush_rx_queue(i);
+ }
+
+The burst of packets received is enqueued in temporary buffers (per node),
+and enqueued in the shared ring between the distributor and the node.
+After this, a new burst of packets is received and this process is
+repeated infinitely.
+
+.. code-block:: c
+
+ static void
+ flush_rx_queue(uint16_t node)
+ {
+ uint16_t j;
+ struct node *cl;
+
+ if (cl_rx_buf[node].count == 0)
+ return;
+
+ cl = &nodes[node];
+ if (rte_ring_enqueue_bulk(cl->rx_q, (void **)cl_rx_buf[node].buffer,
+ cl_rx_buf[node].count) != 0){
+ for (j = 0; j < cl_rx_buf[node].count; j++)
+ rte_pktmbuf_free(cl_rx_buf[node].buffer[j]);
+ cl->stats.rx_drop += cl_rx_buf[node].count;
+ } else
+ cl->stats.rx += cl_rx_buf[node].count;
+
+ cl_rx_buf[node].count = 0;
+ }
+
+The second process, the back-end node, receives the packets from the shared
+ring with the distributor and send them out, if they belong to the node.
+
+At initialization, it attaches to the distributor process memory, to have
+access to the shared ring, parameters and statistics.
+
+.. code-block:: c
+
+ rx_ring = rte_ring_lookup(get_rx_queue_name(node_id));
+ if (rx_ring == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get RX ring - "
+ "is distributor process running?\n");
+
+ mp = rte_mempool_lookup(PKTMBUF_POOL_NAME);
+ if (mp == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get mempool for mbufs\n");
+
+ mz = rte_memzone_lookup(MZ_SHARED_INFO);
+ if (mz == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get port info structure\n");
+ info = mz->addr;
+ tx_stats = &(info->tx_stats[node_id]);
+ filter_stats = &(info->filter_stats[node_id]);
+
+Then, the hash table that contains the flows that will be handled
+by the node is created and populated.
+
+.. code-block:: c
+
+ static struct rte_hash *
+ create_hash_table(const struct shared_info *info)
+ {
+ uint32_t num_flows_node = info->num_flows / info->num_nodes;
+ char name[RTE_HASH_NAMESIZE];
+ struct rte_hash *h;
+
+ /* create table */
+ struct rte_hash_parameters hash_params = {
+ .entries = num_flows_node * 2, /* table load = 50% */
+ .key_len = sizeof(uint32_t), /* Store IPv4 dest IP address */
+ .socket_id = rte_socket_id(),
+ .hash_func_init_val = 0,
+ };
+
+ snprintf(name, sizeof(name), "hash_table_%d", node_id);
+ hash_params.name = name;
+ h = rte_hash_create(&hash_params);
+
+ if (h == NULL)
+ rte_exit(EXIT_FAILURE,
+ "Problem creating the hash table for node %d\n",
+ node_id);
+ return h;
+ }
+
+ static void
+ populate_hash_table(const struct rte_hash *h, const struct shared_info *info)
+ {
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint32_t num_flows_node = 0;
+ uint64_t target_node;
+
+ /* Add flows in table */
+ for (i = 0; i < info->num_flows; i++) {
+ target_node = i % info->num_nodes;
+ if (target_node != node_id)
+ continue;
+
+ ip_dst = rte_cpu_to_be_32(i);
+
+ ret = rte_hash_add_key(h, (void *) &ip_dst);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u "
+ "in hash table\n", i);
+ else
+ num_flows_node++;
+
+ }
+
+ printf("Hash table: Adding 0x%x keys\n", num_flows_node);
+ }
+
+After initialization, packets are dequeued from the shared ring
+(from the distributor) and, like in the distributor process,
+the IPv4 address from the packets is used as a key to look up in the hash table.
+If there is a hit, packet is stored in a buffer, to be eventually transmitted
+in one of the enabled ports. If key is not there, packet is dropped, since the
+flow is not handled by the node.
+
+.. code-block:: c
+
+ static inline void
+ handle_packets(struct rte_hash *h, struct rte_mbuf **bufs, uint16_t num_packets)
+ {
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[PKT_READ_SIZE];
+ const void *key_ptrs[PKT_READ_SIZE];
+ unsigned int i;
+ int32_t positions[PKT_READ_SIZE] = {0};
+
+ for (i = 0; i < num_packets; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(bufs[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = &ipv4_dst_ip[i];
+ }
+ /* Check if packets belongs to any flows handled by this node */
+ rte_hash_lookup_bulk(h, key_ptrs, num_packets, positions);
+
+ for (i = 0; i < num_packets; i++) {
+ if (likely(positions[i] >= 0)) {
+ filter_stats->passed++;
+ transmit_packet(bufs[i]);
+ } else {
+ filter_stats->drop++;
+ /* Drop packet, as flow is not handled by this node */
+ rte_pktmbuf_free(bufs[i]);
+ }
+ }
+ }
+
+Finally, note that both processes updates statistics, such as transmitted, received
+and dropped packets, which are shown and refreshed by the distributor app.
+
+.. code-block:: c
+
+ static void
+ do_stats_display(void)
+ {
+ unsigned int i, j;
+ const char clr[] = {27, '[', '2', 'J', '\0'};
+ const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
+ uint64_t port_tx[RTE_MAX_ETHPORTS], port_tx_drop[RTE_MAX_ETHPORTS];
+ uint64_t node_tx[MAX_NODES], node_tx_drop[MAX_NODES];
+
+ /* to get TX stats, we need to do some summing calculations */
+ memset(port_tx, 0, sizeof(port_tx));
+ memset(port_tx_drop, 0, sizeof(port_tx_drop));
+ memset(node_tx, 0, sizeof(node_tx));
+ memset(node_tx_drop, 0, sizeof(node_tx_drop));
+
+ for (i = 0; i < num_nodes; i++) {
+ const struct tx_stats *tx = &info->tx_stats[i];
+
+ for (j = 0; j < info->num_ports; j++) {
+ const uint64_t tx_val = tx->tx[info->id[j]];
+ const uint64_t drop_val = tx->tx_drop[info->id[j]];
+
+ port_tx[j] += tx_val;
+ port_tx_drop[j] += drop_val;
+ node_tx[i] += tx_val;
+ node_tx_drop[i] += drop_val;
+ }
+ }
+
+ /* Clear screen and move to top left */
+ printf("%s%s", clr, topLeft);
+
+ printf("PORTS\n");
+ printf("-----\n");
+ for (i = 0; i < info->num_ports; i++)
+ printf("Port %u: '%s'\t", (unsigned int)info->id[i],
+ get_printable_mac_addr(info->id[i]));
+ printf("\n\n");
+ for (i = 0; i < info->num_ports; i++) {
+ printf("Port %u - rx: %9"PRIu64"\t"
+ "tx: %9"PRIu64"\n",
+ (unsigned int)info->id[i], info->rx_stats.rx[i],
+ port_tx[i]);
+ }
+
+ printf("\nFLOW DISTRIBUTOR\n");
+ printf("-----\n");
+ printf("distributed: %9"PRIu64", drop: %9"PRIu64"\n",
+ flow_dist_stats.distributed, flow_dist_stats.drop);
+
+ printf("\nNODES\n");
+ printf("-------\n");
+ for (i = 0; i < num_nodes; i++) {
+ const unsigned long long rx = nodes[i].stats.rx;
+ const unsigned long long rx_drop = nodes[i].stats.rx_drop;
+ const struct filter_stats *filter = &info->filter_stats[i];
+
+ printf("Node %2u - rx: %9llu, rx_drop: %9llu\n"
+ " tx: %9"PRIu64", tx_drop: %9"PRIu64"\n"
+ " filter_passed: %9"PRIu64", "
+ "filter_drop: %9"PRIu64"\n",
+ i, rx, rx_drop, node_tx[i], node_tx_drop[i],
+ filter->passed, filter->drop);
+ }
+
+ printf("\n");
+ }
diff --git a/doc/guides/sample_app_ug/img/flow_distributor.svg b/doc/guides/sample_app_ug/img/flow_distributor.svg
new file mode 100644
index 0000000..9aee30b
--- /dev/null
+++ b/doc/guides/sample_app_ug/img/flow_distributor.svg
@@ -0,0 +1,1254 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i6.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="8.2496in" height="5.89673in"
+ viewBox="0 0 593.971 424.565" xml:space="preserve" color-interpolation-filters="sRGB" class="st27">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st5 {fill:#feffff;font-family:Calibri;font-size:0.75em}
+ .st6 {fill:none;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st7 {fill:none;stroke:#2e75b5;stroke-width:2.25}
+ .st8 {fill:#305497;stroke:#2e75b5;stroke-width:1}
+ .st9 {fill:#feffff;font-family:Calibri;font-size:0.833336em;font-weight:bold}
+ .st10 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2)}
+ .st11 {fill:#5b9bd5}
+ .st12 {stroke:#c7c8c8;stroke-width:0.25}
+ .st13 {fill:#acccea;stroke:#c7c8c8;stroke-width:0.25}
+ .st14 {fill:#feffff;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st15 {fill:#ed7d31;stroke:#c7c8c8;stroke-width:0.25}
+ .st16 {fill:#deebf6;stroke:#c7c8c8;stroke-width:0.25}
+ .st17 {marker-end:url(#mrkr5-212);stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st18 {fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st19 {fill:none;stroke:#2e75b5;stroke-width:1}
+ .st20 {fill:#5b9bd5;font-family:Calibri;font-size:1.00001em}
+ .st21 {fill:none;stroke:none;stroke-width:0.25}
+ .st22 {font-size:1em}
+ .st23 {fill:#ffffff}
+ .st24 {stroke:#5b9bd5;stroke-dasharray:1.5,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st25 {marker-end:url(#mrkr5-444);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st26 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st27 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-212" class="st18" v:arrowType="5" v:arrowSize="2" v:setback="5.8" refX="-5.8" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ <marker id="mrkr5-444" class="st26" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(319.501,-335.688)">
+ <title>Rectangle.58</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow3-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape4-7" v:mID="4" v:groupContext="shape" transform="translate(353.251,-335.688)">
+ <title>Rectangle.59</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow4-8" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape5-13" v:mID="5" v:groupContext="shape" transform="translate(400.501,-335.688)">
+ <title>Rectangle.60</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow5-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape6-19" v:mID="6" v:groupContext="shape" transform="translate(434.251,-335.688)">
+ <title>Rectangle.61</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow6-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape7-25" v:mID="7" v:groupContext="shape" transform="translate(481.501,-335.688)">
+ <title>Rectangle.62</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow7-26" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape8-30" v:mID="8" v:groupContext="shape" transform="translate(515.251,-335.688)">
+ <title>Rectangle.63</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow8-31" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape9-35" v:mID="9" v:groupContext="shape" transform="translate(319.501,-313.188)">
+ <title>Rectangle.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow9-36" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape10-40" v:mID="10" v:groupContext="shape" transform="translate(353.251,-313.188)">
+ <title>Rectangle.65</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow10-41" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape11-45" v:mID="11" v:groupContext="shape" transform="translate(400.501,-313.188)">
+ <title>Rectangle.66</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow11-46" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape12-50" v:mID="12" v:groupContext="shape" transform="translate(434.251,-313.188)">
+ <title>Rectangle.67</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow12-51" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape13-55" v:mID="13" v:groupContext="shape" transform="translate(481.501,-313.188)">
+ <title>Rectangle.68</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow13-56" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape14-60" v:mID="14" v:groupContext="shape" transform="translate(515.251,-313.188)">
+ <title>Rectangle.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow14-61" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape15-65" v:mID="15" v:groupContext="shape" transform="translate(319.501,-277.188)">
+ <title>Rectangle.70</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow15-66" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape16-71" v:mID="16" v:groupContext="shape" transform="translate(353.251,-277.188)">
+ <title>Rectangle.71</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow16-72" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape17-77" v:mID="17" v:groupContext="shape" transform="translate(400.501,-277.188)">
+ <title>Rectangle.72</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow17-78" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape18-83" v:mID="18" v:groupContext="shape" transform="translate(434.251,-277.188)">
+ <title>Rectangle.73</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow18-84" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape19-89" v:mID="19" v:groupContext="shape" transform="translate(481.501,-277.188)">
+ <title>Rectangle.74</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow19-90" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape20-95" v:mID="20" v:groupContext="shape" transform="translate(515.251,-277.188)">
+ <title>Rectangle.75</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow20-96" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape21-101" v:mID="21" v:groupContext="shape" transform="translate(319.501,-240.687)">
+ <title>Rectangle.76</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow21-102" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape22-106" v:mID="22" v:groupContext="shape" transform="translate(353.251,-240.687)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow22-107" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape23-111" v:mID="23" v:groupContext="shape" transform="translate(400.501,-240.687)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow23-112" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape24-116" v:mID="24" v:groupContext="shape" transform="translate(434.251,-240.687)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow24-117" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape25-121" v:mID="25" v:groupContext="shape" transform="translate(481.501,-240.687)">
+ <title>Rectangle.80</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow25-122" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape26-127" v:mID="26" v:groupContext="shape" transform="translate(515.251,-240.687)">
+ <title>Rectangle.81</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow26-128" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape27-133" v:mID="27" v:groupContext="shape" transform="translate(317.251,-231.687)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow27-134" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st6"/>
+ </g>
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st7"/>
+ </g>
+ <g id="shape28-138" v:mID="28" v:groupContext="shape" transform="translate(328.501,-362.688)">
+ <title>Sheet.28</title>
+ <desc>Local Table for N Specific Flows Serviced at Node 1</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="110.423" cy="418.94" width="220.85" height="11.25"/>
+ <rect x="0" y="413.315" width="220.846" height="11.25" class="st8"/>
+ <text x="5.77" y="421.94" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Local Table for N Specific Flows Serviced at Node 1</text> </g>
+ <g id="group34-141" transform="translate(66.0294,-165.569)" v:mID="34" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Device)"/>
+ <v:cp v:nameU="SubShapeType" v:lbl="SubShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Load balancer)"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Load balancer</title>
+ <g id="shape35-142" v:mID="35" v:groupContext="shape" transform="translate(0,-7.33146)">
+ <title>Sheet.35</title>
+ <g id="shadow35-143" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23 Z" class="st10"/>
+ <path d="M0 377.86 L72 377.86" class="st6"/>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23" class="st6"/>
+ </g>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23 Z" class="st11"/>
+ <path d="M0 377.86 L72 377.86" class="st12"/>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23" class="st12"/>
+ </g>
+ <g id="shape36-152" v:mID="36" v:groupContext="shape" transform="translate(8.03054,-12.9324)">
+ <title>Sheet.36</title>
+ <g id="shadow36-153" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M45.34 421.81 L41.2 422.66 L44.12 424.56 L49.68 423.16 L48.75 417.51 L45.8 415.59 L46.69 419.68 L36.97
+ 413.34 L35.6 415.45 L45.34 421.81 ZM50.83 405.36 L39.2 405.36 L39.2 407.88 L50.8 407.88 L47.82 410.83
+ L51.34 410.83 L55.21 406.61 L51.32 402.39 L47.83 402.39 L50.83 405.36 ZM46.49 392.01 L36.75 398.37
+ L38.13 400.48 L47.84 394.14 L46.96 398.23 L49.91 396.31 L50.84 390.66 L45.28 389.26 L42.36 391.16
+ L46.49 392.01 ZM27.71 397.16 C22.66 397.16 18.58 401.25 18.58 406.29 C18.58 411.33 22.66 415.42
+ 27.71 415.42 C32.75 415.42 36.84 411.33 36.84 406.29 C36.84 401.25 32.75 397.16 27.71 397.16 ZM27.71
+ 400.04 C31.15 400.04 33.96 402.84 33.96 406.29 C33.96 409.74 31.15 412.54 27.71 412.54 C24.26 412.54
+ 21.46 409.74 21.46 406.29 C21.46 402.84 24.26 400.04 27.71 400.04 ZM11.64 405.04 L0 405.04 L0 407.56
+ L11.6 407.56 L8.62 410.51 L12.14 410.51 L16.01 406.29 L12.12 402.07 L8.64 402.07 L11.64 405.04 Z"
+ class="st2"/>
+ </g>
+ <path d="M45.34 421.81 L41.2 422.66 L44.12 424.56 L49.68 423.16 L48.75 417.51 L45.8 415.59 L46.69 419.68 L36.97 413.34
+ L35.6 415.45 L45.34 421.81 ZM50.83 405.36 L39.2 405.36 L39.2 407.88 L50.8 407.88 L47.82 410.83 L51.34
+ 410.83 L55.21 406.61 L51.32 402.39 L47.83 402.39 L50.83 405.36 ZM46.49 392.01 L36.75 398.37 L38.13 400.48
+ L47.84 394.14 L46.96 398.23 L49.91 396.31 L50.84 390.66 L45.28 389.26 L42.36 391.16 L46.49 392.01 ZM27.71
+ 397.16 C22.66 397.16 18.58 401.25 18.58 406.29 C18.58 411.33 22.66 415.42 27.71 415.42 C32.75 415.42
+ 36.84 411.33 36.84 406.29 C36.84 401.25 32.75 397.16 27.71 397.16 ZM27.71 400.04 C31.15 400.04 33.96
+ 402.84 33.96 406.29 C33.96 409.74 31.15 412.54 27.71 412.54 C24.26 412.54 21.46 409.74 21.46 406.29
+ C21.46 402.84 24.26 400.04 27.71 400.04 ZM11.64 405.04 L0 405.04 L0 407.56 L11.6 407.56 L8.62 410.51
+ L12.14 410.51 L16.01 406.29 L12.12 402.07 L8.64 402.07 L11.64 405.04 Z" class="st13"/>
+ </g>
+ </g>
+ <g id="shape37-157" v:mID="37" v:groupContext="shape" transform="translate(21.0294,-45.4375)">
+ <title>Rectangle</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow37-158" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="336.433" width="135" height="88.1315" class="st2"/>
+ </g>
+ <rect x="0" y="336.433" width="135" height="88.1315" class="st3"/>
+ </g>
+ <g id="shape38-162" v:mID="38" v:groupContext="shape" transform="translate(34.693,-126.438)">
+ <title>Sheet.38</title>
+ <desc>EFD Table</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="49.3364" cy="415.565" width="98.68" height="18"/>
+ <rect x="0" y="406.565" width="98.6728" height="18" class="st8"/>
+ <text x="24.87" y="419.17" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>EFD Table</text> </g>
+ <g id="shape39-165" v:mID="39" v:groupContext="shape" transform="translate(30.0294,-99.4375)">
+ <title>Rectangle.39</title>
+ <desc>Group_id</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="26.9182" cy="415.565" width="53.84" height="18"/>
+ <g id="shadow39-166" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st15"/>
+ <text x="7.87" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group_id</text> </g>
+ <g id="shape40-171" v:mID="40" v:groupContext="shape" transform="translate(93.193,-99.4375)">
+ <title>Rectangle.40</title>
+ <desc>Hash index</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="26.9182" cy="415.565" width="53.84" height="18"/>
+ <g id="shadow40-172" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st15"/>
+ <text x="4.64" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Hash index</text> </g>
+ <g id="shape41-177" v:mID="41" v:groupContext="shape" transform="translate(30.193,-82.4275)">
+ <title>Rectangle.41</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow41-178" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape42-182" v:mID="42" v:groupContext="shape" transform="translate(30.193,-66.8125)">
+ <title>Rectangle.42</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow42-183" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape43-187" v:mID="43" v:groupContext="shape" transform="translate(30.1112,-52.1875)">
+ <title>Rectangle.43</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow43-188" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape44-192" v:mID="44" v:groupContext="shape" transform="translate(93.0294,-81.4375)">
+ <title>Rectangle.44</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow44-193" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape45-197" v:mID="45" v:groupContext="shape" transform="translate(93.193,-66.8125)">
+ <title>Rectangle.45</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow45-198" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape46-202" v:mID="46" v:groupContext="shape" transform="translate(93.193,-52.1875)">
+ <title>Rectangle.46</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow46-203" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape47-207" v:mID="47" v:groupContext="shape" transform="translate(374.924,544.022) rotate(135)">
+ <title>Sheet.47</title>
+ <path d="M-0 417.75 A40.674 18.0151 -156.2 0 0 40.24 422.15 L40.49 421.89" class="st17"/>
+ </g>
+ <g id="shape48-213" v:mID="48" v:groupContext="shape" transform="translate(21.0294,-19)">
+ <title>Sheet.48</title>
+ <desc>Supports X*N Flows</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="415.565" width="135" height="18"/>
+ <rect x="0" y="406.565" width="135" height="18" class="st19"/>
+ <text x="19.05" y="419.17" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Supports X*N Flows</text> </g>
+ <g id="shape49-216" v:mID="49" v:groupContext="shape" transform="translate(48.0294,-229.938)">
+ <title>Sheet.49</title>
+ <desc>Frontend Server or Load Balancer</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="400.94" width="108" height="47.25"/>
+ <rect x="0" y="377.315" width="108" height="47.25" class="st21"/>
+ <text x="14.56" y="397.34" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Frontend Server<v:newlineChar/><tspan
+ x="13.16" dy="1.2em" class="st22">or Load Balancer </tspan> </text> </g>
+ <g id="group51-220" transform="translate(223.876,-310.938)" v:mID="51" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server</title>
+ <g id="shape52-221" v:mID="52" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.52</title>
+ <g id="shadow52-222" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape53-226" v:mID="53" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.53</title>
+ <g id="shadow53-227" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape54-231" v:mID="54" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.54</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow54-232" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape59-239" v:mID="59" v:groupContext="shape" transform="translate(277.876,-373.938)">
+ <title>Sheet.59</title>
+ <path d="M-0 424.56 A111.108 53.2538 42.31 0 1 93.83 421.21 L94.14 421.41" class="st17"/>
+ </g>
+ <g id="shape60-244" v:mID="60" v:groupContext="shape" transform="translate(205.876,-283.938)">
+ <title>Sheet.60</title>
+ <desc>Backend Server 1</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.93" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server 1</text> </g>
+ <g id="group61-247" transform="translate(223.876,-207.438)" v:mID="61" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server.61</title>
+ <g id="shape62-248" v:mID="62" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.62</title>
+ <g id="shadow62-249" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape63-253" v:mID="63" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.63</title>
+ <g id="shadow63-254" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape64-258" v:mID="64" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow64-259" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape65-266" v:mID="65" v:groupContext="shape" transform="translate(205.876,-180.437)">
+ <title>Sheet.65</title>
+ <desc>Backend Server 2</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.93" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server 2</text> </g>
+ <g id="group66-269" transform="translate(219.029,-58.9375)" v:mID="66" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server.66</title>
+ <g id="shape67-270" v:mID="67" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.67</title>
+ <g id="shadow67-271" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape68-275" v:mID="68" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.68</title>
+ <g id="shadow68-276" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape69-280" v:mID="69" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow69-281" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape70-288" v:mID="70" v:groupContext="shape" transform="translate(201.029,-26.056)">
+ <title>Sheet.70</title>
+ <desc>Backend Server X</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.86" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server X</text> </g>
+ <g id="shape71-291" v:mID="71" v:groupContext="shape" transform="translate(684.44,239.627) rotate(90)">
+ <title>Sheet.71</title>
+ <path d="M0 424.56 L45 424.56" class="st24"/>
+ </g>
+ <g id="shape72-294" v:mID="72" v:groupContext="shape" transform="translate(6.85967,-22.443) rotate(-38.1076)">
+ <title>Sheet.72</title>
+ <path d="M-0 424.56 A96.1331 44.4001 55.03 0 1 68.24 420.56 L68.51 420.79" class="st17"/>
+ </g>
+ <g id="shape73-299" v:mID="73" v:groupContext="shape" transform="translate(328.501,-135.937)">
+ <title>Rectangle.73</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow73-300" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape74-305" v:mID="74" v:groupContext="shape" transform="translate(362.251,-135.937)">
+ <title>Rectangle.74</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow74-306" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape75-311" v:mID="75" v:groupContext="shape" transform="translate(409.501,-135.937)">
+ <title>Rectangle.75</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow75-312" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape76-317" v:mID="76" v:groupContext="shape" transform="translate(443.251,-135.937)">
+ <title>Rectangle.76</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow76-318" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape77-323" v:mID="77" v:groupContext="shape" transform="translate(490.501,-135.937)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow77-324" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape78-328" v:mID="78" v:groupContext="shape" transform="translate(524.251,-135.937)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow78-329" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape79-333" v:mID="79" v:groupContext="shape" transform="translate(328.501,-113.437)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow79-334" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape80-338" v:mID="80" v:groupContext="shape" transform="translate(362.251,-113.437)">
+ <title>Rectangle.80</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow80-339" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape81-343" v:mID="81" v:groupContext="shape" transform="translate(409.501,-113.437)">
+ <title>Rectangle.81</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow81-344" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape82-348" v:mID="82" v:groupContext="shape" transform="translate(443.251,-113.437)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow82-349" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape83-353" v:mID="83" v:groupContext="shape" transform="translate(490.501,-113.437)">
+ <title>Rectangle.83</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow83-354" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape84-358" v:mID="84" v:groupContext="shape" transform="translate(524.251,-113.437)">
+ <title>Rectangle.84</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow84-359" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape85-363" v:mID="85" v:groupContext="shape" transform="translate(328.501,-77.4375)">
+ <title>Rectangle.85</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow85-364" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape86-369" v:mID="86" v:groupContext="shape" transform="translate(362.251,-77.4375)">
+ <title>Rectangle.86</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow86-370" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape87-375" v:mID="87" v:groupContext="shape" transform="translate(409.501,-77.4375)">
+ <title>Rectangle.87</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow87-376" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape88-381" v:mID="88" v:groupContext="shape" transform="translate(443.251,-77.4375)">
+ <title>Rectangle.88</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow88-382" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape89-387" v:mID="89" v:groupContext="shape" transform="translate(490.501,-77.4375)">
+ <title>Rectangle.89</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow89-388" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape90-393" v:mID="90" v:groupContext="shape" transform="translate(524.251,-77.4375)">
+ <title>Rectangle.90</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow90-394" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape91-399" v:mID="91" v:groupContext="shape" transform="translate(328.501,-40.9375)">
+ <title>Rectangle.91</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow91-400" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape92-404" v:mID="92" v:groupContext="shape" transform="translate(362.251,-40.9375)">
+ <title>Rectangle.92</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow92-405" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape93-409" v:mID="93" v:groupContext="shape" transform="translate(409.501,-40.9375)">
+ <title>Rectangle.93</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow93-410" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape94-414" v:mID="94" v:groupContext="shape" transform="translate(443.251,-40.9375)">
+ <title>Rectangle.94</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow94-415" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape95-419" v:mID="95" v:groupContext="shape" transform="translate(490.501,-40.9375)">
+ <title>Rectangle.95</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow95-420" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape96-425" v:mID="96" v:groupContext="shape" transform="translate(524.251,-40.9375)">
+ <title>Rectangle.96</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow96-426" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape97-431" v:mID="97" v:groupContext="shape" transform="translate(326.251,-31.9375)">
+ <title>Rectangle.97</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow97-432" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st6"/>
+ </g>
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st7"/>
+ </g>
+ <g id="shape98-436" v:mID="98" v:groupContext="shape" transform="translate(337.501,-162.938)">
+ <title>Sheet.98</title>
+ <desc>Local Table for N Specific Flows Serviced at Node X</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="110.423" cy="418.94" width="220.85" height="11.25"/>
+ <rect x="0" y="413.315" width="220.846" height="11.25" class="st8"/>
+ <text x="5.55" y="421.94" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Local Table for N Specific Flows Serviced at Node X</text> </g>
+ <g id="shape99-439" v:mID="99" v:groupContext="shape" transform="translate(-204.342,-29.4449) rotate(-53.7462)">
+ <title>Sheet.99</title>
+ <path d="M0 424.56 L160.37 424.56" class="st25"/>
+ </g>
+ <g id="shape100-445" v:mID="100" v:groupContext="shape" transform="translate(-37.6568,-164.882) rotate(-24.444)">
+ <title>Sheet.100</title>
+ <path d="M0 424.56 L101.71 424.56" class="st25"/>
+ </g>
+ <g id="shape101-450" v:mID="101" v:groupContext="shape" transform="translate(464.049,-50.8578) rotate(50.099)">
+ <title>Sheet.101</title>
+ <path d="M0 424.56 L139.8 424.56" class="st25"/>
+ </g>
+ <g id="shape102-455" v:mID="102" v:groupContext="shape" transform="translate(372.376,-207.438)">
+ <title>Sheet.102</title>
+ <desc>Supports N Flows</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="415.565" width="135" height="18"/>
+ <rect x="0" y="406.565" width="135" height="18" class="st19"/>
+ <text x="25.15" y="419.17" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Supports N Flows</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/sample_app_ug/index.rst b/doc/guides/sample_app_ug/index.rst
index 775e2f7..260f6a5 100644
--- a/doc/guides/sample_app_ug/index.rst
+++ b/doc/guides/sample_app_ug/index.rst
@@ -57,6 +57,7 @@ Sample Applications User Guides
l3_forward_virtual
link_status_intr
load_balancer
+ flow_distributor
multi_process
qos_metering
qos_scheduler
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v4 0/5] Elastic Flow Distributor
2017-01-12 22:15 ` [dpdk-dev] [PATCH v3 " Pablo de Lara
` (4 preceding siblings ...)
2017-01-12 22:16 ` [dpdk-dev] [PATCH v3 5/5] doc: add flow distributor guide Pablo de Lara
@ 2017-01-15 12:04 ` Pablo de Lara
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 1/5] efd: new Elastic Flow Distributor library Pablo de Lara
` (5 more replies)
5 siblings, 6 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-15 12:04 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara
EFD is a distributor library that uses perfect hashing to determine a
target/value for a given incoming flow key. It has the following advantages:
first, because it uses perfect hashing it does not store the key itself and
hence lookup performance is not dependent on the key size. Second, the
target/value can be any arbitrary value hence the system designer and/or
operator can better optimize service rates and inter-cluster network traffic
locating. Third, since the storage requirement is much smaller than a
hash-based flow table (i.e. better fit for CPU cache), EFD can scale to millions
of flow keys. Finally, with current optimized library implementation performance
is fully scalable with number of CPU cores.
The basic idea of EFD is when a given key is to be inserted, a family of hash
functions is searched until the correct hash function that maps the input key to
the correct value is found. However, rather than explicitly storing all keys and
their associated values, EFD stores only indices of hash functions that map keys
to values, and thereby consumes much less space than conventional flow-based
tables. The lookup operation is very simple, similar to computational-based
scheme, given an input key the lookup operation is reduced to hashing that key
with the correct hash function.
Intuitively, finding a hash function that maps each of a large number (millions)
of input keys to the correct output value is effectively impossible, as a result
EFD, breaks the problem into smaller pieces (divide and conquer). EFD divides
the entire input key set into many small groups. Each group consists of
approximately 20-28 keys (a configurable parameter for the library), then, for
each small group, a brute force search to find a hash function that produces the
correct outputs for each key in the group.
It should be mentioned that since in the online lookup table for EFD doesn’t
store the key itself, the size of the EFD table is independent of the key size
and hence EFD lookup performance which is almost constant irrespective of the
length of the key which is a highly desirable feature especially for longer
keys.
Library code is included in the patch, plus an sample application that shows
how the library can be used.
RFC for this library was already sent:
http://dpdk.org/ml/archives/dev/2016-October/049238.html
For more information on the library, check out the following document:
https://github.com/pablodelara/perfect_hash_flow_distributor/blob/master/EFD_description.pdf
Changes in v4:
- Added References section
Changes in v3:
- Fixed SVG files
- Fixed copyright dates
- Reformatted parts of documentation
Changes in v2:
- Added documentation for library and sample app
- Fixed checkpatch errors/warnings
- Added functional and performance tests
- Made key size variable at runtime
- Made code multi-architecture compatible at runtime
Pablo de Lara (5):
efd: new Elastic Flow Distributor library
app/test: add EFD functional and perf tests
examples/flow_distributor: sample app to demonstrate EFD usage
doc: add EFD library section in Programmers guide
doc: add flow distributor guide
MAINTAINERS | 9 +
app/test/Makefile | 5 +-
app/test/test_efd.c | 494 ++++++++
app/test/test_efd_perf.c | 407 +++++++
config/common_base | 5 +
doc/api/doxy-api-index.md | 3 +-
doc/api/doxy-api.conf | 1 +
doc/api/examples.dox | 4 +
doc/guides/prog_guide/efd_lib.rst | 438 +++++++
doc/guides/prog_guide/img/efd_i1.svg | 130 ++
doc/guides/prog_guide/img/efd_i10.svg | 384 ++++++
doc/guides/prog_guide/img/efd_i11.svg | 319 +++++
doc/guides/prog_guide/img/efd_i12.svg | 1008 +++++++++++++++
doc/guides/prog_guide/img/efd_i2.svg | 280 +++++
doc/guides/prog_guide/img/efd_i3.svg | 634 ++++++++++
doc/guides/prog_guide/img/efd_i4.svg | 203 +++
doc/guides/prog_guide/img/efd_i5.svg | 183 +++
doc/guides/prog_guide/img/efd_i6.svg | 1254 +++++++++++++++++++
doc/guides/prog_guide/img/efd_i7.svg | 790 ++++++++++++
doc/guides/prog_guide/img/efd_i8.svg | 182 +++
doc/guides/prog_guide/img/efd_i9.svg | 390 ++++++
doc/guides/prog_guide/index.rst | 23 +
doc/guides/rel_notes/release_17_02.rst | 15 +
doc/guides/sample_app_ug/flow_distributor.rst | 494 ++++++++
doc/guides/sample_app_ug/img/flow_distributor.svg | 1254 +++++++++++++++++++
doc/guides/sample_app_ug/index.rst | 1 +
examples/Makefile | 1 +
examples/flow_distributor/Makefile | 44 +
examples/flow_distributor/distributor/Makefile | 57 +
examples/flow_distributor/distributor/args.c | 200 +++
examples/flow_distributor/distributor/args.h | 39 +
examples/flow_distributor/distributor/init.c | 371 ++++++
examples/flow_distributor/distributor/init.h | 76 ++
examples/flow_distributor/distributor/main.c | 362 ++++++
examples/flow_distributor/node/Makefile | 48 +
examples/flow_distributor/node/node.c | 417 +++++++
examples/flow_distributor/shared/common.h | 99 ++
lib/Makefile | 3 +-
lib/librte_eal/common/include/rte_log.h | 3 +-
lib/librte_efd/Makefile | 56 +
lib/librte_efd/rte_efd.c | 1354 +++++++++++++++++++++
lib/librte_efd/rte_efd.h | 294 +++++
lib/librte_efd/rte_efd_version.map | 12 +
mk/rte.app.mk | 3 +-
44 files changed, 12344 insertions(+), 5 deletions(-)
create mode 100644 app/test/test_efd.c
create mode 100644 app/test/test_efd_perf.c
create mode 100644 doc/guides/prog_guide/efd_lib.rst
create mode 100644 doc/guides/prog_guide/img/efd_i1.svg
create mode 100644 doc/guides/prog_guide/img/efd_i10.svg
create mode 100644 doc/guides/prog_guide/img/efd_i11.svg
create mode 100644 doc/guides/prog_guide/img/efd_i12.svg
create mode 100644 doc/guides/prog_guide/img/efd_i2.svg
create mode 100644 doc/guides/prog_guide/img/efd_i3.svg
create mode 100644 doc/guides/prog_guide/img/efd_i4.svg
create mode 100644 doc/guides/prog_guide/img/efd_i5.svg
create mode 100644 doc/guides/prog_guide/img/efd_i6.svg
create mode 100644 doc/guides/prog_guide/img/efd_i7.svg
create mode 100644 doc/guides/prog_guide/img/efd_i8.svg
create mode 100644 doc/guides/prog_guide/img/efd_i9.svg
create mode 100644 doc/guides/sample_app_ug/flow_distributor.rst
create mode 100644 doc/guides/sample_app_ug/img/flow_distributor.svg
create mode 100644 examples/flow_distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/args.c
create mode 100644 examples/flow_distributor/distributor/args.h
create mode 100644 examples/flow_distributor/distributor/init.c
create mode 100644 examples/flow_distributor/distributor/init.h
create mode 100644 examples/flow_distributor/distributor/main.c
create mode 100644 examples/flow_distributor/node/Makefile
create mode 100644 examples/flow_distributor/node/node.c
create mode 100644 examples/flow_distributor/shared/common.h
create mode 100644 lib/librte_efd/Makefile
create mode 100644 lib/librte_efd/rte_efd.c
create mode 100644 lib/librte_efd/rte_efd.h
create mode 100644 lib/librte_efd/rte_efd_version.map
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v4 1/5] efd: new Elastic Flow Distributor library
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 0/5] Elastic Flow Distributor Pablo de Lara
@ 2017-01-15 12:04 ` Pablo de Lara
2017-01-16 4:25 ` Jerin Jacob
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 2/5] app/test: add EFD functional and perf tests Pablo de Lara
` (4 subsequent siblings)
5 siblings, 1 reply; 63+ messages in thread
From: Pablo de Lara @ 2017-01-15 12:04 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Byron Marohn, Saikrishna Edupuganti
Elastic Flow Distributor (EFD) is a distributor library that uses
perfect hashing to determine a target/value for a given incoming flow key.
It has the following advantages:
- First, because it uses perfect hashing, it does not store
the key itself and hence lookup performance is not dependent
on the key size.
- Second, the target/value can be any arbitrary value hence
the system designer and/or operator can better optimize service rates
and inter-cluster network traffic locating.
- Third, since the storage requirement is much smaller than a hash-based
flow table (i.e. better fit for CPU cache), EFD can scale to
millions of flow keys.
Finally, with current optimized library implementation performance
is fully scalable with number of CPU cores.
Signed-off-by: Byron Marohn <byron.marohn@intel.com>
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 5 +
config/common_base | 5 +
doc/api/doxy-api-index.md | 3 +-
doc/api/doxy-api.conf | 1 +
doc/guides/rel_notes/release_17_02.rst | 12 +
lib/Makefile | 3 +-
lib/librte_eal/common/include/rte_log.h | 3 +-
lib/librte_efd/Makefile | 56 ++
lib/librte_efd/rte_efd.c | 1354 +++++++++++++++++++++++++++++++
lib/librte_efd/rte_efd.h | 294 +++++++
lib/librte_efd/rte_efd_version.map | 12 +
mk/rte.app.mk | 3 +-
12 files changed, 1747 insertions(+), 4 deletions(-)
create mode 100644 lib/librte_efd/Makefile
create mode 100644 lib/librte_efd/rte_efd.c
create mode 100644 lib/librte_efd/rte_efd.h
create mode 100644 lib/librte_efd/rte_efd_version.map
diff --git a/MAINTAINERS b/MAINTAINERS
index 9645c9b..9c60d67 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -528,6 +528,11 @@ F: app/test/test_acl.*
F: examples/l3fwd-acl/
F: doc/guides/sample_app_ug/l3_forward_access_ctrl.rst
+EFD
+M: Byron Marohn <byron.marohn@intel.com>
+M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
+F: lib/librte_efd/
+
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
M: Pablo de Lara <pablo.de.lara.guarch@intel.com>
diff --git a/config/common_base b/config/common_base
index 8e9dcfa..869d8fb 100644
--- a/config/common_base
+++ b/config/common_base
@@ -467,6 +467,11 @@ CONFIG_RTE_LIBRTE_HASH=y
CONFIG_RTE_LIBRTE_HASH_DEBUG=n
#
+# Compile librte_efd
+#
+CONFIG_RTE_LIBRTE_EFD=y
+
+#
# Compile librte_jobstats
#
CONFIG_RTE_LIBRTE_JOBSTATS=y
diff --git a/doc/api/doxy-api-index.md b/doc/api/doxy-api-index.md
index 72d59b2..0d34e2f 100644
--- a/doc/api/doxy-api-index.md
+++ b/doc/api/doxy-api-index.md
@@ -90,7 +90,8 @@ There are many libraries, so their headers may be grouped by topics:
[frag/reass] (@ref rte_ip_frag.h),
[LPM IPv4 route] (@ref rte_lpm.h),
[LPM IPv6 route] (@ref rte_lpm6.h),
- [ACL] (@ref rte_acl.h)
+ [ACL] (@ref rte_acl.h),
+ [EFD] (@ref rte_efd.h)
- **QoS**:
[metering] (@ref rte_meter.h),
diff --git a/doc/api/doxy-api.conf b/doc/api/doxy-api.conf
index b340fcf..6892315 100644
--- a/doc/api/doxy-api.conf
+++ b/doc/api/doxy-api.conf
@@ -40,6 +40,7 @@ INPUT = doc/api/doxy-api-index.md \
lib/librte_compat \
lib/librte_cryptodev \
lib/librte_distributor \
+ lib/librte_efd \
lib/librte_ether \
lib/librte_hash \
lib/librte_ip_frag \
diff --git a/doc/guides/rel_notes/release_17_02.rst b/doc/guides/rel_notes/release_17_02.rst
index 180af82..5023038 100644
--- a/doc/guides/rel_notes/release_17_02.rst
+++ b/doc/guides/rel_notes/release_17_02.rst
@@ -53,6 +53,18 @@ New Features
information.
+* **Added Elastic Flow Distributor library (rte_efd).**
+
+ This new library uses perfect hashing to determine a target/value for a
+ given incoming flow key.
+
+ It does not store the key itself for lookup operations, and therefore,
+ lookup performance is not dependent on the key size. Also, the target/value
+ can be any arbitrary value (8 bits by default). Finally, the storage requirement
+ is much smaller than a hash-based flow table and therefore, it can better fit for
+ CPU cache, being able to scale to millions of flow keys.
+
+
Resolved Issues
---------------
diff --git a/lib/Makefile b/lib/Makefile
index 990f23a..4178325 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -1,6 +1,6 @@
# BSD LICENSE
#
-# Copyright(c) 2010-2015 Intel Corporation. All rights reserved.
+# Copyright(c) 2010-2017 Intel Corporation. All rights reserved.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -43,6 +43,7 @@ DIRS-$(CONFIG_RTE_LIBRTE_ETHER) += librte_ether
DIRS-$(CONFIG_RTE_LIBRTE_CRYPTODEV) += librte_cryptodev
DIRS-$(CONFIG_RTE_LIBRTE_VHOST) += librte_vhost
DIRS-$(CONFIG_RTE_LIBRTE_HASH) += librte_hash
+DIRS-$(CONFIG_RTE_LIBRTE_EFD) += librte_efd
DIRS-$(CONFIG_RTE_LIBRTE_LPM) += librte_lpm
DIRS-$(CONFIG_RTE_LIBRTE_ACL) += librte_acl
DIRS-$(CONFIG_RTE_LIBRTE_NET) += librte_net
diff --git a/lib/librte_eal/common/include/rte_log.h b/lib/librte_eal/common/include/rte_log.h
index 671e274..954b96c 100644
--- a/lib/librte_eal/common/include/rte_log.h
+++ b/lib/librte_eal/common/include/rte_log.h
@@ -1,7 +1,7 @@
/*-
* BSD LICENSE
*
- * Copyright(c) 2010-2014 Intel Corporation. All rights reserved.
+ * Copyright(c) 2010-2017 Intel Corporation. All rights reserved.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -79,6 +79,7 @@ extern struct rte_logs rte_logs;
#define RTE_LOGTYPE_PIPELINE 0x00008000 /**< Log related to pipeline. */
#define RTE_LOGTYPE_MBUF 0x00010000 /**< Log related to mbuf. */
#define RTE_LOGTYPE_CRYPTODEV 0x00020000 /**< Log related to cryptodev. */
+#define RTE_LOGTYPE_EFD 0x00040000 /**< Log related to EFD. */
/* these log types can be used in an application */
#define RTE_LOGTYPE_USER1 0x01000000 /**< User-defined log type 1. */
diff --git a/lib/librte_efd/Makefile b/lib/librte_efd/Makefile
new file mode 100644
index 0000000..58d34af
--- /dev/null
+++ b/lib/librte_efd/Makefile
@@ -0,0 +1,56 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+# library name
+LIB = librte_efd.a
+
+CFLAGS += -O3
+CFLAGS += $(WERROR_FLAGS) -I$(SRCDIR)
+
+EXPORT_MAP := rte_efd_version.map
+
+LIBABIVER := 1
+
+# all source are stored in SRCS-y
+SRCS-$(CONFIG_RTE_LIBRTE_EFD) := rte_efd.c
+
+# install this header file
+SYMLINK-$(CONFIG_RTE_LIBRTE_EFD)-include := rte_efd.h
+
+# this lib depends upon:
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_mbuf
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_mempool
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_eal
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_ether
+
+include $(RTE_SDK)/mk/rte.lib.mk
diff --git a/lib/librte_efd/rte_efd.c b/lib/librte_efd/rte_efd.c
new file mode 100644
index 0000000..917e076
--- /dev/null
+++ b/lib/librte_efd/rte_efd.c
@@ -0,0 +1,1354 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <immintrin.h>
+#include <math.h>
+#include <sys/queue.h>
+
+#include <rte_log.h>
+#include <rte_eal_memconfig.h>
+#include <rte_errno.h>
+#include <rte_malloc.h>
+#include <rte_memzone.h>
+#include <rte_prefetch.h>
+#include <rte_branch_prediction.h>
+#include <rte_memcpy.h>
+#include <rte_ring.h>
+#include <rte_jhash.h>
+#include <rte_hash_crc.h>
+
+#include "rte_efd.h"
+
+#define EFD_KEY(key_idx, table) (table->keys + ((key_idx) * table->key_len))
+/** Hash function used to determine chunk_id and bin_id for a group */
+#define EFD_HASH(key, table) \
+ (uint32_t)(rte_jhash(key, table->key_len, 0xbc9f1d34))
+/** Hash function used as constant component of perfect hash search */
+#define EFD_HASHFUNCA(key, table) \
+ (uint32_t)(rte_hash_crc(key, table->key_len, 0xbc9f1d35))
+/** Hash function used as multiplicative component of perfect hash search */
+#define EFD_HASHFUNCB(key, table) \
+ (uint32_t)(rte_hash_crc(key, table->key_len, 0xbc9f1d36))
+
+/*************************************************************************
+ * Fixed constants
+ *************************************************************************/
+
+/* These parameters are fixed by the efd_bin_to_group balancing table */
+#define EFD_CHUNK_NUM_GROUPS (64)
+#define EFD_CHUNK_NUM_BINS (256)
+#define EFD_CHUNK_NUM_BIN_TO_GROUP_SETS \
+ (EFD_CHUNK_NUM_BINS / EFD_CHUNK_NUM_GROUPS)
+
+/*
+ * Target number of rules that each chunk is created to handle.
+ * Used when initially allocating the table
+ */
+#define EFD_TARGET_CHUNK_NUM_RULES \
+ (EFD_CHUNK_NUM_GROUPS * EFD_TARGET_GROUP_NUM_RULES)
+/*
+ * Max number of rules that each chunk is created to handle.
+ * Used when initially allocating the table
+ */
+#define EFD_TARGET_CHUNK_MAX_NUM_RULES \
+ (EFD_CHUNK_NUM_GROUPS * EFD_MAX_GROUP_NUM_RULES)
+
+/** This is fixed based on the bin_to_group permutation array */
+#define EFD_MAX_GROUP_NUM_BINS (16)
+
+/**
+ * The end of the chunks array needs some extra padding to ensure
+ * that vectorization over-reads on the last online chunk stay within
+allocated memory
+ */
+#define EFD_NUM_CHUNK_PADDING_BYTES (256)
+
+#define EFD_LOOKUPTBL_SHIFT (32 - 4)
+typedef uint16_t efd_lookuptbl_t;
+typedef uint16_t efd_hashfunc_t;
+
+/* All different signature compare functions */
+enum rte_efd_compare_function {
+ RTE_HASH_COMPARE_SCALAR = 0,
+ RTE_HASH_COMPARE_AVX2,
+ RTE_HASH_COMPARE_NUM
+};
+
+TAILQ_HEAD(rte_efd_list, rte_tailq_entry);
+
+static struct rte_tailq_elem rte_efd_tailq = {
+ .name = "RTE_EFD",
+};
+EAL_REGISTER_TAILQ(rte_efd_tailq);
+
+/** Internal permutation array used to shuffle bins into pseudorandom groups */
+const uint32_t efd_bin_to_group[EFD_CHUNK_NUM_BIN_TO_GROUP_SETS][EFD_CHUNK_NUM_BINS] = {
+ {
+ 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3,
+ 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
+ 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11,
+ 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15,
+ 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19,
+ 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23,
+ 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27,
+ 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31,
+ 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35,
+ 36, 36, 36, 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39,
+ 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, 42, 43, 43, 43, 43,
+ 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47,
+ 48, 48, 48, 48, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51,
+ 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, 54, 55, 55, 55, 55,
+ 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59,
+ 60, 60, 60, 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63
+ },
+ {
+ 34, 33, 48, 59, 0, 21, 36, 18, 9, 49, 54, 38, 51, 23, 31, 5,
+ 44, 23, 37, 52, 11, 4, 58, 20, 38, 40, 38, 22, 26, 28, 42, 6,
+ 46, 16, 31, 28, 46, 14, 60, 0, 35, 53, 16, 58, 16, 29, 39, 7,
+ 1, 54, 15, 11, 48, 3, 62, 9, 58, 5, 30, 43, 17, 7, 36, 34,
+ 6, 36, 2, 14, 10, 1, 47, 47, 20, 45, 62, 56, 34, 25, 39, 18,
+ 51, 41, 61, 25, 56, 40, 41, 37, 52, 35, 30, 57, 11, 42, 37, 27,
+ 54, 19, 26, 13, 48, 31, 46, 15, 12, 10, 16, 20, 43, 17, 12, 55,
+ 45, 18, 8, 41, 7, 31, 42, 63, 12, 14, 21, 57, 24, 40, 5, 41,
+ 13, 44, 23, 59, 25, 57, 52, 50, 62, 1, 2, 49, 32, 57, 26, 43,
+ 56, 60, 55, 5, 49, 6, 3, 50, 46, 39, 27, 33, 17, 4, 53, 13,
+ 2, 19, 36, 51, 63, 0, 22, 33, 59, 28, 29, 23, 45, 33, 53, 27,
+ 22, 21, 40, 56, 4, 18, 44, 47, 28, 17, 4, 50, 21, 62, 8, 39,
+ 0, 8, 15, 24, 29, 24, 9, 11, 48, 61, 35, 55, 43, 1, 54, 42,
+ 53, 60, 22, 3, 32, 52, 25, 8, 15, 60, 7, 55, 27, 63, 19, 10,
+ 63, 24, 61, 19, 12, 38, 6, 29, 13, 37, 10, 3, 45, 32, 32, 30,
+ 49, 61, 44, 14, 20, 58, 35, 30, 2, 26, 34, 51, 9, 59, 47, 50
+ },
+ {
+ 32, 35, 32, 34, 55, 5, 6, 23, 49, 11, 6, 23, 52, 37, 29, 54,
+ 55, 40, 63, 50, 29, 52, 61, 25, 12, 56, 39, 38, 29, 11, 46, 1,
+ 40, 11, 19, 56, 7, 28, 51, 16, 15, 48, 21, 51, 60, 31, 14, 22,
+ 41, 47, 59, 56, 53, 28, 58, 26, 43, 27, 41, 33, 24, 52, 44, 38,
+ 13, 59, 48, 51, 60, 15, 3, 30, 15, 0, 10, 62, 44, 14, 28, 51,
+ 38, 2, 41, 26, 25, 49, 10, 12, 55, 57, 27, 35, 19, 33, 0, 30,
+ 5, 36, 47, 53, 5, 53, 20, 43, 34, 37, 52, 41, 21, 63, 59, 9,
+ 24, 1, 45, 24, 39, 44, 45, 16, 9, 17, 7, 50, 57, 22, 18, 28,
+ 25, 45, 2, 40, 58, 15, 17, 3, 1, 27, 61, 39, 19, 0, 19, 21,
+ 57, 62, 54, 60, 54, 40, 48, 33, 36, 37, 4, 42, 1, 43, 58, 8,
+ 13, 42, 10, 56, 35, 22, 48, 61, 63, 10, 49, 9, 24, 9, 25, 57,
+ 33, 18, 13, 31, 42, 36, 36, 55, 30, 37, 53, 34, 59, 4, 4, 23,
+ 8, 16, 58, 14, 30, 11, 12, 63, 49, 62, 2, 39, 47, 22, 2, 60,
+ 18, 8, 46, 31, 6, 20, 32, 29, 46, 42, 20, 31, 32, 61, 34, 4,
+ 47, 26, 20, 43, 26, 21, 7, 3, 16, 35, 18, 44, 27, 62, 13, 23,
+ 6, 50, 12, 8, 45, 17, 3, 46, 50, 7, 14, 5, 17, 54, 38, 0
+ },
+ {
+ 29, 56, 5, 7, 54, 48, 23, 37, 35, 44, 52, 40, 33, 49, 60, 0,
+ 59, 51, 28, 12, 41, 26, 2, 23, 34, 5, 59, 40, 3, 19, 6, 26,
+ 35, 53, 45, 49, 29, 57, 28, 62, 58, 59, 19, 53, 59, 62, 6, 54,
+ 13, 15, 48, 50, 45, 21, 41, 12, 34, 40, 24, 56, 19, 21, 35, 18,
+ 55, 45, 9, 61, 47, 61, 19, 15, 16, 39, 17, 31, 3, 51, 21, 50,
+ 17, 25, 25, 11, 44, 16, 18, 28, 14, 2, 37, 61, 58, 27, 62, 4,
+ 14, 17, 1, 9, 46, 28, 37, 0, 53, 43, 57, 7, 57, 46, 21, 41,
+ 39, 14, 52, 60, 44, 53, 49, 60, 49, 63, 13, 11, 29, 1, 55, 47,
+ 55, 12, 60, 43, 54, 37, 13, 6, 42, 10, 36, 13, 9, 8, 34, 51,
+ 31, 32, 12, 7, 57, 2, 26, 14, 3, 30, 63, 3, 32, 1, 5, 11,
+ 27, 24, 26, 44, 31, 23, 56, 38, 62, 0, 40, 30, 6, 23, 38, 2,
+ 47, 5, 15, 27, 16, 10, 31, 25, 22, 63, 30, 25, 20, 33, 32, 50,
+ 29, 43, 55, 10, 50, 45, 56, 20, 4, 7, 27, 46, 11, 16, 22, 52,
+ 35, 20, 41, 54, 46, 33, 42, 18, 63, 8, 22, 58, 36, 4, 51, 42,
+ 38, 32, 38, 22, 17, 0, 47, 8, 48, 8, 48, 1, 61, 36, 33, 20,
+ 24, 39, 39, 18, 30, 36, 9, 43, 42, 24, 10, 58, 4, 15, 34, 52
+ },
+};
+
+/*************************************************************************
+ * Offline region structures
+ *************************************************************************/
+
+/** Online group containing number of rules, values, keys and their bins
+ * for EFD_MAX_GROUP_NUM_RULES rules.
+ */
+struct efd_offline_group_rules {
+ uint32_t num_rules;
+ /**< Sum of the number of rules in all bins assigned to this group. */
+
+ uint32_t key_idx[EFD_MAX_GROUP_NUM_RULES];
+ /**< Array with all keys of the group. */
+ efd_value_t value[EFD_MAX_GROUP_NUM_RULES];
+ /**< Array with all values of the keys of the group. */
+
+ uint8_t bin_id[EFD_MAX_GROUP_NUM_RULES];
+ /**< Stores the bin for each correspending key to
+ * avoid having to recompute it
+ */
+};
+
+/** Offline chunk record, containing EFD_TARGET_CHUNK_NUM_RULES rules.
+ * Those rules are split into EFD_CHUNK_NUM_GROUPS groups per chunk.
+ */
+struct efd_offline_chunk_rules {
+ uint16_t num_rules;
+ /**< Number of rules in the entire chunk;
+ * used to detect unbalanced groups
+ */
+
+ struct efd_offline_group_rules group_rules[EFD_CHUNK_NUM_GROUPS];
+ /**< Array of all groups in the chunk. */
+};
+
+/*************************************************************************
+ * Online region structures
+ *************************************************************************/
+
+/** Online group containing values for EFD_MAX_GROUP_NUM_RULES rules. */
+struct efd_online_group_entry {
+ efd_hashfunc_t hash_idx[RTE_EFD_VALUE_NUM_BITS];
+ efd_lookuptbl_t lookup_table[RTE_EFD_VALUE_NUM_BITS];
+} __attribute__((__packed__));
+
+/**
+ * A single chunk record, containing EFD_TARGET_CHUNK_NUM_RULES rules.
+ * Those rules are split into EFD_CHUNK_NUM_GROUPS groups per chunk.
+ */
+struct efd_online_chunk {
+ uint8_t bin_choice_list[(EFD_CHUNK_NUM_BINS * 2 + 7) / 8];
+ /**< This is a packed indirection index into the 'groups' array.
+ * Each byte contains four two-bit values which index into
+ * the efd_bin_to_group array.
+ * The efd_bin_to_group array returns the index into the groups array
+ */
+
+ struct efd_online_group_entry groups[EFD_CHUNK_NUM_GROUPS];
+ /**< Array of all the groups in the chunk. */
+} __attribute__((__packed__));
+
+/**
+ * EFD table structure
+ */
+struct rte_efd_table {
+ char name[RTE_EFD_NAMESIZE]; /**< Name of the efd table. */
+
+ uint32_t key_len; /**< Length of the key stored offline */
+
+ uint32_t max_num_rules;
+ /**< Static maximum number of entries the table was constructed to hold. */
+
+ uint32_t num_rules;
+ /**< Number of entries currently in the table . */
+
+ uint32_t num_chunks;
+ /**< Number of chunks in the table needed to support num_rules. */
+
+ uint32_t num_chunks_shift;
+ /**< Bits to shift to get chunk id, instead of dividing by num_chunk. */
+
+ enum rte_efd_compare_function cmp_fn;
+ /**< Indicates which compare function to use. */
+
+ struct efd_online_chunk *chunks[RTE_MAX_NUMA_NODES];
+ /**< Dynamic array of size num_chunks of chunk records. */
+
+ struct efd_offline_chunk_rules *offline_chunks;
+ /**< Dynamic array of size num_chunks of key-value pairs. */
+
+ struct rte_ring *free_slots;
+ /**< Ring that stores all indexes of the free slots in the key table */
+
+ uint8_t *keys; /**< Dynamic array of size max_num_rules of keys */
+};
+
+/**
+ * Computes the chunk ID for a given key hash
+ *
+ * @param table
+ * EFD table to reference
+ * @param hashed_key
+ * 32-bit key hash returned by EFD_HASH
+ *
+ * @return
+ * chunk ID containing this key hash
+ */
+static inline uint32_t
+efd_get_chunk_id(const struct rte_efd_table * const table,
+ const uint32_t hashed_key)
+{
+ return hashed_key & (table->num_chunks - 1);
+}
+
+/**
+ * Computes the bin ID for a given key hash
+ *
+ * @param table
+ * EFD table to reference
+ * @param hashed_key
+ * 32-bit key hash returned by EFD_HASH
+ *
+ * @return bin ID containing this key hash
+ */
+static inline uint32_t
+efd_get_bin_id(const struct rte_efd_table * const table,
+ const uint32_t hashed_key)
+{
+ return (hashed_key >> table->num_chunks_shift) & (EFD_CHUNK_NUM_BINS - 1);
+}
+
+/**
+ * Looks up the current permutation choice for a particular bin in the online table
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to look up existing values (ideally caller's socket id)
+ * @param chunk_id
+ * Chunk ID of bin to look up
+ * @param bin_id
+ * Bin ID to look up
+ *
+ * @return
+ * Currently active permutation choice in the online table
+ */
+static inline uint8_t
+efd_get_choice(const struct rte_efd_table * const table,
+ const unsigned int socket_id, const uint32_t chunk_id,
+ const uint32_t bin_id)
+{
+ struct efd_online_chunk *chunk = &table->chunks[socket_id][chunk_id];
+
+ /*
+ * Grab the chunk (byte) that contains the choices
+ * for four neighboring bins.
+ */
+ uint8_t choice_chunk =
+ chunk->bin_choice_list[bin_id / EFD_CHUNK_NUM_BIN_TO_GROUP_SETS];
+
+ /*
+ * Compute the offset into the chunk that contains
+ * the group_id lookup position
+ */
+ int offset = (bin_id & 0x3) * 2;
+
+ /* Extract from the byte just the desired lookup position */
+ return (uint8_t) ((choice_chunk >> offset) & 0x3);
+}
+
+/**
+ * Compute the chunk_id and bin_id for a given key
+ *
+ * @param table
+ * EFD table to reference
+ * @param key
+ * Key to hash and find location of
+ * @param chunk_id
+ * Computed chunk ID
+ * @param bin_id
+ * Computed bin ID
+ *
+ */
+static inline void
+efd_compute_ids(const struct rte_efd_table * const table,
+ const void *key, uint32_t * const chunk_id, uint32_t * const bin_id)
+{
+ /* Compute the position of the entry in the hash table */
+ uint32_t h = EFD_HASH(key, table);
+
+ /* Compute the chunk_id where that entry can be found */
+ *chunk_id = efd_get_chunk_id(table, h);
+
+ /*
+ * Compute the bin within that chunk where the entry
+ * can be found (0 - 255)
+ */
+ *bin_id = efd_get_bin_id(table, h);
+}
+
+/**
+ * Search for a hash function for a group that satisfies all group results
+ */
+static inline int
+efd_search_hash(struct rte_efd_table * const table,
+ const struct efd_offline_group_rules * const off_group,
+ struct efd_online_group_entry * const on_group)
+{
+ efd_hashfunc_t hash_idx;
+ efd_hashfunc_t start_hash_idx[RTE_EFD_VALUE_NUM_BITS];
+ efd_lookuptbl_t start_lookup_table[RTE_EFD_VALUE_NUM_BITS];
+
+ uint32_t i, j, rule_id;
+ uint32_t hash_val_a[EFD_MAX_GROUP_NUM_RULES];
+ uint32_t hash_val_b[EFD_MAX_GROUP_NUM_RULES];
+ uint32_t hash_val[EFD_MAX_GROUP_NUM_RULES];
+
+
+ rte_prefetch0(off_group->value);
+
+ /*
+ * Prepopulate the hash_val tables by running the two hash functions
+ * for each provided rule
+ */
+ for (i = 0; i < off_group->num_rules; i++) {
+ void *key_stored = EFD_KEY(off_group->key_idx[i], table);
+ hash_val_b[i] = EFD_HASHFUNCB(key_stored, table);
+ hash_val_a[i] = EFD_HASHFUNCA(key_stored, table);
+ }
+
+ for (i = 0; i < RTE_EFD_VALUE_NUM_BITS; i++) {
+ hash_idx = on_group->hash_idx[i];
+ start_hash_idx[i] = hash_idx;
+ start_lookup_table[i] = on_group->lookup_table[i];
+
+ do {
+ efd_lookuptbl_t lookup_table = 0;
+ efd_lookuptbl_t lookup_table_complement = 0;
+
+ for (rule_id = 0; rule_id < off_group->num_rules; rule_id++)
+ hash_val[rule_id] = hash_val_a[rule_id] + (hash_idx *
+ hash_val_b[rule_id]);
+
+ /*
+ * The goal here is to find a hash function for this
+ * particular bit entry that meets the following criteria:
+ * The most significant bits of the hash result define a
+ * shift into the lookup table where the bit will be stored
+ */
+
+ /* Iterate over each provided rule */
+ for (rule_id = 0; rule_id < off_group->num_rules;
+ rule_id++) {
+ /*
+ * Use the few most significant bits (number based on
+ * EFD_LOOKUPTBL_SIZE) to see what position the
+ * expected bit should be set in the lookup_table
+ */
+ uint32_t bucket_idx = hash_val[rule_id] >>
+ EFD_LOOKUPTBL_SHIFT;
+
+ /*
+ * Get the current bit of interest.
+ * This only find an appropriate hash function
+ * for one bit at a time of the rule
+ */
+ efd_lookuptbl_t expected =
+ (off_group->value[rule_id] >> i) & 0x1;
+
+ /*
+ * Add the expected bit (if set) to a map
+ * (lookup_table). Also set its complement
+ * in lookup_table_complement
+ */
+ lookup_table |= expected << bucket_idx;
+ lookup_table_complement |= (1 - expected)
+ << bucket_idx;
+
+ /*
+ * If ever the hash function of two different
+ * elements result in different values at the
+ * same location in the lookup_table,
+ * the current hash_idx is not valid.
+ */
+ if (lookup_table & lookup_table_complement)
+ break;
+ }
+
+ /*
+ * Check if the previous loop completed without
+ * breaking early
+ */
+ if (rule_id == off_group->num_rules) {
+ /*
+ * Current hash function worked, store it
+ * for the current group
+ */
+ on_group->hash_idx[i] = hash_idx;
+ on_group->lookup_table[i] = lookup_table;
+
+ /*
+ * Make sure that the hash function has changed
+ * from the starting value
+ */
+ hash_idx = start_hash_idx[i] + 1;
+ break;
+ }
+ hash_idx++;
+
+ } while (hash_idx != start_hash_idx[i]);
+
+ /* Failed to find perfect hash for this group */
+ if (hash_idx == start_hash_idx[i]) {
+ /*
+ * Restore previous hash_idx and lookup_table
+ * for all value bits
+ */
+ for (j = 0; j < i; j++) {
+ on_group->hash_idx[j] = start_hash_idx[j];
+ on_group->lookup_table[j] = start_lookup_table[j];
+ }
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+struct rte_efd_table *
+rte_efd_create(const char *name, uint32_t max_num_rules, uint32_t key_len,
+ uint8_t online_cpu_socket_bitmask, uint8_t offline_cpu_socket)
+{
+ struct rte_efd_table *table = NULL;
+ uint8_t *key_array = NULL;
+ uint32_t num_chunks, num_chunks_shift;
+ uint8_t socket_id;
+ struct rte_efd_list *efd_list = NULL;
+ struct rte_tailq_entry *te;
+ uint64_t offline_table_size;
+ char ring_name[RTE_RING_NAMESIZE];
+ struct rte_ring *r = NULL;
+ unsigned int i;
+
+ efd_list = RTE_TAILQ_CAST(rte_efd_tailq.head, rte_efd_list);
+
+ if (online_cpu_socket_bitmask == 0) {
+ RTE_LOG(ERR, EFD, "At least one CPU socket must be enabled "
+ "in the bitmask\n");
+ return NULL;
+ }
+
+ if (max_num_rules == 0) {
+ RTE_LOG(ERR, EFD, "Max num rules must be higher than 0\n");
+ return NULL;
+ }
+
+ /*
+ * Compute the minimum number of chunks (smallest power of 2)
+ * that can hold all of the rules
+ */
+ if (max_num_rules % EFD_TARGET_CHUNK_NUM_RULES == 0)
+ num_chunks = rte_align32pow2(max_num_rules /
+ EFD_TARGET_CHUNK_NUM_RULES);
+ else
+ num_chunks = rte_align32pow2((max_num_rules /
+ EFD_TARGET_CHUNK_NUM_RULES) + 1);
+
+ num_chunks_shift = log2(num_chunks);
+
+ rte_rwlock_write_lock(RTE_EAL_TAILQ_RWLOCK);
+
+ /*
+ * Guarantee there's no existing: this is normally already checked
+ * by ring creation above
+ */
+ TAILQ_FOREACH(te, efd_list, next)
+ {
+ table = (struct rte_efd_table *) te->data;
+ if (strncmp(name, table->name, RTE_EFD_NAMESIZE) == 0)
+ break;
+ }
+
+ table = NULL;
+ if (te != NULL) {
+ rte_errno = EEXIST;
+ te = NULL;
+ goto error_unlock_exit;
+ }
+
+ te = rte_zmalloc("EFD_TAILQ_ENTRY", sizeof(*te), 0);
+ if (te == NULL) {
+ RTE_LOG(ERR, EFD, "tailq entry allocation failed\n");
+ goto error_unlock_exit;
+ }
+
+ /* Create a new EFD table management structure */
+ table = (struct rte_efd_table *) rte_zmalloc_socket(NULL,
+ sizeof(struct rte_efd_table),
+ RTE_CACHE_LINE_SIZE,
+ offline_cpu_socket);
+ if (table == NULL) {
+ RTE_LOG(ERR, EFD, "Allocating EFD table management structure"
+ " on socket %u failed\n",
+ offline_cpu_socket);
+ goto error_unlock_exit;
+ }
+
+
+ RTE_LOG(DEBUG, EFD, "Allocated EFD table management structure "
+ "on socket %u\n", offline_cpu_socket);
+
+ table->max_num_rules = num_chunks * EFD_TARGET_CHUNK_MAX_NUM_RULES;
+ table->num_rules = 0;
+ table->num_chunks = num_chunks;
+ table->num_chunks_shift = num_chunks_shift;
+ table->key_len = key_len;
+
+ /* key_array */
+ key_array = (uint8_t *) rte_zmalloc_socket(NULL,
+ table->max_num_rules * table->key_len,
+ RTE_CACHE_LINE_SIZE,
+ offline_cpu_socket);
+ if (key_array == NULL) {
+ RTE_LOG(ERR, EFD, "Allocating key array"
+ " on socket %u failed\n",
+ offline_cpu_socket);
+ goto error_unlock_exit;
+ }
+ table->keys = key_array;
+ snprintf(table->name, sizeof(table->name), "%s", name);
+
+ RTE_LOG(DEBUG, EFD, "Creating an EFD table with %u chunks,"
+ " which potentially supports %u entries\n",
+ num_chunks, table->max_num_rules);
+
+ /* Make sure all the allocatable table pointers are NULL initially */
+ for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++)
+ table->chunks[socket_id] = NULL;
+ table->offline_chunks = NULL;
+
+ /*
+ * Allocate one online table per socket specified
+ * in the user-supplied bitmask
+ */
+ uint64_t online_table_size = num_chunks * sizeof(struct efd_online_chunk) +
+ EFD_NUM_CHUNK_PADDING_BYTES;
+
+ for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++) {
+ if ((online_cpu_socket_bitmask >> socket_id) & 0x01) {
+ /*
+ * Allocate all of the EFD table chunks (the online portion)
+ * as a continuous block
+ */
+ table->chunks[socket_id] =
+ (struct efd_online_chunk *) rte_zmalloc_socket(
+ NULL,
+ online_table_size,
+ RTE_CACHE_LINE_SIZE,
+ socket_id);
+ if (table->chunks[socket_id] == NULL) {
+ RTE_LOG(ERR, EFD,
+ "Allocating EFD online table on "
+ "socket %u failed\n",
+ socket_id);
+ goto error_unlock_exit;
+ }
+ RTE_LOG(DEBUG, EFD,
+ "Allocated EFD online table of size "
+ "%"PRIu64" bytes (%.2f MB) on socket %u\n",
+ online_table_size,
+ (float) online_table_size /
+ (1024.0F * 1024.0F),
+ socket_id);
+ }
+ }
+
+#if defined(RTE_ARCH_X86)
+ if (RTE_EFD_VALUE_NUM_BITS > 3 && rte_cpu_get_flag_enabled(RTE_CPUFLAG_AVX2))
+ table->cmp_fn = RTE_HASH_COMPARE_AVX2;
+ else
+#endif
+ table->cmp_fn = RTE_HASH_COMPARE_SCALAR;
+
+ /*
+ * Allocate the EFD table offline portion (with the actual rules
+ * mapping keys to values) as a continuous block.
+ * This could be several gigabytes of memory.
+ */
+ offline_table_size = num_chunks * sizeof(struct efd_offline_chunk_rules);
+ table->offline_chunks =
+ (struct efd_offline_chunk_rules *) rte_zmalloc_socket(NULL,
+ offline_table_size,
+ RTE_CACHE_LINE_SIZE,
+ offline_cpu_socket);
+ if (table->offline_chunks == NULL) {
+ RTE_LOG(ERR, EFD, "Allocating EFD offline table on socket %u "
+ "failed\n", offline_cpu_socket);
+ goto error_unlock_exit;
+ }
+
+ RTE_LOG(DEBUG, EFD,
+ "Allocated EFD offline table of size %"PRIu64" bytes "
+ " (%.2f MB) on socket %u\n", offline_table_size,
+ (float) offline_table_size / (1024.0F * 1024.0F),
+ offline_cpu_socket);
+
+ te->data = (void *) table;
+ TAILQ_INSERT_TAIL(efd_list, te, next);
+ rte_rwlock_write_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+ snprintf(ring_name, sizeof(ring_name), "HT_%s", table->name);
+ /* Create ring (Dummy slot index is not enqueued) */
+ r = rte_ring_create(ring_name, rte_align32pow2(table->max_num_rules),
+ offline_cpu_socket, 0);
+ if (r == NULL) {
+ RTE_LOG(ERR, EFD, "memory allocation failed\n");
+ goto error_unlock_exit;
+ }
+
+ /* Populate free slots ring. Entry zero is reserved for key misses. */
+ for (i = 0; i < table->max_num_rules; i++)
+ rte_ring_sp_enqueue(r, (void *) ((uintptr_t) i));
+
+ table->free_slots = r;
+ return table;
+
+error_unlock_exit:
+ rte_rwlock_write_unlock(RTE_EAL_TAILQ_RWLOCK);
+ rte_efd_free(table);
+
+ return NULL;
+}
+
+struct rte_efd_table *
+rte_efd_find_existing(const char *name)
+{
+ struct rte_efd_table *table = NULL;
+ struct rte_tailq_entry *te;
+ struct rte_efd_list *efd_list;
+
+ efd_list = RTE_TAILQ_CAST(rte_efd_tailq.head, rte_efd_list);
+
+ rte_rwlock_read_lock(RTE_EAL_TAILQ_RWLOCK);
+
+ TAILQ_FOREACH(te, efd_list, next)
+ {
+ table = (struct rte_efd_table *) te->data;
+ if (strncmp(name, table->name, RTE_EFD_NAMESIZE) == 0)
+ break;
+ }
+ rte_rwlock_read_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+ if (te == NULL) {
+ rte_errno = ENOENT;
+ return NULL;
+ }
+ return table;
+}
+
+void
+rte_efd_free(struct rte_efd_table *table)
+{
+ uint8_t socket_id;
+
+ if (table == NULL)
+ return;
+
+ for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++)
+ rte_free(table->chunks[socket_id]);
+
+ rte_ring_free(table->free_slots);
+ rte_free(table->offline_chunks);
+ rte_free(table->keys);
+ rte_free(table);
+}
+
+/**
+ * Applies a previously computed table entry to the specified table for all
+ * socket-local copies of the online table.
+ * Intended to apply an update for only a single change
+ * to a key/value pair at a time
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing values (ideally caller's socket id)
+ * @param chunk_id
+ * Chunk index to update
+ * @param group_id
+ * Group index to update
+ * @param bin_id
+ * Bin within the group that this update affects
+ * @param new_bin_choice
+ * Newly chosen permutation which this bin should use - only lower 2 bits
+ * @param new_group_entry
+ * Previously computed updated chunk/group entry
+ */
+static inline void
+efd_apply_update(struct rte_efd_table * const table, const unsigned int socket_id,
+ const uint32_t chunk_id, const uint32_t group_id,
+ const uint32_t bin_id, const uint8_t new_bin_choice,
+ const struct efd_online_group_entry * const new_group_entry)
+{
+ int i;
+ struct efd_online_chunk *chunk = &table->chunks[socket_id][chunk_id];
+ uint8_t bin_index = bin_id / EFD_CHUNK_NUM_BIN_TO_GROUP_SETS;
+
+ /*
+ * Grab the current byte that contains the choices
+ * for four neighboring bins
+ */
+ uint8_t choice_chunk =
+ chunk->bin_choice_list[bin_index];
+
+
+ /* Compute the offset into the chunk that needs to be updated */
+ int offset = (bin_id & 0x3) * 2;
+
+ /* Zero the two bits of interest and set them to new_bin_choice */
+ choice_chunk = (choice_chunk & (~(0x03 << offset)))
+ | ((new_bin_choice & 0x03) << offset);
+
+ /* Update the online table with the new data across all sockets */
+ for (i = 0; i < RTE_MAX_NUMA_NODES; i++) {
+ if (table->chunks[i] != NULL) {
+ memcpy(&(table->chunks[i][chunk_id].groups[group_id]),
+ new_group_entry,
+ sizeof(struct efd_online_group_entry));
+ table->chunks[i][chunk_id].bin_choice_list[bin_index] =
+ choice_chunk;
+ }
+ }
+}
+
+/*
+ * Move the bin from prev group to the new group
+ */
+static inline void
+move_groups(uint32_t bin_id, uint8_t bin_size,
+ struct efd_offline_group_rules *new_group,
+ struct efd_offline_group_rules * const current_group)
+{
+
+ uint8_t empty_idx = 0;
+ unsigned int i;
+
+ if (new_group == current_group)
+ return;
+
+ for (i = 0; i < current_group->num_rules; i++) {
+ /*
+ * Move keys that belong to the same bin
+ * to the new group
+ */
+ if (current_group->bin_id[i] == bin_id) {
+ new_group->key_idx[new_group->num_rules] =
+ current_group->key_idx[i];
+ new_group->value[new_group->num_rules] =
+ current_group->value[i];
+ new_group->bin_id[new_group->num_rules] =
+ current_group->bin_id[i];
+ new_group->num_rules++;
+ } else {
+ if (i != empty_idx) {
+ /*
+ * Need to move this key towards
+ * the top of the array
+ */
+ current_group->key_idx[empty_idx] =
+ current_group->key_idx[i];
+ current_group->value[empty_idx] =
+ current_group->value[i];
+ current_group->bin_id[empty_idx] =
+ current_group->bin_id[i];
+ }
+ empty_idx++;
+ }
+
+ }
+ current_group->num_rules -= bin_size;
+}
+
+/*
+ * Revert group/s to their previous state before
+ * trying to insert/add a new key
+ */
+static inline void
+revert_groups(struct efd_offline_group_rules *previous_group,
+ struct efd_offline_group_rules *current_group, uint8_t bin_size)
+{
+ unsigned int i;
+
+ if (current_group == previous_group)
+ return;
+
+ /* Move keys back to previous group */
+ for (i = current_group->num_rules - bin_size;
+ i < current_group->num_rules; i++) {
+ previous_group->key_idx[previous_group->num_rules] =
+ current_group->key_idx[i];
+ previous_group->value[previous_group->num_rules] =
+ current_group->value[i];
+ previous_group->bin_id[previous_group->num_rules] =
+ current_group->bin_id[i];
+ previous_group->num_rules++;
+ }
+
+ /*
+ * Decrease number of rules after the move
+ * in the new group
+ */
+ current_group->num_rules -= bin_size;
+}
+
+/**
+ * Computes an updated table entry where the supplied key points to a new host.
+ * If no entry exists, one is inserted.
+ *
+ * This function does NOT modify the online table(s)
+ * This function DOES modify the offline table
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing values (ideally caller's socket id)
+ * @param key
+ * Key to insert
+ * @param value
+ * Value to associate with key
+ * @param chunk_id
+ * Chunk ID of the chunk that was modified
+ * @param group_id
+ * Group ID of the group that was modified
+ * @param bin_id
+ * Bin ID that was modified
+ * @param new_bin_choice
+ * Newly chosen permutation which this bin will use
+ * @param entry
+ * Newly computed online entry to apply later with efd_apply_update
+ *
+ * @return
+ * RTE_EFD_UPDATE_WARN_GROUP_FULL
+ * Operation is insert, and the last available space in the
+ * key's group was just used. Future inserts may fail as groups fill up.
+ * This operation was still successful, and entry contains a valid update
+ * RTE_EFD_UPDATE_FAILED
+ * Either the EFD failed to find a suitable perfect hash or the group was full
+ * This is a fatal error, and the table is now in an indeterminite state
+ * RTE_EFD_UPDATE_NO_CHANGE
+ * Operation resulted in no change to the table (same value already exists)
+ * 0
+ * Insert or update was successful, and the new efd_online_group_entry
+ * is stored in *entry
+ *
+ * @warning
+ * Note that entry will be UNCHANGED if the update has no effect, and thus any
+ * subsequent use of the entry content will likely be invalid
+ */
+static inline int
+efd_compute_update(struct rte_efd_table * const table,
+ const unsigned int socket_id, const void *key,
+ const efd_value_t value, uint32_t * const chunk_id,
+ uint32_t * const group_id, uint32_t * const bin_id,
+ uint8_t * const new_bin_choice,
+ struct efd_online_group_entry * const entry)
+{
+ unsigned int i;
+ int ret;
+ uint32_t new_idx;
+ void *new_k, *slot_id = NULL;
+ int status = EXIT_SUCCESS;
+ unsigned int found = 0;
+
+ efd_compute_ids(table, key, chunk_id, bin_id);
+
+ struct efd_offline_chunk_rules * const chunk =
+ &table->offline_chunks[*chunk_id];
+ struct efd_offline_group_rules *new_group;
+
+ uint8_t current_choice = efd_get_choice(table, socket_id,
+ *chunk_id, *bin_id);
+ uint32_t current_group_id = efd_bin_to_group[current_choice][*bin_id];
+ struct efd_offline_group_rules * const current_group =
+ &chunk->group_rules[current_group_id];
+ uint8_t bin_size = 0;
+ uint8_t key_changed_index = 0;
+ efd_value_t key_changed_previous_value = 0;
+ uint32_t key_idx_previous = 0;
+
+ /* Scan the current group and see if the key is already present */
+ for (i = 0; i < current_group->num_rules; i++) {
+ if (current_group->bin_id[i] == *bin_id)
+ bin_size++;
+ else
+ continue;
+
+ void *key_stored = EFD_KEY(current_group->key_idx[i], table);
+ if (found == 0 && unlikely(memcmp(key_stored, key,
+ table->key_len) == 0)) {
+ /* Key is already present */
+
+ /*
+ * If previous value is same as new value,
+ * no additional work is required
+ */
+ if (current_group->value[i] == value)
+ return RTE_EFD_UPDATE_NO_CHANGE;
+
+ key_idx_previous = current_group->key_idx[i];
+ key_changed_previous_value = current_group->value[i];
+ key_changed_index = i;
+ current_group->value[i] = value;
+ found = 1;
+ }
+ }
+
+ if (found == 0) {
+ /* Key does not exist. Insert the rule into the bin/group */
+ if (unlikely(current_group->num_rules >= EFD_MAX_GROUP_NUM_RULES)) {
+ RTE_LOG(ERR, EFD,
+ "Fatal: No room remaining for insert into "
+ "chunk %u group %u bin %u\n",
+ *chunk_id,
+ current_group_id, *bin_id);
+ return RTE_EFD_UPDATE_FAILED;
+ }
+
+ if (unlikely(current_group->num_rules ==
+ (EFD_MAX_GROUP_NUM_RULES - 1))) {
+ RTE_LOG(INFO, EFD, "Warn: Insert into last "
+ "available slot in chunk %u "
+ "group %u bin %u\n", *chunk_id,
+ current_group_id, *bin_id);
+ status = RTE_EFD_UPDATE_WARN_GROUP_FULL;
+ }
+
+ if (rte_ring_sc_dequeue(table->free_slots, &slot_id) != 0)
+ return RTE_EFD_UPDATE_FAILED;
+
+ new_k = RTE_PTR_ADD(table->keys, (uintptr_t) slot_id *
+ table->key_len);
+ rte_prefetch0(new_k);
+ new_idx = (uint32_t) ((uintptr_t) slot_id);
+
+ rte_memcpy(EFD_KEY(new_idx, table), key, table->key_len);
+ current_group->key_idx[current_group->num_rules] = new_idx;
+ current_group->value[current_group->num_rules] = value;
+ current_group->bin_id[current_group->num_rules] = *bin_id;
+ current_group->num_rules++;
+ table->num_rules++;
+ bin_size++;
+ } else {
+ uint32_t last = current_group->num_rules - 1;
+ /* Swap the key with the last key inserted*/
+ current_group->key_idx[key_changed_index] =
+ current_group->key_idx[last];
+ current_group->value[key_changed_index] =
+ current_group->value[last];
+ current_group->bin_id[key_changed_index] =
+ current_group->bin_id[last];
+
+ /*
+ * Key to be updated will always be available
+ * at the end of the group
+ */
+ current_group->key_idx[last] = key_idx_previous;
+ current_group->value[last] = value;
+ current_group->bin_id[last] = *bin_id;
+ }
+
+ *new_bin_choice = current_choice;
+ *group_id = current_group_id;
+ new_group = current_group;
+
+ /* Group need to be rebalanced when it starts to get loaded */
+ if (current_group->num_rules > EFD_MIN_BALANCED_NUM_RULES) {
+
+ /*
+ * Subtract the number of entries in the bin from
+ * the original group
+ */
+ current_group->num_rules -= bin_size;
+
+ /*
+ * Figure out which of the available groups that this bin
+ * can map to is the smallest (using the current group
+ * as baseline)
+ */
+ uint8_t smallest_choice = current_choice;
+ uint8_t smallest_size = current_group->num_rules;
+ uint32_t smallest_group_id = current_group_id;
+ unsigned char choice;
+
+ for (choice = 0; choice < EFD_CHUNK_NUM_BIN_TO_GROUP_SETS;
+ choice++) {
+ uint32_t test_group_id =
+ efd_bin_to_group[choice][*bin_id];
+ uint32_t num_rules =
+ chunk->group_rules[test_group_id].num_rules;
+ if (num_rules < smallest_size) {
+ smallest_choice = choice;
+ smallest_size = num_rules;
+ smallest_group_id = test_group_id;
+ }
+ }
+
+ *new_bin_choice = smallest_choice;
+ *group_id = smallest_group_id;
+ new_group = &chunk->group_rules[smallest_group_id];
+ current_group->num_rules += bin_size;
+
+ }
+
+ uint8_t choice = 0;
+ for (;;) {
+ if (current_group != new_group &&
+ new_group->num_rules + bin_size >
+ EFD_MAX_GROUP_NUM_RULES) {
+ RTE_LOG(DEBUG, EFD,
+ "Unable to move_groups to dest group "
+ "containing %u entries."
+ "bin_size:%u choice:%02x\n",
+ new_group->num_rules, bin_size,
+ choice - 1);
+ goto next_choice;
+ }
+ move_groups(*bin_id, bin_size, new_group, current_group);
+ /*
+ * Recompute the hash function for the modified group,
+ * and return it to the caller
+ */
+ ret = efd_search_hash(table, new_group, entry);
+
+ if (!ret)
+ return status;
+
+ RTE_LOG(DEBUG, EFD,
+ "Failed to find perfect hash for group "
+ "containing %u entries. bin_size:%u choice:%02x\n",
+ new_group->num_rules, bin_size, choice - 1);
+ /* Restore groups modified to their previous state */
+ revert_groups(current_group, new_group, bin_size);
+
+next_choice:
+ if (choice == EFD_CHUNK_NUM_BIN_TO_GROUP_SETS)
+ break;
+ *new_bin_choice = choice;
+ *group_id = efd_bin_to_group[choice][*bin_id];
+ new_group = &chunk->group_rules[*group_id];
+ choice++;
+ }
+
+ if (!found) {
+ current_group->num_rules--;
+ table->num_rules--;
+ } else
+ current_group->value[current_group->num_rules - 1] =
+ key_changed_previous_value;
+ return RTE_EFD_UPDATE_FAILED;
+}
+
+int
+rte_efd_update(struct rte_efd_table * const table, const unsigned int socket_id,
+ const void *key, const efd_value_t value)
+{
+ uint32_t chunk_id = 0, group_id = 0, bin_id = 0;
+ uint8_t new_bin_choice = 0;
+ struct efd_online_group_entry entry;
+
+ int status = efd_compute_update(table, socket_id, key, value,
+ &chunk_id, &group_id, &bin_id,
+ &new_bin_choice, &entry);
+
+ if (status == RTE_EFD_UPDATE_NO_CHANGE)
+ return EXIT_SUCCESS;
+
+ if (status == RTE_EFD_UPDATE_FAILED)
+ return status;
+
+ efd_apply_update(table, socket_id, chunk_id, group_id, bin_id,
+ new_bin_choice, &entry);
+ return status;
+}
+
+int
+rte_efd_delete(struct rte_efd_table * const table, const unsigned int socket_id,
+ const void *key, efd_value_t * const prev_value)
+{
+ unsigned int i;
+ uint32_t chunk_id, bin_id;
+ uint8_t not_found = 1;
+
+ efd_compute_ids(table, key, &chunk_id, &bin_id);
+
+ struct efd_offline_chunk_rules * const chunk =
+ &table->offline_chunks[chunk_id];
+
+ uint8_t current_choice = efd_get_choice(table, socket_id,
+ chunk_id, bin_id);
+ uint32_t current_group_id = efd_bin_to_group[current_choice][bin_id];
+ struct efd_offline_group_rules * const current_group =
+ &chunk->group_rules[current_group_id];
+
+ /*
+ * Search the current group for the specified key.
+ * If it exists, remove it and re-pack the other values
+ */
+ for (i = 0; i < current_group->num_rules; i++) {
+ if (not_found) {
+ /* Found key that needs to be removed */
+ if (memcmp(EFD_KEY(current_group->key_idx[i], table),
+ key, table->key_len) == 0) {
+ /* Store previous value if requested by caller */
+ if (prev_value != NULL)
+ *prev_value = current_group->value[i];
+
+ not_found = 0;
+ rte_ring_sp_enqueue(table->free_slots,
+ (void *)((uintptr_t)current_group->key_idx[i]));
+ }
+ } else {
+ /*
+ * If the desired key has been found,
+ * need to shift other values up one
+ */
+
+ /* Need to shift this entry back up one index */
+ current_group->key_idx[i - 1] = current_group->key_idx[i];
+ current_group->value[i - 1] = current_group->value[i];
+ current_group->bin_id[i - 1] = current_group->bin_id[i];
+ }
+ }
+
+ if (not_found == 0) {
+ table->num_rules--;
+ current_group->num_rules--;
+ }
+
+ return not_found;
+}
+
+
+#if (RTE_EFD_VALUE_NUM_BITS == 8 || RTE_EFD_VALUE_NUM_BITS == 16 || \
+ RTE_EFD_VALUE_NUM_BITS == 24 || RTE_EFD_VALUE_NUM_BITS == 32)
+#define EFD_LOAD_SI128(val) _mm_load_si128(val)
+#else
+#define EFD_LOAD_SI128(val) _mm_lddqu_si128(val)
+#endif
+
+static inline efd_value_t
+efd_lookup_internal(const struct efd_online_group_entry * const group,
+ const uint32_t hash_val_a, const uint32_t hash_val_b,
+ enum rte_efd_compare_function cmp_fn)
+{
+ efd_value_t value = 0;
+ uint32_t i;
+
+ switch (cmp_fn) {
+#ifdef RTE_MACHINE_CPUFLAG_AVX2
+ case RTE_HASH_COMPARE_AVX2:
+
+ i = 0;
+ __m256i vhash_val_a = _mm256_set1_epi32(hash_val_a);
+ __m256i vhash_val_b = _mm256_set1_epi32(hash_val_b);
+
+ for (; i < RTE_EFD_VALUE_NUM_BITS; i += 8) {
+ __m256i vhash_idx =
+ _mm256_cvtepu16_epi32(EFD_LOAD_SI128(
+ (__m128i const *) &group->hash_idx[i]));
+ __m256i vlookup_table = _mm256_cvtepu16_epi32(
+ EFD_LOAD_SI128((__m128i const *)
+ &group->lookup_table[i]));
+ __m256i vhash = _mm256_add_epi32(vhash_val_a,
+ _mm256_mullo_epi32(vhash_idx, vhash_val_b));
+ __m256i vbucket_idx = _mm256_srli_epi32(vhash,
+ EFD_LOOKUPTBL_SHIFT);
+ __m256i vresult = _mm256_srlv_epi32(vlookup_table,
+ vbucket_idx);
+
+ value |= (_mm256_movemask_ps(
+ (__m256) _mm256_slli_epi32(vresult, 31))
+ & ((1 << (RTE_EFD_VALUE_NUM_BITS - i)) - 1)) << i;
+ }
+ break;
+#endif
+ default:
+
+ i = 0;
+ for (; i < RTE_EFD_VALUE_NUM_BITS; i++) {
+ value <<= 1;
+
+ uint32_t h = hash_val_a + (hash_val_b *
+ group->hash_idx[RTE_EFD_VALUE_NUM_BITS - i - 1]);
+ uint16_t bucket_idx = h >> EFD_LOOKUPTBL_SHIFT;
+
+ value |= (group->lookup_table[
+ RTE_EFD_VALUE_NUM_BITS - i - 1] >>
+ bucket_idx) & 0x1;
+ }
+ }
+ return value;
+}
+
+efd_value_t
+rte_efd_lookup(const struct rte_efd_table * const table,
+ const unsigned int socket_id, const void *key)
+{
+ uint32_t chunk_id, group_id, bin_id;
+ uint8_t bin_choice;
+ const struct efd_online_group_entry *group;
+ const struct efd_online_chunk * const chunks = table->chunks[socket_id];
+
+ /* Determine the chunk and group location for the given key */
+ efd_compute_ids(table, key, &chunk_id, &bin_id);
+ bin_choice = efd_get_choice(table, socket_id, chunk_id, bin_id);
+ group_id = efd_bin_to_group[bin_choice][bin_id];
+ group = &chunks[chunk_id].groups[group_id];
+
+ return efd_lookup_internal(group,
+ EFD_HASHFUNCA(key, table),
+ EFD_HASHFUNCB(key, table),
+ table->cmp_fn);
+}
+
+void rte_efd_lookup_bulk(const struct rte_efd_table * const table,
+ const unsigned int socket_id, const int num_keys,
+ const void **key_list, efd_value_t * const value_list)
+{
+ int i;
+ uint32_t chunk_id_list[RTE_EFD_BURST_MAX];
+ uint32_t bin_id_list[RTE_EFD_BURST_MAX];
+ uint8_t bin_choice_list[RTE_EFD_BURST_MAX];
+ uint32_t group_id_list[RTE_EFD_BURST_MAX];
+ struct efd_online_group_entry *group;
+
+ struct efd_online_chunk *chunks = table->chunks[socket_id];
+
+ for (i = 0; i < num_keys; i++) {
+ efd_compute_ids(table, key_list[i], &chunk_id_list[i],
+ &bin_id_list[i]);
+ rte_prefetch0(&chunks[chunk_id_list[i]].bin_choice_list);
+ }
+
+ for (i = 0; i < num_keys; i++) {
+ bin_choice_list[i] = efd_get_choice(table, socket_id,
+ chunk_id_list[i], bin_id_list[i]);
+ group_id_list[i] =
+ efd_bin_to_group[bin_choice_list[i]][bin_id_list[i]];
+ group = &chunks[chunk_id_list[i]].groups[group_id_list[i]];
+ rte_prefetch0(group);
+ }
+
+ for (i = 0; i < num_keys; i++) {
+ group = &chunks[chunk_id_list[i]].groups[group_id_list[i]];
+ value_list[i] = efd_lookup_internal(group,
+ EFD_HASHFUNCA(key_list[i], table),
+ EFD_HASHFUNCB(key_list[i], table),
+ table->cmp_fn);
+ }
+}
diff --git a/lib/librte_efd/rte_efd.h b/lib/librte_efd/rte_efd.h
new file mode 100644
index 0000000..a728096
--- /dev/null
+++ b/lib/librte_efd/rte_efd.h
@@ -0,0 +1,294 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _RTE_EFD_H_
+#define _RTE_EFD_H_
+
+/**
+ * @file
+ *
+ * RTE EFD Table
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*************************************************************************
+ * User selectable constants
+ *************************************************************************/
+
+/*
+ * If possible, best lookup performance will be achieved by ensuring that
+ * the entire table fits in the L3 cache.
+ *
+ * Some formulas for calculating various sizes are listed below:
+ *
+ * # of chunks =
+ * 2 ^ (ceiling(log2((requested # of rules) /
+ * (EFD_CHUNK_NUM_GROUPS * EFD_TARGET_GROUP_NUM_RULES))))
+ *
+ * Target # of rules = (# of chunks) * EFD_CHUNK_NUM_GROUPS *
+ * EFD_TARGET_GROUP_NUM_RULES
+ *
+ * Group Size (in bytes) = 4 (per value bit)
+ *
+ * Table size (in bytes) = RTE_EFD_VALUE_NUM_BITS * (# of chunks) *
+ * EFD_CHUNK_NUM_GROUPS * (group size)
+ */
+
+/**
+ * !!! This parameter should be adjusted for your application !!!
+ *
+ * This parameter adjusts the number of bits of value that can be
+ * stored in the table.
+ * For example, setting the number of bits to 3 will allow storing 8 values
+ * in the table (between 0 and 7).
+ *
+ * This number directly affects the performance of both lookups and insertion.
+ * In general, performance decreases as more bits are stored in the table.
+ *
+ * This number is directly proportional to the size of the online region
+ * used for lookups.
+ *
+ * Note that due to the way the CPU operates on memory, best lookup performance
+ * will be achieved when RTE_EFD_VALUE_NUM_BITS is a multiple of 8.
+ * These values align the hash indexes on 16-byte boundaries.
+ * The greatest performance drop is moving from 8->9 bits, 16->17 bits, etc.
+ *
+ * This value must be between 1 and 32
+ */
+#ifndef RTE_EFD_VALUE_NUM_BITS
+#define RTE_EFD_VALUE_NUM_BITS (8)
+#endif
+
+/*
+ * EFD_TARGET_GROUP_NUM_RULES:
+ * Adjusts how many groups/chunks are allocated at table creation time
+ * to support the requested number of rules. Higher values pack entries
+ * more tightly in memory, resulting in a smaller memory footprint
+ * for the online table.
+ * This comes at the cost of lower insert/update performance.
+ *
+ * EFD_MAX_GROUP_NUM_RULES:
+ * This adjusts the amount of offline memory allocated to store key/value
+ * pairs for the table. The recommended numbers are upper-bounds for
+ * this parameter
+ * - any higher and it becomes very unlikely that a perfect hash function
+ * can be found for that group size. This value should be at
+ * least 40% larger than EFD_TARGET_GROUP_NUM_RULES
+ *
+ * Recommended values for various lookuptable and hashfunc sizes are:
+ *
+ * HASH_FUNC_SIZE = 16, LOOKUPTBL_SIZE = 16:
+ * EFD_TARGET_GROUP_NUM_RULES = 22
+ * EFD_MAX_GROUP_NUM_RULES = 28
+ */
+#define EFD_TARGET_GROUP_NUM_RULES (22)
+#define EFD_MAX_GROUP_NUM_RULES (28LU)
+
+#define EFD_MIN_BALANCED_NUM_RULES 5
+
+/**
+ * Maximum number of keys that can be looked up in one call to efd_lookup_bulk
+ */
+#ifndef RTE_EFD_BURST_MAX
+#define RTE_EFD_BURST_MAX (32)
+#endif
+
+/** Maximum number of characters in efd name.*/
+#define RTE_EFD_NAMESIZE 32
+
+#if (RTE_EFD_VALUE_NUM_BITS > 0 && RTE_EFD_VALUE_NUM_BITS <= 8)
+typedef uint8_t efd_value_t;
+#elif (RTE_EFD_VALUE_NUM_BITS > 8 && RTE_EFD_VALUE_NUM_BITS <= 16)
+typedef uint16_t efd_value_t;
+#elif (RTE_EFD_VALUE_NUM_BITS > 16 && RTE_EFD_VALUE_NUM_BITS <= 32)
+typedef uint32_t efd_value_t;
+#else
+#error("RTE_EFD_VALUE_NUM_BITS must be in the range [1:32]")
+#endif
+
+/**
+ * Creates an EFD table with a single offline region and multiple per-socket
+ * internally-managed copies of the online table used for lookups
+ *
+ * @param name
+ * EFD table name
+ * @param max_num_rules
+ * Minimum number of rules the table should be sized to hold.
+ * Will be rounded up to the next smallest valid table size
+ * @param online_cpu_socket_bitmask
+ * Bitmask specifying which sockets should get a copy of the online table.
+ * LSB = socket 0, etc.
+ * @param offline_cpu_socket
+ * Identifies the socket where the offline table will be allocated
+ * (and most efficiently accessed in the case of updates/insertions)
+ *
+ * @return
+ * EFD table, or NULL if table allocation failed or the bitmask is invalid
+ */
+struct rte_efd_table *
+rte_efd_create(const char *name, uint32_t max_num_rules, uint32_t key_len,
+ uint8_t online_cpu_socket_bitmask, uint8_t offline_cpu_socket);
+
+/**
+ * Releases the resources from an EFD table
+ *
+ * @param table
+ * Table to free
+ */
+void
+rte_efd_free(struct rte_efd_table *table);
+
+/**
+ * Find an existing EFD table object and return a pointer to it.
+ *
+ * @param name
+ * Name of the EFD table as passed to rte_efd_create()
+ * @return
+ * Pointer to EFD table or NULL if object not found
+ * with rte_errno set appropriately. Possible rte_errno values include:
+ * - ENOENT - value not available for return
+ */
+struct rte_efd_table*
+rte_efd_find_existing(const char *name);
+
+#define RTE_EFD_UPDATE_WARN_GROUP_FULL (1)
+#define RTE_EFD_UPDATE_NO_CHANGE (2)
+#define RTE_EFD_UPDATE_FAILED (3)
+
+/**
+ * Computes an updated table entry for the supplied key/value pair.
+ * The update is then immediately applied to the provided table and
+ * all socket-local copies of the chunks are updated.
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param key
+ * EFD table key to modify
+ * @param value
+ * Value to associate with the key
+ *
+ * @return
+ * RTE_EFD_UPDATE_WARN_GROUP_FULL
+ * Operation is insert, and the last available space in the
+ * key's group was just used
+ * Future inserts may fail as groups fill up
+ * This operation was still successful, and entry contains a valid update
+ * RTE_EFD_UPDATE_FAILED
+ * Either the EFD failed to find a suitable perfect hash or the group was full
+ * This is a fatal error, and the table is now in an indeterminite state
+ * RTE_EFD_UPDATE_NO_CHANGE
+ * Operation resulted in no change to the table (same value already exists)
+ * 0 - success
+ */
+int
+rte_efd_update(struct rte_efd_table *table, unsigned int socket_id,
+ const void *key, efd_value_t value);
+
+/**
+ * Removes any value currently associated with the specified key from the table
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param key
+ * EFD table key to delete
+ * @param prev_value
+ * If not NULL, will store the previous value here before deleting it
+ *
+ * @return
+ * 0 - successfully found and deleted the key
+ * nonzero otherwise
+ */
+int
+rte_efd_delete(struct rte_efd_table *table, unsigned int socket_id,
+ const void *key, efd_value_t *prev_value);
+
+/**
+ * Looks up the value associated with a key
+ *
+ * NOTE: Lookups will *always* succeed - this is a property of
+ * using a perfect hash table.
+ * If the specified key was never inserted, a pseudorandom answer will be returned.
+ * There is no way to know based on the lookup if the key was ever inserted
+ * originally, so this must be tracked elsewhere.
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param key
+ * EFD table key to look up
+ *
+ * @return
+ * Value associated with the key, or random junk if they key was never inserted
+ */
+efd_value_t
+rte_efd_lookup(const struct rte_efd_table *table, unsigned int socket_id,
+ const void *key);
+
+/**
+ * Looks up the value associated with several keys.
+ *
+ * NOTE: Lookups will *always* succeed - this is a property of
+ * using a perfect hash table.
+ * If the specified key was never inserted, a pseudorandom answer will be returned.
+ * There is no way to know based on the lookup if the key was ever inserted
+ * originally, so this must be tracked elsewhere.
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param num_keys
+ * Number of keys in the key_list array, must be less than RTE_EFD_BURST_MAX
+ * @param key_list
+ * Array of num_keys pointers which point to keys to look up
+ * @param value_list
+ * Array of size num_keys where lookup values will be stored
+ */
+void
+rte_efd_lookup_bulk(const struct rte_efd_table *table, unsigned int socket_id,
+ int num_keys, const void **key_list,
+ efd_value_t *value_list);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RTE_EFD_H_ */
diff --git a/lib/librte_efd/rte_efd_version.map b/lib/librte_efd/rte_efd_version.map
new file mode 100644
index 0000000..91810b3
--- /dev/null
+++ b/lib/librte_efd/rte_efd_version.map
@@ -0,0 +1,12 @@
+DPDK_17.02 {
+ global:
+
+ rte_efd_create;
+ rte_efd_delete;
+ rte_efd_free;
+ rte_efd_lookup;
+ rte_efd_lookup_bulk;
+ rte_efd_update;
+
+ local: *;
+};
diff --git a/mk/rte.app.mk b/mk/rte.app.mk
index f75f0e2..ed1e68a 100644
--- a/mk/rte.app.mk
+++ b/mk/rte.app.mk
@@ -1,6 +1,6 @@
# BSD LICENSE
#
-# Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+# Copyright(c) 2010-2017 Intel Corporation. All rights reserved.
# Copyright(c) 2014-2015 6WIND S.A.
# All rights reserved.
#
@@ -86,6 +86,7 @@ _LDLIBS-y += --whole-archive
_LDLIBS-$(CONFIG_RTE_LIBRTE_TIMER) += -lrte_timer
_LDLIBS-$(CONFIG_RTE_LIBRTE_HASH) += -lrte_hash
+_LDLIBS-$(CONFIG_RTE_LIBRTE_EFD) += -lrte_efd
_LDLIBS-$(CONFIG_RTE_LIBRTE_VHOST) += -lrte_vhost
_LDLIBS-$(CONFIG_RTE_LIBRTE_KVARGS) += -lrte_kvargs
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v4 2/5] app/test: add EFD functional and perf tests
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 0/5] Elastic Flow Distributor Pablo de Lara
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 1/5] efd: new Elastic Flow Distributor library Pablo de Lara
@ 2017-01-15 12:04 ` Pablo de Lara
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 3/5] examples/flow_distributor: sample app to demonstrate EFD usage Pablo de Lara
` (3 subsequent siblings)
5 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-15 12:04 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Byron Marohn, Karla Saur, Saikrishna Edupuganti
Signed-off-by: Byron Marohn <byron.marohn@intel.com>
Signed-off-by: Karla Saur <karla.saur@intel.com>
Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 1 +
app/test/Makefile | 5 +-
app/test/test_efd.c | 494 +++++++++++++++++++++++++++++++++++++++++++++++
app/test/test_efd_perf.c | 407 ++++++++++++++++++++++++++++++++++++++
4 files changed, 906 insertions(+), 1 deletion(-)
create mode 100644 app/test/test_efd.c
create mode 100644 app/test/test_efd_perf.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 9c60d67..d812962 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -532,6 +532,7 @@ EFD
M: Byron Marohn <byron.marohn@intel.com>
M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
F: lib/librte_efd/
+F: app/test/test_efd*
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
diff --git a/app/test/Makefile b/app/test/Makefile
index 5be023a..9de301f 100644
--- a/app/test/Makefile
+++ b/app/test/Makefile
@@ -1,6 +1,6 @@
# BSD LICENSE
#
-# Copyright(c) 2010-2015 Intel Corporation. All rights reserved.
+# Copyright(c) 2010-2017 Intel Corporation. All rights reserved.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -123,6 +123,9 @@ SRCS-y += test_logs.c
SRCS-y += test_memcpy.c
SRCS-y += test_memcpy_perf.c
+SRCS-$(CONFIG_RTE_LIBRTE_EFD) += test_efd.c
+SRCS-$(CONFIG_RTE_LIBRTE_EFD) += test_efd_perf.c
+
SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_hash.c
SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_thash.c
SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_hash_perf.c
diff --git a/app/test/test_efd.c b/app/test/test_efd.c
new file mode 100644
index 0000000..d5c3bd9
--- /dev/null
+++ b/app/test/test_efd.c
@@ -0,0 +1,494 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rte_memcpy.h>
+#include <rte_malloc.h>
+#include <rte_efd.h>
+#include <rte_byteorder.h>
+#include <rte_random.h>
+#include <rte_debug.h>
+#include <rte_ip.h>
+
+#include "test.h"
+
+#define EFD_TEST_KEY_LEN 8
+#define TABLE_SIZE (1 << 21)
+#define ITERATIONS 3
+static unsigned int test_socket_id;
+
+/* 5-tuple key type */
+struct flow_key {
+ uint32_t ip_src;
+ uint32_t ip_dst;
+ uint16_t port_src;
+ uint16_t port_dst;
+ uint8_t proto;
+} __attribute__((packed));
+/*
+ * Print out result of unit test efd operation.
+ */
+#if defined(UNIT_TEST_EFD_VERBOSE)
+
+static void print_key_info(const char *msg, const struct flow_key *key,
+ efd_value_t val)
+{
+ const uint8_t *p = (const uint8_t *) key;
+ unsigned int i;
+
+ printf("%s key:0x", msg);
+ for (i = 0; i < sizeof(struct flow_key); i++)
+ printf("%02X", p[i]);
+
+ printf(" @ val %d\n", val);
+}
+#else
+
+static void print_key_info(__attribute__((unused)) const char *msg,
+ __attribute__((unused)) const struct flow_key *key,
+ __attribute__((unused)) efd_value_t val)
+{
+}
+#endif
+
+/* Keys used by unit test functions */
+static struct flow_key keys[5] = {
+ {
+ .ip_src = IPv4(0x03, 0x02, 0x01, 0x00),
+ .ip_dst = IPv4(0x07, 0x06, 0x05, 0x04),
+ .port_src = 0x0908,
+ .port_dst = 0x0b0a,
+ .proto = 0x0c,
+ },
+ {
+ .ip_src = IPv4(0x13, 0x12, 0x11, 0x10),
+ .ip_dst = IPv4(0x17, 0x16, 0x15, 0x14),
+ .port_src = 0x1918,
+ .port_dst = 0x1b1a,
+ .proto = 0x1c,
+ },
+ {
+ .ip_src = IPv4(0x23, 0x22, 0x21, 0x20),
+ .ip_dst = IPv4(0x27, 0x26, 0x25, 0x24),
+ .port_src = 0x2928,
+ .port_dst = 0x2b2a,
+ .proto = 0x2c,
+ },
+ {
+ .ip_src = IPv4(0x33, 0x32, 0x31, 0x30),
+ .ip_dst = IPv4(0x37, 0x36, 0x35, 0x34),
+ .port_src = 0x3938,
+ .port_dst = 0x3b3a,
+ .proto = 0x3c,
+ },
+ {
+ .ip_src = IPv4(0x43, 0x42, 0x41, 0x40),
+ .ip_dst = IPv4(0x47, 0x46, 0x45, 0x44),
+ .port_src = 0x4948,
+ .port_dst = 0x4b4a,
+ .proto = 0x4c,
+ }
+};
+/* Array to store the data */
+efd_value_t data[5];
+
+static inline uint8_t efd_get_all_sockets_bitmask(void)
+{
+ uint8_t all_cpu_sockets_bitmask = 0;
+ unsigned int i;
+ unsigned int next_lcore = rte_get_master_lcore();
+ const int val_true = 1, val_false = 0;
+ for (i = 0; i < rte_lcore_count(); i++) {
+ all_cpu_sockets_bitmask |= 1 << rte_lcore_to_socket_id(next_lcore);
+ next_lcore = rte_get_next_lcore(next_lcore, val_false, val_true);
+ }
+
+ return all_cpu_sockets_bitmask;
+}
+
+/*
+ * Basic sequence of operations for a single key:
+ * - add
+ * - lookup (hit)
+ * - delete
+ * Note: lookup (miss) is not applicable since this is a filter
+ */
+static int test_add_delete(void)
+{
+ struct rte_efd_table *handle;
+ /* test with standard add/lookup/delete functions */
+ efd_value_t prev_value;
+ printf("Entering %s\n", __func__);
+
+ handle = rte_efd_create("test_add_delete",
+ TABLE_SIZE, sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the EFD table\n");
+
+ data[0] = mrand48() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id, &keys[0],
+ data[0]),
+ "Error inserting the key");
+ print_key_info("Add", &keys[0], data[0]);
+
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id, &keys[0]),
+ data[0],
+ "failed to find key");
+
+ TEST_ASSERT_SUCCESS(rte_efd_delete(handle, test_socket_id, &keys[0],
+ &prev_value),
+ "failed to delete key");
+ TEST_ASSERT_EQUAL(prev_value, data[0],
+ "failed to delete the expected value, got %d, "
+ "expected %d", prev_value, data[0]);
+ print_key_info("Del", &keys[0], data[0]);
+
+ rte_efd_free(handle);
+
+ return 0;
+}
+
+/*
+ * Sequence of operations for a single key:
+ * - add
+ * - lookup: hit
+ * - add: update
+ * - lookup: hit (updated data)
+ * - delete: hit
+ */
+static int test_add_update_delete(void)
+{
+ struct rte_efd_table *handle;
+ printf("Entering %s\n", __func__);
+ /* test with standard add/lookup/delete functions */
+ efd_value_t prev_value;
+ data[1] = mrand48() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+
+ handle = rte_efd_create("test_add_update_delete", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the efd table\n");
+
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id, &keys[1],
+ data[1]), "Error inserting the key");
+ print_key_info("Add", &keys[1], data[1]);
+
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id, &keys[1]),
+ data[1], "failed to find key");
+ print_key_info("Lkp", &keys[1], data[1]);
+
+ data[1] = data[1] + 1;
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id, &keys[1],
+ data[1]), "Error re-inserting the key");
+ print_key_info("Add", &keys[1], data[1]);
+
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id, &keys[1]),
+ data[1], "failed to find key");
+ print_key_info("Lkp", &keys[1], data[1]);
+
+ TEST_ASSERT_SUCCESS(rte_efd_delete(handle, test_socket_id, &keys[1],
+ &prev_value), "failed to delete key");
+ TEST_ASSERT_EQUAL(prev_value, data[1],
+ "failed to delete the expected value, got %d, "
+ "expected %d", prev_value, data[1]);
+ print_key_info("Del", &keys[1], data[1]);
+
+
+ rte_efd_free(handle);
+ return 0;
+}
+
+/*
+ * Sequence of operations for find existing EFD table
+ *
+ * - create table
+ * - find existing table: hit
+ * - find non-existing table: miss
+ *
+ */
+static int test_efd_find_existing(void)
+{
+ struct rte_efd_table *handle = NULL, *result = NULL;
+
+ printf("Entering %s\n", __func__);
+
+ /* Create EFD table. */
+ handle = rte_efd_create("efd_find_existing", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the efd table\n");
+
+ /* Try to find existing EFD table */
+ result = rte_efd_find_existing("efd_find_existing");
+ TEST_ASSERT_EQUAL(result, handle, "could not find existing efd table");
+
+ /* Try to find non-existing EFD table */
+ result = rte_efd_find_existing("efd_find_non_existing");
+ TEST_ASSERT_NULL(result, "found table that shouldn't exist");
+
+ /* Cleanup. */
+ rte_efd_free(handle);
+
+ return 0;
+}
+
+/*
+ * Sequence of operations for 5 keys
+ * - add keys
+ * - lookup keys: hit (bulk)
+ * - add keys (update)
+ * - lookup keys: hit (updated data)
+ * - delete keys : hit
+ */
+static int test_five_keys(void)
+{
+ struct rte_efd_table *handle;
+ const void *key_array[5] = {0};
+ efd_value_t result[5] = {0};
+ efd_value_t prev_value;
+ unsigned int i;
+ printf("Entering %s\n", __func__);
+
+ handle = rte_efd_create("test_five_keys", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the efd table\n");
+
+ /* Setup data */
+ for (i = 0; i < 5; i++)
+ data[i] = mrand48() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+
+ /* Add */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id,
+ &keys[i], data[i]),
+ "Error inserting the key");
+ print_key_info("Add", &keys[i], data[i]);
+ }
+
+ /* Lookup */
+ for (i = 0; i < 5; i++)
+ key_array[i] = &keys[i];
+
+ rte_efd_lookup_bulk(handle, test_socket_id, 5,
+ (const void **) (void *) &key_array, result);
+
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_EQUAL(result[i], data[i],
+ "bulk: failed to find key. Expected %d, got %d",
+ data[i], result[i]);
+ print_key_info("Lkp", &keys[i], data[i]);
+ }
+
+ /* Modify data (bulk) */
+ for (i = 0; i < 5; i++)
+ data[i] = data[i] + 1;
+
+ /* Add - update */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id,
+ &keys[i], data[i]),
+ "Error inserting the key");
+ print_key_info("Add", &keys[i], data[i]);
+ }
+
+ /* Lookup */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id,
+ &keys[i]), data[i],
+ "failed to find key");
+ print_key_info("Lkp", &keys[i], data[i]);
+ }
+
+ /* Delete */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_SUCCESS(rte_efd_delete(handle, test_socket_id,
+ &keys[i], &prev_value),
+ "failed to delete key");
+ TEST_ASSERT_EQUAL(prev_value, data[i],
+ "failed to delete the expected value, got %d, "
+ "expected %d", prev_value, data[i]);
+ print_key_info("Del", &keys[i], data[i]);
+ }
+
+
+ rte_efd_free(handle);
+
+ return 0;
+}
+
+/*
+ * Test to see the average table utilization (entries added/max entries)
+ * before hitting a random entry that cannot be added
+ */
+static int test_average_table_utilization(void)
+{
+ struct rte_efd_table *handle = NULL;
+ uint32_t num_rules_in = TABLE_SIZE;
+ uint8_t simple_key[EFD_TEST_KEY_LEN];
+ unsigned int i, j;
+ unsigned int added_keys, average_keys_added = 0;
+
+ printf("Evaluating table utilization and correctness, please wait\n");
+ fflush(stdout);
+
+ for (j = 0; j < ITERATIONS; j++) {
+ handle = rte_efd_create("test_efd", num_rules_in,
+ EFD_TEST_KEY_LEN, efd_get_all_sockets_bitmask(),
+ test_socket_id);
+ if (handle == NULL) {
+ printf("efd table creation failed\n");
+ return -1;
+ }
+
+ unsigned int succeeded = 0;
+ unsigned int lost_keys = 0;
+
+ /* Add random entries until key cannot be added */
+ for (added_keys = 0; added_keys < num_rules_in; added_keys++) {
+
+ for (i = 0; i < EFD_TEST_KEY_LEN; i++)
+ simple_key[i] = rte_rand() & 0xFF;
+
+ efd_value_t val = simple_key[0];
+
+ if (rte_efd_update(handle, test_socket_id, simple_key,
+ val))
+ break; /* continue;*/
+ if (rte_efd_lookup(handle, test_socket_id, simple_key)
+ != val)
+ lost_keys++;
+ else
+ succeeded++;
+ }
+
+ average_keys_added += succeeded;
+
+ /* Reset the table */
+ rte_efd_free(handle);
+
+ /* Print progress on operations */
+ printf("Added %10u Succeeded %10u Lost %10u\n",
+ added_keys, succeeded, lost_keys);
+ fflush(stdout);
+ }
+
+ average_keys_added /= ITERATIONS;
+
+ printf("\nAverage table utilization = %.2f%% (%u/%u)\n",
+ ((double) average_keys_added / num_rules_in * 100),
+ average_keys_added, num_rules_in);
+
+ return 0;
+}
+
+/*
+ * Do tests for EFD creation with bad parameters.
+ */
+static int test_efd_creation_with_bad_parameters(void)
+{
+ struct rte_efd_table *handle, *tmp;
+ printf("Entering %s, **Errors are expected **\n", __func__);
+
+ handle = rte_efd_create("creation_with_bad_parameters_0", TABLE_SIZE, 0,
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ if (handle != NULL) {
+ rte_efd_free(handle);
+ printf("Impossible creating EFD table successfully "
+ "if key_len in parameter is zero\n");
+ return -1;
+ }
+
+ handle = rte_efd_create("creation_with_bad_parameters_1", TABLE_SIZE,
+ sizeof(struct flow_key), 0, test_socket_id);
+ if (handle != NULL) {
+ rte_efd_free(handle);
+ printf("Impossible creating EFD table successfully "
+ "with invalid socket bitmask\n");
+ return -1;
+ }
+
+ handle = rte_efd_create("creation_with_bad_parameters_2", TABLE_SIZE,
+ sizeof(struct flow_key), efd_get_all_sockets_bitmask(),
+ 255);
+ if (handle != NULL) {
+ rte_efd_free(handle);
+ printf("Impossible creating EFD table successfully "
+ "with invalid socket\n");
+ return -1;
+ }
+
+ /* test with same name should fail */
+ handle = rte_efd_create("same_name", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), 0);
+ if (handle == NULL) {
+ printf("Cannot create first EFD table with 'same_name'\n");
+ return -1;
+ }
+ tmp = rte_efd_create("same_name", TABLE_SIZE, sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), 0);
+ if (tmp != NULL) {
+ printf("Creation of EFD table with same name should fail\n");
+ rte_efd_free(handle);
+ rte_efd_free(tmp);
+ return -1;
+ }
+ rte_efd_free(handle);
+
+ printf("# Test successful. No more errors expected\n");
+
+ return 0;
+}
+
+static int
+test_efd(void)
+{
+
+ /* Unit tests */
+ if (test_add_delete() < 0)
+ return -1;
+ if (test_efd_find_existing() < 0)
+ return -1;
+ if (test_add_update_delete() < 0)
+ return -1;
+ if (test_five_keys() < 0)
+ return -1;
+ if (test_efd_creation_with_bad_parameters() < 0)
+ return -1;
+ if (test_average_table_utilization() < 0)
+ return -1;
+
+ return 0;
+}
+
+REGISTER_TEST_COMMAND(efd_autotest, test_efd);
diff --git a/app/test/test_efd_perf.c b/app/test/test_efd_perf.c
new file mode 100644
index 0000000..998a25b
--- /dev/null
+++ b/app/test/test_efd_perf.c
@@ -0,0 +1,407 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <inttypes.h>
+
+#include <rte_lcore.h>
+#include <rte_cycles.h>
+#include <rte_malloc.h>
+#include <rte_random.h>
+#include <rte_efd.h>
+#include <rte_memcpy.h>
+#include <rte_thash.h>
+
+#include "test.h"
+#define NUM_KEYSIZES 10
+#define NUM_SHUFFLES 10
+#define MAX_KEYSIZE 64
+#define MAX_ENTRIES (1 << 19)
+#define KEYS_TO_ADD (MAX_ENTRIES * 3 / 4) /* 75% table utilization */
+#define NUM_LOOKUPS (KEYS_TO_ADD * 5) /* Loop among keys added, several times */
+static unsigned int test_socket_id;
+
+static inline uint8_t efd_get_all_sockets_bitmask(void)
+{
+ uint8_t all_cpu_sockets_bitmask = 0;
+ unsigned int i;
+ unsigned int next_lcore = rte_get_master_lcore();
+ const int val_true = 1, val_false = 0;
+ for (i = 0; i < rte_lcore_count(); i++) {
+ all_cpu_sockets_bitmask |= 1 << rte_lcore_to_socket_id(next_lcore);
+ next_lcore = rte_get_next_lcore(next_lcore, val_false, val_true);
+ }
+
+ return all_cpu_sockets_bitmask;
+}
+
+enum operations {
+ ADD = 0,
+ LOOKUP,
+ LOOKUP_MULTI,
+ DELETE,
+ NUM_OPERATIONS
+};
+
+struct efd_perf_params {
+ struct rte_efd_table *efd_table;
+ uint32_t key_size;
+ unsigned int cycle;
+};
+
+static uint32_t hashtest_key_lens[] = {
+ /* standard key sizes */
+ 4, 8, 16, 32, 48, 64,
+ /* IPv4 SRC + DST + protocol, unpadded */
+ 9,
+ /* IPv4 5-tuple, unpadded */
+ 13,
+ /* IPv6 5-tuple, unpadded */
+ 37,
+ /* IPv6 5-tuple, padded to 8-byte boundary */
+ 40
+};
+
+/* Array to store number of cycles per operation */
+uint64_t cycles[NUM_KEYSIZES][NUM_OPERATIONS];
+
+/* Array to store the data */
+efd_value_t data[KEYS_TO_ADD];
+
+/* Array to store all input keys */
+uint8_t keys[KEYS_TO_ADD][MAX_KEYSIZE];
+
+/* Shuffle the keys that have been added, so lookups will be totally random */
+static void
+shuffle_input_keys(struct efd_perf_params *params)
+{
+ efd_value_t temp_data;
+ unsigned int i;
+ uint32_t swap_idx;
+ uint8_t temp_key[MAX_KEYSIZE];
+
+ for (i = KEYS_TO_ADD - 1; i > 0; i--) {
+ swap_idx = rte_rand() % i;
+
+ memcpy(temp_key, keys[i], hashtest_key_lens[params->cycle]);
+ temp_data = data[i];
+
+ memcpy(keys[i], keys[swap_idx], hashtest_key_lens[params->cycle]);
+ data[i] = data[swap_idx];
+
+ memcpy(keys[swap_idx], temp_key, hashtest_key_lens[params->cycle]);
+ data[swap_idx] = temp_data;
+ }
+}
+
+static int key_compare(const void *key1, const void *key2)
+{
+ return memcmp(key1, key2, MAX_KEYSIZE);
+}
+
+/*
+ * TODO: we could "error proof" these as done in test_hash_perf.c ln 165:
+ *
+ * The current setup may give errors if too full in some cases which we check
+ * for. However, since EFD allows for ~99% capacity, these errors are rare for
+ * #"KEYS_TO_ADD" which is 75% capacity.
+ */
+static int
+setup_keys_and_data(struct efd_perf_params *params, unsigned int cycle)
+{
+ unsigned int i, j;
+ int num_duplicates;
+
+ params->key_size = hashtest_key_lens[cycle];
+ params->cycle = cycle;
+
+ /* Reset all arrays */
+ for (i = 0; i < params->key_size; i++)
+ keys[0][i] = 0;
+
+ /* Generate a list of keys, some of which may be duplicates */
+ for (i = 0; i < KEYS_TO_ADD; i++) {
+ for (j = 0; j < params->key_size; j++)
+ keys[i][j] = rte_rand() & 0xFF;
+
+ data[i] = rte_rand() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+ }
+
+ /* Remove duplicates from the keys array */
+ do {
+ num_duplicates = 0;
+
+ /* Sort the list of keys to make it easier to find duplicates */
+ qsort(keys, KEYS_TO_ADD, MAX_KEYSIZE, key_compare);
+
+ /* Sift through the list of keys and look for duplicates */
+ int num_duplicates = 0;
+ for (i = 0; i < KEYS_TO_ADD - 1; i++) {
+ if (memcmp(keys[i], keys[i + 1], params->key_size) == 0) {
+ /* This key already exists, try again */
+ num_duplicates++;
+ for (j = 0; j < params->key_size; j++)
+ keys[i][j] = rte_rand() & 0xFF;
+ }
+ }
+ } while (num_duplicates != 0);
+
+ /* Shuffle the random values again */
+ shuffle_input_keys(params);
+
+ params->efd_table = rte_efd_create("test_efd_perf",
+ MAX_ENTRIES, params->key_size,
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(params->efd_table, "Error creating the efd table\n");
+
+ return 0;
+}
+
+static int
+timed_adds(struct efd_perf_params *params)
+{
+ const uint64_t start_tsc = rte_rdtsc();
+ unsigned int i, a;
+ int32_t ret;
+
+ for (i = 0; i < KEYS_TO_ADD; i++) {
+ ret = rte_efd_update(params->efd_table, test_socket_id, keys[i],
+ data[i]);
+ if (ret != 0) {
+ printf("Error %d in rte_efd_update - key=0x", ret);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x", keys[i][a]);
+ printf(" value=%d\n", data[i]);
+
+ return -1;
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][ADD] = time_taken / KEYS_TO_ADD;
+ return 0;
+}
+
+static int
+timed_lookups(struct efd_perf_params *params)
+{
+ unsigned int i, j, a;
+ const uint64_t start_tsc = rte_rdtsc();
+ efd_value_t ret_data;
+
+ for (i = 0; i < NUM_LOOKUPS / KEYS_TO_ADD; i++) {
+ for (j = 0; j < KEYS_TO_ADD; j++) {
+ ret_data = rte_efd_lookup(params->efd_table,
+ test_socket_id, keys[j]);
+ if (ret_data != data[j]) {
+ printf("Value mismatch using rte_efd_lookup: "
+ "key #%d (0x", i);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x", keys[i][a]);
+ printf(")\n");
+ printf(" Expected %d, got %d\n", data[i],
+ ret_data);
+
+ return -1;
+ }
+
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][LOOKUP] = time_taken / NUM_LOOKUPS;
+
+ return 0;
+}
+
+static int
+timed_lookups_multi(struct efd_perf_params *params)
+{
+ unsigned int i, j, k, a;
+ efd_value_t result[RTE_EFD_BURST_MAX] = {0};
+ const void *keys_burst[RTE_EFD_BURST_MAX];
+ const uint64_t start_tsc = rte_rdtsc();
+
+ for (i = 0; i < NUM_LOOKUPS / KEYS_TO_ADD; i++) {
+ for (j = 0; j < KEYS_TO_ADD / RTE_EFD_BURST_MAX; j++) {
+ for (k = 0; k < RTE_EFD_BURST_MAX; k++)
+ keys_burst[k] = keys[j * RTE_EFD_BURST_MAX + k];
+
+ rte_efd_lookup_bulk(params->efd_table, test_socket_id,
+ RTE_EFD_BURST_MAX,
+ keys_burst, result);
+
+ for (k = 0; k < RTE_EFD_BURST_MAX; k++) {
+ uint32_t data_idx = j * RTE_EFD_BURST_MAX + k;
+ if (result[k] != data[data_idx]) {
+ printf("Value mismatch using "
+ "rte_efd_lookup_bulk: key #%d "
+ "(0x", i);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x",
+ keys[data_idx][a]);
+ printf(")\n");
+ printf(" Expected %d, got %d\n",
+ data[data_idx], result[k]);
+
+ return -1;
+ }
+ }
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][LOOKUP_MULTI] = time_taken / NUM_LOOKUPS;
+
+ return 0;
+}
+
+static int
+timed_deletes(struct efd_perf_params *params)
+{
+ unsigned int i, a;
+ const uint64_t start_tsc = rte_rdtsc();
+ int32_t ret;
+
+ for (i = 0; i < KEYS_TO_ADD; i++) {
+ ret = rte_efd_delete(params->efd_table, test_socket_id, keys[i],
+ NULL);
+
+ if (ret != 0) {
+ printf("Error %d in rte_efd_delete - key=0x", ret);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x", keys[i][a]);
+ printf("\n");
+
+ return -1;
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][DELETE] = time_taken / KEYS_TO_ADD;
+
+ return 0;
+}
+
+static void
+perform_frees(struct efd_perf_params *params)
+{
+ if (params->efd_table != NULL) {
+ rte_efd_free(params->efd_table);
+ params->efd_table = NULL;
+ }
+}
+
+static int
+exit_with_fail(const char *testname, struct efd_perf_params *params,
+ unsigned int i)
+{
+
+ printf("<<<<<Test %s failed at keysize %d iteration %d >>>>>\n",
+ testname, hashtest_key_lens[params->cycle], i);
+ perform_frees(params);
+ return -1;
+}
+
+static int
+run_all_tbl_perf_tests(void)
+{
+ unsigned int i, j;
+ struct efd_perf_params params;
+
+ printf("Measuring performance, please wait\n");
+ fflush(stdout);
+
+ test_socket_id = rte_socket_id();
+
+ for (i = 0; i < NUM_KEYSIZES; i++) {
+
+ if (setup_keys_and_data(¶ms, i) < 0) {
+ printf("Could not create keys/data/table\n");
+ return -1;
+ }
+
+ if (timed_adds(¶ms) < 0)
+ return exit_with_fail("timed_adds", ¶ms, i);
+
+ for (j = 0; j < NUM_SHUFFLES; j++)
+ shuffle_input_keys(¶ms);
+
+ if (timed_lookups(¶ms) < 0)
+ return exit_with_fail("timed_lookups", ¶ms, i);
+
+ if (timed_lookups_multi(¶ms) < 0)
+ return exit_with_fail("timed_lookups_multi", ¶ms, i);
+
+ if (timed_deletes(¶ms) < 0)
+ return exit_with_fail("timed_deletes", ¶ms, i);
+
+ /* Print a dot to show progress on operations */
+ printf(".");
+ fflush(stdout);
+
+ perform_frees(¶ms);
+ }
+
+ printf("\nResults (in CPU cycles/operation)\n");
+ printf("-----------------------------------\n");
+ printf("\n%-18s%-18s%-18s%-18s%-18s\n",
+ "Keysize", "Add", "Lookup", "Lookup_bulk", "Delete");
+ for (i = 0; i < NUM_KEYSIZES; i++) {
+ printf("%-18d", hashtest_key_lens[i]);
+ for (j = 0; j < NUM_OPERATIONS; j++)
+ printf("%-18"PRIu64, cycles[i][j]);
+ printf("\n");
+ }
+ return 0;
+}
+
+static int
+test_efd_perf(void)
+{
+
+ if (run_all_tbl_perf_tests() < 0)
+ return -1;
+
+ return 0;
+}
+
+REGISTER_TEST_COMMAND(efd_perf_autotest, test_efd_perf);
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v4 3/5] examples/flow_distributor: sample app to demonstrate EFD usage
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 0/5] Elastic Flow Distributor Pablo de Lara
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 1/5] efd: new Elastic Flow Distributor library Pablo de Lara
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 2/5] app/test: add EFD functional and perf tests Pablo de Lara
@ 2017-01-15 12:04 ` Pablo de Lara
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 4/5] doc: add EFD library section in Programmers guide Pablo de Lara
` (2 subsequent siblings)
5 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-15 12:04 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Saikrishna Edupuganti
This new sample app, based on the client/server sample app,
shows the user an scenario using the EFD library.
It consists of:
- A front-end server which has an EFD table that stores the
node id for each flow key, which will distribute the incoming
packets to the different nodes
- A back-end node, which has a hash table where node checks,
after reading packets coming from the server, whether the packet
is meant to be used in such node, in which case it will be TXed,
or not, in which case, packet will be dropped.
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 1 +
doc/api/examples.dox | 4 +
examples/Makefile | 1 +
examples/flow_distributor/Makefile | 44 +++
examples/flow_distributor/distributor/Makefile | 57 ++++
examples/flow_distributor/distributor/args.c | 200 ++++++++++++
examples/flow_distributor/distributor/args.h | 39 +++
examples/flow_distributor/distributor/init.c | 371 ++++++++++++++++++++++
examples/flow_distributor/distributor/init.h | 76 +++++
examples/flow_distributor/distributor/main.c | 362 +++++++++++++++++++++
examples/flow_distributor/node/Makefile | 48 +++
examples/flow_distributor/node/node.c | 417 +++++++++++++++++++++++++
examples/flow_distributor/shared/common.h | 99 ++++++
13 files changed, 1719 insertions(+)
create mode 100644 examples/flow_distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/args.c
create mode 100644 examples/flow_distributor/distributor/args.h
create mode 100644 examples/flow_distributor/distributor/init.c
create mode 100644 examples/flow_distributor/distributor/init.h
create mode 100644 examples/flow_distributor/distributor/main.c
create mode 100644 examples/flow_distributor/node/Makefile
create mode 100644 examples/flow_distributor/node/node.c
create mode 100644 examples/flow_distributor/shared/common.h
diff --git a/MAINTAINERS b/MAINTAINERS
index d812962..b124f6e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -533,6 +533,7 @@ M: Byron Marohn <byron.marohn@intel.com>
M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
F: lib/librte_efd/
F: app/test/test_efd*
+F: examples/flow_distributor/
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
diff --git a/doc/api/examples.dox b/doc/api/examples.dox
index 1626852..c13e574 100644
--- a/doc/api/examples.dox
+++ b/doc/api/examples.dox
@@ -52,6 +52,10 @@
@example load_balancer/init.c
@example load_balancer/main.c
@example load_balancer/runtime.c
+@example flow_distributor/distributor/args.c
+@example flow_distributor/distributor/init.c
+@example flow_distributor/distributor/main.c
+@example flow_distributor/node/node.c
@example multi_process/client_server_mp/mp_client/client.c
@example multi_process/client_server_mp/mp_server/args.c
@example multi_process/client_server_mp/mp_server/init.c
diff --git a/examples/Makefile b/examples/Makefile
index d49c7f2..b404982 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -45,6 +45,7 @@ DIRS-y += dpdk_qat
endif
DIRS-y += ethtool
DIRS-y += exception_path
+DIRS-$(CONFIG_RTE_LIBRTE_EFD) += flow_distributor
DIRS-y += helloworld
DIRS-$(CONFIG_RTE_LIBRTE_PIPELINE) += ip_pipeline
ifeq ($(CONFIG_RTE_LIBRTE_LPM),y)
diff --git a/examples/flow_distributor/Makefile b/examples/flow_distributor/Makefile
new file mode 100644
index 0000000..5bae706
--- /dev/null
+++ b/examples/flow_distributor/Makefile
@@ -0,0 +1,44 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overridden by command line or environment
+RTE_TARGET ?= x86_64-native-linuxapp-gcc
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+DIRS-$(CONFIG_RTE_EXEC_ENV_LINUXAPP) += distributor
+DIRS-$(CONFIG_RTE_EXEC_ENV_LINUXAPP) += node
+
+include $(RTE_SDK)/mk/rte.extsubdir.mk
diff --git a/examples/flow_distributor/distributor/Makefile b/examples/flow_distributor/distributor/Makefile
new file mode 100644
index 0000000..8714151
--- /dev/null
+++ b/examples/flow_distributor/distributor/Makefile
@@ -0,0 +1,57 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overridden by command line or environment
+RTE_TARGET ?= x86_64-native-linuxapp-gcc
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+ifneq ($(CONFIG_RTE_EXEC_ENV), "linuxapp")
+$(error This application can only operate in a linuxapp environment, \
+please change the definition of the RTE_TARGET environment variable)
+endif
+
+# binary name
+APP = distributor
+
+# all source are stored in SRCS-y
+SRCS-y := main.c init.c args.c
+
+INC := $(wildcard *.h)
+
+CFLAGS += $(WERROR_FLAGS) -O3
+CFLAGS += -I$(SRCDIR)/../shared
+
+include $(RTE_SDK)/mk/rte.extapp.mk
diff --git a/examples/flow_distributor/distributor/args.c b/examples/flow_distributor/distributor/args.c
new file mode 100644
index 0000000..ee29203
--- /dev/null
+++ b/examples/flow_distributor/distributor/args.c
@@ -0,0 +1,200 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <getopt.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include <rte_memory.h>
+#include <rte_string_fns.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+/* 1M flows by default */
+#define DEFAULT_NUM_FLOWS 0x100000
+
+/* global var for number of nodes - extern in header */
+uint8_t num_nodes;
+/* global var for number of flows - extern in header */
+uint32_t num_flows = DEFAULT_NUM_FLOWS;
+
+static const char *progname;
+
+/**
+ * Prints out usage information to stdout
+ */
+static void
+usage(void)
+{
+ printf("%s [EAL options] -- -p PORTMASK -n NUM_NODES -f NUM_FLOWS\n"
+ " -p PORTMASK: hexadecimal bitmask of ports to use\n"
+ " -n NUM_NODES: number of node processes to use\n"
+ " -f NUM_FLOWS: number of flows to be added in the EFD table\n",
+ progname);
+}
+
+/**
+ * The ports to be used by the application are passed in
+ * the form of a bitmask. This function parses the bitmask
+ * and places the port numbers to be used into the port[]
+ * array variable
+ */
+static int
+parse_portmask(uint8_t max_ports, const char *portmask)
+{
+ char *end = NULL;
+ unsigned long pm;
+ uint8_t count = 0;
+
+ if (portmask == NULL || *portmask == '\0')
+ return -1;
+
+ /* convert parameter to a number and verify */
+ pm = strtoul(portmask, &end, 16);
+ if (end == NULL || *end != '\0' || pm == 0)
+ return -1;
+
+ /* loop through bits of the mask and mark ports */
+ while (pm != 0) {
+ if (pm & 0x01) { /* bit is set in mask, use port */
+ if (count >= max_ports)
+ printf("WARNING: requested port %u not present"
+ " - ignoring\n", (unsigned int)count);
+ else
+ info->id[info->num_ports++] = count;
+ }
+ pm = (pm >> 1);
+ count++;
+ }
+
+ return 0;
+}
+
+/**
+ * Take the number of nodes parameter passed to the app
+ * and convert to a number to store in the num_nodes variable
+ */
+static int
+parse_num_nodes(const char *nodes)
+{
+ char *end = NULL;
+ unsigned long temp;
+
+ if (nodes == NULL || *nodes == '\0')
+ return -1;
+
+ temp = strtoul(nodes, &end, 10);
+ if (end == NULL || *end != '\0' || temp == 0)
+ return -1;
+
+ num_nodes = (uint8_t)temp;
+ return 0;
+}
+
+static int
+parse_num_flows(const char *flows)
+{
+ char *end = NULL;
+
+ /* parse hexadecimal string */
+ num_flows = strtoul(flows, &end, 16);
+ if ((flows[0] == '\0') || (end == NULL) || (*end != '\0'))
+ return -1;
+
+ if (num_flows == 0)
+ return -1;
+
+ return 0;
+}
+
+/**
+ * The application specific arguments follow the DPDK-specific
+ * arguments which are stripped by the DPDK init. This function
+ * processes these application arguments, printing usage info
+ * on error.
+ */
+int
+parse_app_args(uint8_t max_ports, int argc, char *argv[])
+{
+ int option_index, opt;
+ char **argvopt = argv;
+ static struct option lgopts[] = { /* no long options */
+ {NULL, 0, 0, 0 }
+ };
+ progname = argv[0];
+
+ while ((opt = getopt_long(argc, argvopt, "n:f:p:", lgopts,
+ &option_index)) != EOF) {
+ switch (opt) {
+ case 'p':
+ if (parse_portmask(max_ports, optarg) != 0) {
+ usage();
+ return -1;
+ }
+ break;
+ case 'n':
+ if (parse_num_nodes(optarg) != 0) {
+ usage();
+ return -1;
+ }
+ break;
+ case 'f':
+ if (parse_num_flows(optarg) != 0) {
+ usage();
+ return -1;
+ }
+ break;
+ default:
+ printf("ERROR: Unknown option '%c'\n", opt);
+ usage();
+ return -1;
+ }
+ }
+
+ if (info->num_ports == 0 || num_nodes == 0) {
+ usage();
+ return -1;
+ }
+
+ if (info->num_ports % 2 != 0) {
+ printf("ERROR: application requires an even "
+ "number of ports to use\n");
+ return -1;
+ }
+ return 0;
+}
diff --git a/examples/flow_distributor/distributor/args.h b/examples/flow_distributor/distributor/args.h
new file mode 100644
index 0000000..cacf395
--- /dev/null
+++ b/examples/flow_distributor/distributor/args.h
@@ -0,0 +1,39 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _ARGS_H_
+#define _ARGS_H_
+
+int parse_app_args(uint8_t max_ports, int argc, char *argv[]);
+
+#endif /* ifndef _ARGS_H_ */
diff --git a/examples/flow_distributor/distributor/init.c b/examples/flow_distributor/distributor/init.c
new file mode 100644
index 0000000..3b0aa85
--- /dev/null
+++ b/examples/flow_distributor/distributor/init.c
@@ -0,0 +1,371 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/queue.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <inttypes.h>
+
+#include <rte_common.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_byteorder.h>
+#include <rte_atomic.h>
+#include <rte_launch.h>
+#include <rte_per_lcore.h>
+#include <rte_lcore.h>
+#include <rte_branch_prediction.h>
+#include <rte_debug.h>
+#include <rte_ring.h>
+#include <rte_log.h>
+#include <rte_mempool.h>
+#include <rte_memcpy.h>
+#include <rte_mbuf.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ether.h>
+#include <rte_ethdev.h>
+#include <rte_malloc.h>
+#include <rte_string_fns.h>
+#include <rte_cycles.h>
+#include <rte_efd.h>
+#include <rte_hash.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+#define MBUFS_PER_NODE 1536
+#define MBUFS_PER_PORT 1536
+#define MBUF_CACHE_SIZE 512
+
+#define RTE_MP_RX_DESC_DEFAULT 512
+#define RTE_MP_TX_DESC_DEFAULT 512
+#define NODE_QUEUE_RINGSIZE 128
+
+#define NO_FLAGS 0
+
+/* The mbuf pool for packet rx */
+struct rte_mempool *pktmbuf_pool;
+
+/* array of info/queues for nodes */
+struct node *nodes;
+
+/* Flow distributor table */
+struct rte_efd_table *efd_table;
+
+/* Shared info between distributor and nodes */
+struct shared_info *info;
+
+/**
+ * Initialise the mbuf pool for packet reception for the NIC, and any other
+ * buffer pools needed by the app - currently none.
+ */
+static int
+init_mbuf_pools(void)
+{
+ const unsigned int num_mbufs = (num_nodes * MBUFS_PER_NODE) +
+ (info->num_ports * MBUFS_PER_PORT);
+
+ /*
+ * Don't pass single-producer/single-consumer flags to mbuf create as it
+ * seems faster to use a cache instead
+ */
+ printf("Creating mbuf pool '%s' [%u mbufs] ...\n",
+ PKTMBUF_POOL_NAME, num_mbufs);
+ pktmbuf_pool = rte_pktmbuf_pool_create(PKTMBUF_POOL_NAME, num_mbufs,
+ MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
+
+ return pktmbuf_pool == NULL; /* 0 on success */
+}
+
+/**
+ * Initialise an individual port:
+ * - configure number of rx and tx rings
+ * - set up each rx ring, to pull from the main mbuf pool
+ * - set up each tx ring
+ * - start the port and report its status to stdout
+ */
+static int
+init_port(uint8_t port_num)
+{
+ /* for port configuration all features are off by default */
+ const struct rte_eth_conf port_conf = {
+ .rxmode = {
+ .mq_mode = ETH_MQ_RX_RSS
+ }
+ };
+ const uint16_t rx_rings = 1, tx_rings = num_nodes;
+ const uint16_t rx_ring_size = RTE_MP_RX_DESC_DEFAULT;
+ const uint16_t tx_ring_size = RTE_MP_TX_DESC_DEFAULT;
+
+ uint16_t q;
+ int retval;
+
+ printf("Port %u init ... ", (unsigned int)port_num);
+ fflush(stdout);
+
+ /*
+ * Standard DPDK port initialisation - config port, then set up
+ * rx and tx rings.
+ */
+ retval = rte_eth_dev_configure(port_num, rx_rings, tx_rings, &port_conf);
+ if (retval != 0)
+ return retval;
+
+ for (q = 0; q < rx_rings; q++) {
+ retval = rte_eth_rx_queue_setup(port_num, q, rx_ring_size,
+ rte_eth_dev_socket_id(port_num),
+ NULL, pktmbuf_pool);
+ if (retval < 0)
+ return retval;
+ }
+
+ for (q = 0; q < tx_rings; q++) {
+ retval = rte_eth_tx_queue_setup(port_num, q, tx_ring_size,
+ rte_eth_dev_socket_id(port_num),
+ NULL);
+ if (retval < 0)
+ return retval;
+ }
+
+ rte_eth_promiscuous_enable(port_num);
+
+ retval = rte_eth_dev_start(port_num);
+ if (retval < 0)
+ return retval;
+
+ printf("done:\n");
+
+ return 0;
+}
+
+/**
+ * Set up the DPDK rings which will be used to pass packets, via
+ * pointers, between the multi-process distributor and node processes.
+ * Each node needs one RX queue.
+ */
+static int
+init_shm_rings(void)
+{
+ unsigned int i;
+ unsigned int socket_id;
+ const char *q_name;
+ const unsigned int ringsize = NODE_QUEUE_RINGSIZE;
+
+ nodes = rte_malloc("node details",
+ sizeof(*nodes) * num_nodes, 0);
+ if (nodes == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot allocate memory for "
+ "node program details\n");
+
+ for (i = 0; i < num_nodes; i++) {
+ /* Create an RX queue for each node */
+ socket_id = rte_socket_id();
+ q_name = get_rx_queue_name(i);
+ nodes[i].rx_q = rte_ring_create(q_name,
+ ringsize, socket_id,
+ RING_F_SP_ENQ | RING_F_SC_DEQ);
+ if (nodes[i].rx_q == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot create rx ring queue "
+ "for node %u\n", i);
+ }
+ return 0;
+}
+
+/*
+ * Create flow distributor table which will contain all the flows
+ * that will be distributed among the nodes
+ */
+static void
+create_flow_distributor_table(void)
+{
+ uint8_t socket_id = rte_socket_id();
+
+ /* create table */
+ efd_table = rte_efd_create("flow table", num_flows * 2, sizeof(uint32_t),
+ 1 << socket_id, socket_id);
+
+ if (efd_table == NULL)
+ rte_exit(EXIT_FAILURE, "Problem creating the flow table\n");
+}
+
+static void
+populate_flow_distributor_table(void)
+{
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint8_t socket_id = rte_socket_id();
+ uint64_t node_id;
+
+ /* Add flows in table */
+ for (i = 0; i < num_flows; i++) {
+ node_id = i % num_nodes;
+
+ ip_dst = rte_cpu_to_be_32(i);
+ ret = rte_efd_update(efd_table, socket_id,
+ (void *)&ip_dst, (efd_value_t)node_id);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u in "
+ "flow distributor table\n", i);
+ }
+
+ printf("EFD table: Adding 0x%x keys\n", num_flows);
+}
+
+/* Check the link status of all ports in up to 9s, and print them finally */
+static void
+check_all_ports_link_status(uint8_t port_num, uint32_t port_mask)
+{
+#define CHECK_INTERVAL 100 /* 100ms */
+#define MAX_CHECK_TIME 90 /* 9s (90 * 100ms) in total */
+ uint8_t portid, count, all_ports_up, print_flag = 0;
+ struct rte_eth_link link;
+
+ printf("\nChecking link status");
+ fflush(stdout);
+ for (count = 0; count <= MAX_CHECK_TIME; count++) {
+ all_ports_up = 1;
+ for (portid = 0; portid < port_num; portid++) {
+ if ((port_mask & (1 << info->id[portid])) == 0)
+ continue;
+ memset(&link, 0, sizeof(link));
+ rte_eth_link_get_nowait(info->id[portid], &link);
+ /* print link status if flag set */
+ if (print_flag == 1) {
+ if (link.link_status)
+ printf("Port %d Link Up - speed %u "
+ "Mbps - %s\n", info->id[portid],
+ (unsigned int)link.link_speed,
+ (link.link_duplex == ETH_LINK_FULL_DUPLEX) ?
+ ("full-duplex") : ("half-duplex\n"));
+ else
+ printf("Port %d Link Down\n",
+ (uint8_t)info->id[portid]);
+ continue;
+ }
+ /* clear all_ports_up flag if any link down */
+ if (link.link_status == ETH_LINK_DOWN) {
+ all_ports_up = 0;
+ break;
+ }
+ }
+ /* after finally printing all link status, get out */
+ if (print_flag == 1)
+ break;
+
+ if (all_ports_up == 0) {
+ printf(".");
+ fflush(stdout);
+ rte_delay_ms(CHECK_INTERVAL);
+ }
+
+ /* set the print_flag if all ports up or timeout */
+ if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
+ print_flag = 1;
+ printf("done\n");
+ }
+ }
+}
+
+/**
+ * Main init function for the multi-process distributor app,
+ * calls subfunctions to do each stage of the initialisation.
+ */
+int
+init(int argc, char *argv[])
+{
+ int retval;
+ const struct rte_memzone *mz;
+ uint8_t i, total_ports;
+
+ /* init EAL, parsing EAL args */
+ retval = rte_eal_init(argc, argv);
+ if (retval < 0)
+ return -1;
+ argc -= retval;
+ argv += retval;
+
+ /* get total number of ports */
+ total_ports = rte_eth_dev_count();
+
+ /* set up array for port data */
+ mz = rte_memzone_reserve(MZ_SHARED_INFO, sizeof(*info),
+ rte_socket_id(), NO_FLAGS);
+ if (mz == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot reserve memory zone "
+ "for port information\n");
+ memset(mz->addr, 0, sizeof(*info));
+ info = mz->addr;
+
+ /* parse additional, application arguments */
+ retval = parse_app_args(total_ports, argc, argv);
+ if (retval != 0)
+ return -1;
+
+ /* initialise mbuf pools */
+ retval = init_mbuf_pools();
+ if (retval != 0)
+ rte_exit(EXIT_FAILURE, "Cannot create needed mbuf pools\n");
+
+ /* now initialise the ports we will use */
+ for (i = 0; i < info->num_ports; i++) {
+ retval = init_port(info->id[i]);
+ if (retval != 0)
+ rte_exit(EXIT_FAILURE, "Cannot initialise port %u\n",
+ (unsigned int) i);
+ }
+
+ check_all_ports_link_status(info->num_ports, (~0x0));
+
+ /* initialise the node queues/rings for inter-eu comms */
+ init_shm_rings();
+
+ /* Create the flow distributor table */
+ create_flow_distributor_table();
+
+ /* Populate the flow distributor table */
+ populate_flow_distributor_table();
+
+ /* Share the total number of nodes */
+ info->num_nodes = num_nodes;
+
+ /* Share the total number of flows */
+ info->num_flows = num_flows;
+ return 0;
+}
diff --git a/examples/flow_distributor/distributor/init.h b/examples/flow_distributor/distributor/init.h
new file mode 100644
index 0000000..d11aacf
--- /dev/null
+++ b/examples/flow_distributor/distributor/init.h
@@ -0,0 +1,76 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _INIT_H_
+#define _INIT_H_
+
+/*
+ * #include <rte_ring.h>
+ * #include "args.h"
+ */
+
+/*
+ * Define a node structure with all needed info, including
+ * stats from the nodes.
+ */
+struct node {
+ struct rte_ring *rx_q;
+ unsigned int node_id;
+ /* these stats hold how many packets the node will actually receive,
+ * and how many packets were dropped because the node's queue was full.
+ * The port-info stats, in contrast, record how many packets were received
+ * or transmitted on an actual NIC port.
+ */
+ struct {
+ uint64_t rx;
+ uint64_t rx_drop;
+ } stats;
+};
+
+extern struct rte_efd_table *efd_table;
+extern struct node *nodes;
+
+/*
+ * shared information between distributor and nodes: number of clients,
+ * port numbers, rx and tx stats etc.
+ */
+extern struct shared_info *info;
+
+extern struct rte_mempool *pktmbuf_pool;
+extern uint8_t num_nodes;
+extern unsigned int num_sockets;
+extern uint32_t num_flows;
+
+int init(int argc, char *argv[]);
+
+#endif /* ifndef _INIT_H_ */
diff --git a/examples/flow_distributor/distributor/main.c b/examples/flow_distributor/distributor/main.c
new file mode 100644
index 0000000..f97f003
--- /dev/null
+++ b/examples/flow_distributor/distributor/main.c
@@ -0,0 +1,362 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <inttypes.h>
+#include <sys/queue.h>
+#include <errno.h>
+#include <netinet/ip.h>
+
+#include <rte_common.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_byteorder.h>
+#include <rte_launch.h>
+#include <rte_per_lcore.h>
+#include <rte_lcore.h>
+#include <rte_branch_prediction.h>
+#include <rte_atomic.h>
+#include <rte_ring.h>
+#include <rte_log.h>
+#include <rte_debug.h>
+#include <rte_mempool.h>
+#include <rte_memcpy.h>
+#include <rte_mbuf.h>
+#include <rte_ether.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ethdev.h>
+#include <rte_byteorder.h>
+#include <rte_malloc.h>
+#include <rte_string_fns.h>
+#include <rte_efd.h>
+#include <rte_ip.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+/*
+ * When doing reads from the NIC or the node queues,
+ * use this batch size
+ */
+#define PACKET_READ_SIZE 32
+
+/*
+ * Local buffers to put packets in, used to send packets in bursts to the
+ * nodes
+ */
+struct node_rx_buf {
+ struct rte_mbuf *buffer[PACKET_READ_SIZE];
+ uint16_t count;
+};
+
+struct flow_distributor_stats {
+ uint64_t distributed;
+ uint64_t drop;
+} flow_dist_stats;
+
+/* One buffer per node rx queue - dynamically allocate array */
+static struct node_rx_buf *cl_rx_buf;
+
+static const char *
+get_printable_mac_addr(uint8_t port)
+{
+ static const char err_address[] = "00:00:00:00:00:00";
+ static char addresses[RTE_MAX_ETHPORTS][sizeof(err_address)];
+ struct ether_addr mac;
+
+ if (unlikely(port >= RTE_MAX_ETHPORTS))
+ return err_address;
+ if (unlikely(addresses[port][0] == '\0')) {
+ rte_eth_macaddr_get(port, &mac);
+ snprintf(addresses[port], sizeof(addresses[port]),
+ "%02x:%02x:%02x:%02x:%02x:%02x\n",
+ mac.addr_bytes[0], mac.addr_bytes[1],
+ mac.addr_bytes[2], mac.addr_bytes[3],
+ mac.addr_bytes[4], mac.addr_bytes[5]);
+ }
+ return addresses[port];
+}
+
+/*
+ * This function displays the recorded statistics for each port
+ * and for each node. It uses ANSI terminal codes to clear
+ * screen when called. It is called from a single non-master
+ * thread in the distributor process, when the process is run with more
+ * than one lcore enabled.
+ */
+static void
+do_stats_display(void)
+{
+ unsigned int i, j;
+ const char clr[] = {27, '[', '2', 'J', '\0'};
+ const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
+ uint64_t port_tx[RTE_MAX_ETHPORTS], port_tx_drop[RTE_MAX_ETHPORTS];
+ uint64_t node_tx[MAX_NODES], node_tx_drop[MAX_NODES];
+
+ /* to get TX stats, we need to do some summing calculations */
+ memset(port_tx, 0, sizeof(port_tx));
+ memset(port_tx_drop, 0, sizeof(port_tx_drop));
+ memset(node_tx, 0, sizeof(node_tx));
+ memset(node_tx_drop, 0, sizeof(node_tx_drop));
+
+ for (i = 0; i < num_nodes; i++) {
+ const struct tx_stats *tx = &info->tx_stats[i];
+
+ for (j = 0; j < info->num_ports; j++) {
+ const uint64_t tx_val = tx->tx[info->id[j]];
+ const uint64_t drop_val = tx->tx_drop[info->id[j]];
+
+ port_tx[j] += tx_val;
+ port_tx_drop[j] += drop_val;
+ node_tx[i] += tx_val;
+ node_tx_drop[i] += drop_val;
+ }
+ }
+
+ /* Clear screen and move to top left */
+ printf("%s%s", clr, topLeft);
+
+ printf("PORTS\n");
+ printf("-----\n");
+ for (i = 0; i < info->num_ports; i++)
+ printf("Port %u: '%s'\t", (unsigned int)info->id[i],
+ get_printable_mac_addr(info->id[i]));
+ printf("\n\n");
+ for (i = 0; i < info->num_ports; i++) {
+ printf("Port %u - rx: %9"PRIu64"\t"
+ "tx: %9"PRIu64"\n",
+ (unsigned int)info->id[i], info->rx_stats.rx[i],
+ port_tx[i]);
+ }
+
+ printf("\nFLOW DISTRIBUTOR\n");
+ printf("-----\n");
+ printf("distributed: %9"PRIu64", drop: %9"PRIu64"\n",
+ flow_dist_stats.distributed, flow_dist_stats.drop);
+
+ printf("\nNODES\n");
+ printf("-------\n");
+ for (i = 0; i < num_nodes; i++) {
+ const unsigned long long rx = nodes[i].stats.rx;
+ const unsigned long long rx_drop = nodes[i].stats.rx_drop;
+ const struct filter_stats *filter = &info->filter_stats[i];
+
+ printf("Node %2u - rx: %9llu, rx_drop: %9llu\n"
+ " tx: %9"PRIu64", tx_drop: %9"PRIu64"\n"
+ " filter_passed: %9"PRIu64", "
+ "filter_drop: %9"PRIu64"\n",
+ i, rx, rx_drop, node_tx[i], node_tx_drop[i],
+ filter->passed, filter->drop);
+ }
+
+ printf("\n");
+}
+
+/*
+ * The function called from each non-master lcore used by the process.
+ * The test_and_set function is used to randomly pick a single lcore on which
+ * the code to display the statistics will run. Otherwise, the code just
+ * repeatedly sleeps.
+ */
+static int
+sleep_lcore(__attribute__((unused)) void *dummy)
+{
+ /* Used to pick a display thread - static, so zero-initialised */
+ static rte_atomic32_t display_stats;
+
+ /* Only one core should display stats */
+ if (rte_atomic32_test_and_set(&display_stats)) {
+ const unsigned int sleeptime = 1;
+
+ printf("Core %u displaying statistics\n", rte_lcore_id());
+
+ /* Longer initial pause so above printf is seen */
+ sleep(sleeptime * 3);
+
+ /* Loop forever: sleep always returns 0 or <= param */
+ while (sleep(sleeptime) <= sleeptime)
+ do_stats_display();
+ }
+ return 0;
+}
+
+/*
+ * Function to set all the node statistic values to zero.
+ * Called at program startup.
+ */
+static void
+clear_stats(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_nodes; i++)
+ nodes[i].stats.rx = nodes[i].stats.rx_drop = 0;
+}
+
+/*
+ * send a burst of traffic to a node, assuming there are packets
+ * available to be sent to this node
+ */
+static void
+flush_rx_queue(uint16_t node)
+{
+ uint16_t j;
+ struct node *cl;
+
+ if (cl_rx_buf[node].count == 0)
+ return;
+
+ cl = &nodes[node];
+ if (rte_ring_enqueue_bulk(cl->rx_q, (void **)cl_rx_buf[node].buffer,
+ cl_rx_buf[node].count) != 0){
+ for (j = 0; j < cl_rx_buf[node].count; j++)
+ rte_pktmbuf_free(cl_rx_buf[node].buffer[j]);
+ cl->stats.rx_drop += cl_rx_buf[node].count;
+ } else
+ cl->stats.rx += cl_rx_buf[node].count;
+
+ cl_rx_buf[node].count = 0;
+}
+
+/*
+ * marks a packet down to be sent to a particular node process
+ */
+static inline void
+enqueue_rx_packet(uint8_t node, struct rte_mbuf *buf)
+{
+ cl_rx_buf[node].buffer[cl_rx_buf[node].count++] = buf;
+}
+
+/*
+ * This function takes a group of packets and routes them
+ * individually to the node process. Very simply round-robins the packets
+ * without checking any of the packet contents.
+ */
+static void
+process_packets(uint32_t port_num __rte_unused, struct rte_mbuf *pkts[],
+ uint16_t rx_count, unsigned int socket_id)
+{
+ uint16_t i;
+ uint8_t node;
+ efd_value_t data[RTE_EFD_BURST_MAX];
+ const void *key_ptrs[RTE_EFD_BURST_MAX];
+
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[RTE_EFD_BURST_MAX];
+
+ for (i = 0; i < rx_count; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(pkts[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = (void *)&ipv4_dst_ip[i];
+ }
+
+ rte_efd_lookup_bulk(efd_table, socket_id, rx_count,
+ (const void **) key_ptrs, data);
+ for (i = 0; i < rx_count; i++) {
+ node = (uint8_t) ((uintptr_t)data[i]);
+
+ if (node >= num_nodes) {
+ /*
+ * Node is out of range, which means that
+ * flow has not been inserted
+ */
+ flow_dist_stats.drop++;
+ rte_pktmbuf_free(pkts[i]);
+ } else {
+ flow_dist_stats.distributed++;
+ enqueue_rx_packet(node, pkts[i]);
+ }
+ }
+
+ for (i = 0; i < num_nodes; i++)
+ flush_rx_queue(i);
+}
+
+/*
+ * Function called by the master lcore of the DPDK process.
+ */
+static void
+do_packet_forwarding(void)
+{
+ unsigned int port_num = 0; /* indexes the port[] array */
+ unsigned int socket_id = rte_socket_id();
+
+ for (;;) {
+ struct rte_mbuf *buf[PACKET_READ_SIZE];
+ uint16_t rx_count;
+
+ /* read a port */
+ rx_count = rte_eth_rx_burst(info->id[port_num], 0,
+ buf, PACKET_READ_SIZE);
+ info->rx_stats.rx[port_num] += rx_count;
+
+ /* Now process the NIC packets read */
+ if (likely(rx_count > 0))
+ process_packets(port_num, buf, rx_count, socket_id);
+
+ /* move to next port */
+ if (++port_num == info->num_ports)
+ port_num = 0;
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ /* initialise the system */
+ if (init(argc, argv) < 0)
+ return -1;
+ RTE_LOG(INFO, APP, "Finished Process Init.\n");
+
+ cl_rx_buf = calloc(num_nodes, sizeof(cl_rx_buf[0]));
+
+ /* clear statistics */
+ clear_stats();
+
+ /* put all other cores to sleep bar master */
+ rte_eal_mp_remote_launch(sleep_lcore, NULL, SKIP_MASTER);
+
+ do_packet_forwarding();
+ return 0;
+}
diff --git a/examples/flow_distributor/node/Makefile b/examples/flow_distributor/node/Makefile
new file mode 100644
index 0000000..8cf7b65
--- /dev/null
+++ b/examples/flow_distributor/node/Makefile
@@ -0,0 +1,48 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overridden by command line or environment
+include $(RTE_SDK)/mk/rte.vars.mk
+
+# binary name
+APP = node
+
+# all source are stored in SRCS-y
+SRCS-y := node.c
+
+CFLAGS += $(WERROR_FLAGS) -O3
+CFLAGS += -I$(SRCDIR)/../shared
+
+include $(RTE_SDK)/mk/rte.extapp.mk
diff --git a/examples/flow_distributor/node/node.c b/examples/flow_distributor/node/node.c
new file mode 100644
index 0000000..1f1e7e7
--- /dev/null
+++ b/examples/flow_distributor/node/node.c
@@ -0,0 +1,417 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <sys/queue.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_malloc.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_atomic.h>
+#include <rte_branch_prediction.h>
+#include <rte_log.h>
+#include <rte_per_lcore.h>
+#include <rte_launch.h>
+#include <rte_lcore.h>
+#include <rte_ring.h>
+#include <rte_launch.h>
+#include <rte_lcore.h>
+#include <rte_debug.h>
+#include <rte_mempool.h>
+#include <rte_mbuf.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ether.h>
+#include <rte_ethdev.h>
+#include <rte_string_fns.h>
+#include <rte_ip.h>
+
+#include "common.h"
+
+/* Number of packets to attempt to read from queue */
+#define PKT_READ_SIZE ((uint16_t)32)
+
+/*
+ * Our node id number - tells us which rx queue to read, and NIC TX
+ * queue to write to.
+ */
+static uint8_t node_id;
+
+#define MBQ_CAPACITY 32
+
+/* maps input ports to output ports for packets */
+static uint8_t output_ports[RTE_MAX_ETHPORTS];
+
+/* buffers up a set of packet that are ready to send */
+struct rte_eth_dev_tx_buffer *tx_buffer[RTE_MAX_ETHPORTS];
+
+/* shared data from distributor. We update statistics here */
+static struct tx_stats *tx_stats;
+
+static struct filter_stats *filter_stats;
+
+/*
+ * print a usage message
+ */
+static void
+usage(const char *progname)
+{
+ printf("Usage: %s [EAL args] -- -n <node_id>\n\n", progname);
+}
+
+/*
+ * Convert the node id number from a string to an int.
+ */
+static int
+parse_node_num(const char *node)
+{
+ char *end = NULL;
+ unsigned long temp;
+
+ if (node == NULL || *node == '\0')
+ return -1;
+
+ temp = strtoul(node, &end, 10);
+ if (end == NULL || *end != '\0')
+ return -1;
+
+ node_id = (uint8_t)temp;
+ return 0;
+}
+
+/*
+ * Parse the application arguments to the node app.
+ */
+static int
+parse_app_args(int argc, char *argv[])
+{
+ int option_index, opt;
+ char **argvopt = argv;
+ const char *progname = NULL;
+ static struct option lgopts[] = { /* no long options */
+ {NULL, 0, 0, 0 }
+ };
+ progname = argv[0];
+
+ while ((opt = getopt_long(argc, argvopt, "n:", lgopts,
+ &option_index)) != EOF) {
+ switch (opt) {
+ case 'n':
+ if (parse_node_num(optarg) != 0) {
+ usage(progname);
+ return -1;
+ }
+ break;
+ default:
+ usage(progname);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Tx buffer error callback
+ */
+static void
+flush_tx_error_callback(struct rte_mbuf **unsent, uint16_t count,
+ void *userdata) {
+ int i;
+ uint8_t port_id = (uintptr_t)userdata;
+
+ tx_stats->tx_drop[port_id] += count;
+
+ /* free the mbufs which failed from transmit */
+ for (i = 0; i < count; i++)
+ rte_pktmbuf_free(unsent[i]);
+
+}
+
+static void
+configure_tx_buffer(uint8_t port_id, uint16_t size)
+{
+ int ret;
+
+ /* Initialize TX buffers */
+ tx_buffer[port_id] = rte_zmalloc_socket("tx_buffer",
+ RTE_ETH_TX_BUFFER_SIZE(size), 0,
+ rte_eth_dev_socket_id(port_id));
+ if (tx_buffer[port_id] == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot allocate buffer for tx "
+ "on port %u\n", (unsigned int) port_id);
+
+ rte_eth_tx_buffer_init(tx_buffer[port_id], size);
+
+ ret = rte_eth_tx_buffer_set_err_callback(tx_buffer[port_id],
+ flush_tx_error_callback, (void *)(intptr_t)port_id);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Cannot set error callback for "
+ "tx buffer on port %u\n", (unsigned int) port_id);
+}
+
+/*
+ * set up output ports so that all traffic on port gets sent out
+ * its paired port. Index using actual port numbers since that is
+ * what comes in the mbuf structure.
+ */
+static void
+configure_output_ports(const struct shared_info *info)
+{
+ int i;
+
+ if (info->num_ports > RTE_MAX_ETHPORTS)
+ rte_exit(EXIT_FAILURE, "Too many ethernet ports. "
+ "RTE_MAX_ETHPORTS = %u\n",
+ (unsigned int)RTE_MAX_ETHPORTS);
+ for (i = 0; i < info->num_ports - 1; i += 2) {
+ uint8_t p1 = info->id[i];
+ uint8_t p2 = info->id[i+1];
+
+ output_ports[p1] = p2;
+ output_ports[p2] = p1;
+
+ configure_tx_buffer(p1, MBQ_CAPACITY);
+ configure_tx_buffer(p2, MBQ_CAPACITY);
+
+ }
+}
+
+/*
+ * Create the hash table that will contain the flows that
+ * the node will handle, which will be used to decide if packet
+ * is transmitted or dropped.
+ */
+static struct rte_hash *
+create_hash_table(const struct shared_info *info)
+{
+ uint32_t num_flows_node = info->num_flows / info->num_nodes;
+ char name[RTE_HASH_NAMESIZE];
+ struct rte_hash *h;
+
+ /* create table */
+ struct rte_hash_parameters hash_params = {
+ .entries = num_flows_node * 2, /* table load = 50% */
+ .key_len = sizeof(uint32_t), /* Store IPv4 dest IP address */
+ .socket_id = rte_socket_id(),
+ .hash_func_init_val = 0,
+ };
+
+ snprintf(name, sizeof(name), "hash_table_%d", node_id);
+ hash_params.name = name;
+ h = rte_hash_create(&hash_params);
+
+ if (h == NULL)
+ rte_exit(EXIT_FAILURE,
+ "Problem creating the hash table for node %d\n",
+ node_id);
+ return h;
+}
+
+static void
+populate_hash_table(const struct rte_hash *h, const struct shared_info *info)
+{
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint32_t num_flows_node = 0;
+ uint64_t target_node;
+
+ /* Add flows in table */
+ for (i = 0; i < info->num_flows; i++) {
+ target_node = i % info->num_nodes;
+ if (target_node != node_id)
+ continue;
+
+ ip_dst = rte_cpu_to_be_32(i);
+
+ ret = rte_hash_add_key(h, (void *) &ip_dst);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u "
+ "in hash table\n", i);
+ else
+ num_flows_node++;
+
+ }
+
+ printf("Hash table: Adding 0x%x keys\n", num_flows_node);
+}
+
+/*
+ * This function performs routing of packets
+ * Just sends each input packet out an output port based solely on the input
+ * port it arrived on.
+ */
+static inline void
+transmit_packet(struct rte_mbuf *buf)
+{
+ int sent;
+ const uint8_t in_port = buf->port;
+ const uint8_t out_port = output_ports[in_port];
+ struct rte_eth_dev_tx_buffer *buffer = tx_buffer[out_port];
+
+ sent = rte_eth_tx_buffer(out_port, node_id, buffer, buf);
+ if (sent)
+ tx_stats->tx[out_port] += sent;
+
+}
+
+static inline void
+handle_packets(struct rte_hash *h, struct rte_mbuf **bufs, uint16_t num_packets)
+{
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[PKT_READ_SIZE];
+ const void *key_ptrs[PKT_READ_SIZE];
+ unsigned int i;
+ int32_t positions[PKT_READ_SIZE] = {0};
+
+ for (i = 0; i < num_packets; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(bufs[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = &ipv4_dst_ip[i];
+ }
+ /* Check if packets belongs to any flows handled by this node */
+ rte_hash_lookup_bulk(h, key_ptrs, num_packets, positions);
+
+ for (i = 0; i < num_packets; i++) {
+ if (likely(positions[i] >= 0)) {
+ filter_stats->passed++;
+ transmit_packet(bufs[i]);
+ } else {
+ filter_stats->drop++;
+ /* Drop packet, as flow is not handled by this node */
+ rte_pktmbuf_free(bufs[i]);
+ }
+ }
+}
+
+/*
+ * Application main function - loops through
+ * receiving and processing packets. Never returns
+ */
+int
+main(int argc, char *argv[])
+{
+ const struct rte_memzone *mz;
+ struct rte_ring *rx_ring;
+ struct rte_hash *h;
+ struct rte_mempool *mp;
+ struct shared_info *info;
+ int need_flush = 0; /* indicates whether we have unsent packets */
+ int retval;
+ void *pkts[PKT_READ_SIZE];
+ uint16_t sent;
+
+ retval = rte_eal_init(argc, argv);
+ if (retval < 0)
+ return -1;
+ argc -= retval;
+ argv += retval;
+
+ if (parse_app_args(argc, argv) < 0)
+ rte_exit(EXIT_FAILURE, "Invalid command-line arguments\n");
+
+ if (rte_eth_dev_count() == 0)
+ rte_exit(EXIT_FAILURE, "No Ethernet ports - bye\n");
+
+ rx_ring = rte_ring_lookup(get_rx_queue_name(node_id));
+ if (rx_ring == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get RX ring - "
+ "is distributor process running?\n");
+
+ mp = rte_mempool_lookup(PKTMBUF_POOL_NAME);
+ if (mp == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get mempool for mbufs\n");
+
+ mz = rte_memzone_lookup(MZ_SHARED_INFO);
+ if (mz == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get port info structure\n");
+ info = mz->addr;
+ tx_stats = &(info->tx_stats[node_id]);
+ filter_stats = &(info->filter_stats[node_id]);
+
+ configure_output_ports(info);
+
+ h = create_hash_table(info);
+
+ populate_hash_table(h, info);
+
+ RTE_LOG(INFO, APP, "Finished Process Init.\n");
+
+ printf("\nNode process %d handling packets\n", node_id);
+ printf("[Press Ctrl-C to quit ...]\n");
+
+ for (;;) {
+ uint16_t rx_pkts = PKT_READ_SIZE;
+ uint8_t port;
+
+ /*
+ * Try dequeuing max possible packets first, if that fails,
+ * get the most we can. Loop body should only execute once,
+ * maximum
+ */
+ while (rx_pkts > 0 &&
+ unlikely(rte_ring_dequeue_bulk(rx_ring, pkts,
+ rx_pkts) != 0))
+ rx_pkts = (uint16_t)RTE_MIN(rte_ring_count(rx_ring),
+ PKT_READ_SIZE);
+
+ if (unlikely(rx_pkts == 0)) {
+ if (need_flush)
+ for (port = 0; port < info->num_ports; port++) {
+ sent = rte_eth_tx_buffer_flush(
+ info->id[port],
+ node_id,
+ tx_buffer[port]);
+ if (unlikely(sent))
+ tx_stats->tx[port] += sent;
+ }
+ need_flush = 0;
+ continue;
+ }
+
+ handle_packets(h, (struct rte_mbuf **)pkts, rx_pkts);
+
+ need_flush = 1;
+ }
+}
diff --git a/examples/flow_distributor/shared/common.h b/examples/flow_distributor/shared/common.h
new file mode 100644
index 0000000..5dcffd6
--- /dev/null
+++ b/examples/flow_distributor/shared/common.h
@@ -0,0 +1,99 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _COMMON_H_
+#define _COMMON_H_
+
+#include <rte_hash_crc.h>
+#include <rte_hash.h>
+
+#define MAX_NODES 16
+/*
+ * Shared port info, including statistics information for display by distributor.
+ * Structure will be put in a memzone.
+ * - All port id values share one cache line as this data will be read-only
+ * during operation.
+ * - All rx statistic values share cache lines, as this data is written only
+ * by the distributor process. (rare reads by stats display)
+ * - The tx statistics have values for all ports per cache line, but the stats
+ * themselves are written by the nodes, so we have a distinct set, on different
+ * cache lines for each node to use.
+ */
+struct rx_stats {
+ uint64_t rx[RTE_MAX_ETHPORTS];
+} __rte_cache_aligned;
+
+struct tx_stats {
+ uint64_t tx[RTE_MAX_ETHPORTS];
+ uint64_t tx_drop[RTE_MAX_ETHPORTS];
+} __rte_cache_aligned;
+
+struct filter_stats {
+ uint64_t drop;
+ uint64_t passed;
+} __rte_cache_aligned;
+
+struct shared_info {
+ uint8_t num_nodes;
+ uint8_t num_ports;
+ uint32_t num_flows;
+ uint8_t id[RTE_MAX_ETHPORTS];
+ struct rx_stats rx_stats;
+ struct tx_stats tx_stats[MAX_NODES];
+ struct filter_stats filter_stats[MAX_NODES];
+};
+
+/* define common names for structures shared between distributor and node */
+#define MP_NODE_RXQ_NAME "MProc_Node_%u_RX"
+#define PKTMBUF_POOL_NAME "MProc_pktmbuf_pool"
+#define MZ_SHARED_INFO "MProc_shared_info"
+
+/*
+ * Given the rx queue name template above, get the queue name
+ */
+static inline const char *
+get_rx_queue_name(unsigned int id)
+{
+ /*
+ * Buffer for return value. Size calculated by %u being replaced
+ * by maximum 3 digits (plus an extra byte for safety)
+ */
+ static char buffer[sizeof(MP_NODE_RXQ_NAME) + 2];
+
+ snprintf(buffer, sizeof(buffer) - 1, MP_NODE_RXQ_NAME, id);
+ return buffer;
+}
+
+#define RTE_LOGTYPE_APP RTE_LOGTYPE_USER1
+
+#endif
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v4 4/5] doc: add EFD library section in Programmers guide
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 0/5] Elastic Flow Distributor Pablo de Lara
` (2 preceding siblings ...)
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 3/5] examples/flow_distributor: sample app to demonstrate EFD usage Pablo de Lara
@ 2017-01-15 12:04 ` Pablo de Lara
2017-01-16 4:15 ` Jerin Jacob
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 5/5] doc: add flow distributor guide Pablo de Lara
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 0/5] Elastic Flow Distributor Pablo de Lara
5 siblings, 1 reply; 63+ messages in thread
From: Pablo de Lara @ 2017-01-15 12:04 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Sameh Gobriel
Signed-off-by: Sameh Gobriel <sameh.gobriel@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 1 +
doc/guides/prog_guide/efd_lib.rst | 438 +++++++++++
doc/guides/prog_guide/img/efd_i1.svg | 130 ++++
doc/guides/prog_guide/img/efd_i10.svg | 384 ++++++++++
doc/guides/prog_guide/img/efd_i11.svg | 319 ++++++++
doc/guides/prog_guide/img/efd_i12.svg | 1008 +++++++++++++++++++++++++
doc/guides/prog_guide/img/efd_i2.svg | 280 +++++++
doc/guides/prog_guide/img/efd_i3.svg | 634 ++++++++++++++++
doc/guides/prog_guide/img/efd_i4.svg | 203 ++++++
doc/guides/prog_guide/img/efd_i5.svg | 183 +++++
doc/guides/prog_guide/img/efd_i6.svg | 1254 ++++++++++++++++++++++++++++++++
doc/guides/prog_guide/img/efd_i7.svg | 790 ++++++++++++++++++++
doc/guides/prog_guide/img/efd_i8.svg | 182 +++++
doc/guides/prog_guide/img/efd_i9.svg | 390 ++++++++++
doc/guides/prog_guide/index.rst | 23 +
doc/guides/rel_notes/release_17_02.rst | 3 +
16 files changed, 6222 insertions(+)
create mode 100644 doc/guides/prog_guide/efd_lib.rst
create mode 100644 doc/guides/prog_guide/img/efd_i1.svg
create mode 100644 doc/guides/prog_guide/img/efd_i10.svg
create mode 100644 doc/guides/prog_guide/img/efd_i11.svg
create mode 100644 doc/guides/prog_guide/img/efd_i12.svg
create mode 100644 doc/guides/prog_guide/img/efd_i2.svg
create mode 100644 doc/guides/prog_guide/img/efd_i3.svg
create mode 100644 doc/guides/prog_guide/img/efd_i4.svg
create mode 100644 doc/guides/prog_guide/img/efd_i5.svg
create mode 100644 doc/guides/prog_guide/img/efd_i6.svg
create mode 100644 doc/guides/prog_guide/img/efd_i7.svg
create mode 100644 doc/guides/prog_guide/img/efd_i8.svg
create mode 100644 doc/guides/prog_guide/img/efd_i9.svg
diff --git a/MAINTAINERS b/MAINTAINERS
index b124f6e..66e9466 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -532,6 +532,7 @@ EFD
M: Byron Marohn <byron.marohn@intel.com>
M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
F: lib/librte_efd/
+F: doc/guides/prog_guide/efd_lib.rst
F: app/test/test_efd*
F: examples/flow_distributor/
diff --git a/doc/guides/prog_guide/efd_lib.rst b/doc/guides/prog_guide/efd_lib.rst
new file mode 100644
index 0000000..c489ae4
--- /dev/null
+++ b/doc/guides/prog_guide/efd_lib.rst
@@ -0,0 +1,438 @@
+.. BSD LICENSE
+ Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of Intel Corporation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+.. _Efd_Library:
+
+Elastic Flow Distributor Library
+================================
+
+Introduction
+------------
+
+In Data Centers today, clustering and scheduling of distributed workloads
+is a very common task. Many workloads require a deterministic
+partitioning of a flat key space among a cluster of machines. When a
+packet enters the cluster, the ingress node will direct the packet to
+its handling node. For example, data-centers with disaggregated storage
+use storage metadata tables to forward I/O requests to the correct back end
+storage cluster, stateful packet inspection will use match incoming
+flows to signatures in flow tables to send incoming packets to their
+intended deep packet inspection (DPI) devices, and so on.
+
+EFD is a distributor library that uses perfect hashing to determine a
+target/value for a given incoming flow key. It has the following
+advantages: first, because it uses perfect hashing it does not store the
+key itself and hence lookup performance is not dependent on the key
+size. Second, the target/value can be any arbitrary value hence the
+system designer and/or operator can better optimize service rates and
+inter-cluster network traffic locating. Third, since the storage
+requirement is much smaller than a hash-based flow table (i.e. better
+fit for CPU cache), EFD can scale to millions of flow keys. Finally,
+with the current optimized library implementation, performance is fully
+scalable with any number of CPU cores.
+
+Flow Based Distribution
+-----------------------
+
+Computation Based Schemes
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Flow distribution and/or load balancing can be simply done using a
+stateless computation, for instance using round-robin or a simple
+computation based on the flow key as an input. For example, a hash
+function can be used to direct a certain flow to a target based on
+the flow key (e.g. ``h(key) mod n``) where h(key) is the hash value of the
+flow key and n is the number of possible targets.
+
+.. _figure_efd1:
+
+.. figure:: img/efd_i1.*
+
+ Load Balancing Using Front End Node
+
+In this scheme (:numref:`figure_efd1`), the front end server/distributor/load balancer
+extracts the flow key from the input packet and applies a computation to determine where
+this flow should be directed. Intuitively, this scheme is very simple
+and requires no state to be kept at the front end node, and hence,
+storage requirements are minimum.
+
+.. _figure_efd2:
+
+.. figure:: img/efd_i2.*
+
+ Consistent Hashing
+
+A widely used flow distributor that belongs to the same category of
+computation-based schemes is ``consistent hashing``, shown in :numref:`figure_efd2`.
+Target destinations (shown in red) are hashed into the same space as the flow
+keys (shown in blue), and keys are mapped to the nearest target in a clockwise
+fashion. Dynamically adding and removing targets with consistent hashing
+requires only K/n keys to be remapped on average, where K is the number of
+keys, and n is the number of targets. In contrast, in a traditional hash-based
+scheme, a change in the number of targets causes nearly all keys to be
+remapped.
+
+Although computation-based schemes are simple and need very little
+storage requirement, they suffer from the drawback that the system
+designer/operator can’t fully control the target to assign a specific
+key, as this is dictated by the hash function.
+Deterministically co-locating of keys together (for example, to minimize
+inter-server traffic or to optimize for network traffic conditions,
+target load, etc.) is simply not possible.
+
+Flow-Table Based Schemes
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+When using a Flow-Table based scheme to handle flow distribution/load
+balancing, in contrast with computation-based schemes, the system designer
+has the flexibility of assigning a given flow to any given
+target. The flow table (e.g. DPDK RTE Hash Library) will simply store
+both the flow key and the target value.
+
+.. _figure_efd3:
+
+.. figure:: img/efd_i3.*
+
+ Table Based Flow Distribution
+
+As shown in :numref:`figure_efd3`, when doing a lookup, the flow-table
+is indexed with the hash of the flow key and the keys (more than one is possible,
+because of hash collision) stored in this index and corresponding values
+are retrieved. The retrieved key(s) is matched with the input flow key
+and if there is a match the value (target id) is returned.
+
+The drawback of using a hash table for flow distribution/load balancing
+is the storage requirement, since the flow table need to store keys,
+signatures and target values. This doesn't allow this scheme to scale to
+millions of flow keys. Large tables will usually not fit in
+the CPU cache, and hence, the lookup performance is degraded because of
+the latency to access the main memory.
+
+EFD Based Scheme
+~~~~~~~~~~~~~~~~
+
+EFD combines the advantages of both flow-table based and computation-based
+schemes. It doesn't require the large storage necessary for
+flow-table based schemes (because EFD doesn't store the key as explained
+below), and it supports any arbitrary value for any given key.
+
+.. _figure_efd4:
+
+.. figure:: img/efd_i4.*
+
+ Searching for Perfect Hash Function
+
+The basic idea of EFD is when a given key is to be inserted, a family of
+hash functions is searched until the correct hash function that maps the
+input key to the correct value is found, as shown in :numref:`figure_efd4`.
+However, rather than explicitly storing all keys and their associated values,
+EFD stores only indices of hash functions that map keys to values, and
+thereby consumes much less space than conventional flow-based tables.
+The lookup operation is very simple, similar to a computational-based
+scheme: given an input key the lookup operation is reduced to hashing
+that key with the correct hash function.
+
+.. _figure_efd5:
+
+.. figure:: img/efd_i5.*
+
+ Divide and Conquer for Millions of Keys
+
+Intuitively, finding a hash function that maps each of a large number
+(millions) of input keys to the correct output value is effectively
+impossible, as a result EFD, as shown in :numref:`figure_efd5`,
+breaks the problem into smaller pieces (divide and conquer).
+EFD divides the entire input key set into many small groups.
+Each group consists of approximately 20-28 keys (a configurable parameter
+for the library), then, for each small group, a brute force search to find
+a hash function that produces the correct outputs for each key in the group.
+
+It should be mentioned that, since the online lookup table for EFD
+doesn't store the key itself, the size of the EFD table is independent
+of the key size and hence EFD lookup performance which is almost
+constant irrespective of the length of the key which is a highly
+desirable feature especially for longer keys.
+
+In summary, EFD is a set separation data structure that supports millions of
+keys. It is used to distribute a given key to an intended target. By itself
+EFD is not a FIB data structure with an exact match the input flow key.
+
+.. _Efd_example:
+
+Example of EFD Library Usage
+----------------------------
+
+EFD can be used along the data path of many network functions and middleboxes.
+As previously mentioned, it can used as an index table for
+<key,value> pairs, meta-data for objects, a flow-level load balancer, etc.
+:numref:`figure_efd6` shows an example of using EFD as a flow-level load
+balancer, where flows are received at a front end server before being forwarded
+to the target back end server for processing. The system designer would
+deterministically co-locate flows together in order to minimize cross-server
+interaction.
+(For example, flows requesting certain webpage objects are co-located
+together, to minimize forwarding of common objects across servers).
+
+.. _figure_efd6:
+
+.. figure:: img/efd_i6.*
+
+ EFD as a Flow-Level Load Balancer
+
+As shown in :numref:`figure_efd6`, the front end server will have an EFD table that
+stores for each group what is the perfect hash index that satisfies the
+correct output. Because the table size is small and fits in cache (since
+keys are not stored), it sustains a large number of flows (N*X, where N
+is the maximum number of flows served by each back end server of the X
+possible targets).
+
+With an input flow key, the group id is computed (for example, using
+last few bits of CRC hash) and then the EFD table is indexed with the
+group id to retrieve the corresponding hash index to use. Once the index
+is retrieved the key is hashed using this hash function and the result
+will be the intended correct target where this flow is supposed to be
+processed.
+
+It should be noted that as a result of EFD not matching the exact key but
+rather distributing the flows to a target back end node based on the
+perfect hash index, a key that has not been inserted before
+will be distributed to a valid target. Hence, a local table which stores
+the flows served at each node is used and is
+exact matched with the input key to rule out new never seen before
+flows.
+
+.. _Efd_api:
+
+Library API Overview
+--------------------
+
+The EFD library API is created with a very similar semantics of a
+hash-index or a flow table. The application creates an EFD table for a
+given maximum number of flows, a function is called to insert a flow key
+with a specific target value, and another function is used to retrieve
+target values for a given individual flow key or a bulk of keys.
+
+EFD Table Create
+~~~~~~~~~~~~~~~~
+
+The function ``rte_efd_create()`` is used to create and return a pointer
+to an EFD table that is sized to hold up to num_flows key.
+The online version of the EFD table (the one that does
+not store the keys and is used for lookups) will be allocated and
+created in the last level cache (LLC) of the socket defined by the
+online_socket_bitmask, while the offline EFD table (the one that
+stores the keys and is used for key inserts and for computing the
+perfect hashing) is allocated and created in the LLC of the socket
+defined by offline_socket_bitmask. It should be noted, that for
+highest performance the socket id should match that where the thread is
+running, i.e. the online EFD lookup table should be created on the same
+socket as where the lookup thread is running.
+
+EFD Insert and Update
+~~~~~~~~~~~~~~~~~~~~~
+
+The EFD function to insert a key or update a key to a new value is
+``rte_efd_update()``. This function will update an existing key to
+a new value (target) if the key has already been inserted
+before, or will insert the <key,value> pair if this key has not been inserted
+before. It will return 0 upon success. It will return
+``EFD_UPDATE_WARN_GROUP_FULL (1)`` if the operation is insert, and the
+last available space in the key's group was just used. It will return
+``EFD_UPDATE_FAILED (2)`` when the insertion or update has failed (either it
+failed to find a suitable perfect hash or the group was full). The function
+will return ``EFD_UPDATE_NO_CHANGE (3)`` if there is no change to the EFD
+table (i.e, same value already exists).
+
+EFD Lookup
+~~~~~~~~~~
+
+To lookup a certain key in an EFD table, the function ``rte_efd_lookup()``
+is used to return the value associated with single key.
+As previously mentioned, if the key has been inserted, the correct value
+inserted is returned, if the key has not been inserted before,
+a ‘random’ value (based on hashing of the key) is returned.
+For better performance and to decrease the overhead of
+function calls per key, it is always recommended to use a bulk lookup
+function (simultaneous lookup of multiple keys) instead of a single key
+lookup function. ``rte_efd_lookup_bulk()`` is the bulk lookup function,
+that looks up num_keys simultaneously stored in the key_list and the
+corresponding return values will be returned in the value_list.
+
+EFD Delete
+~~~~~~~~~~
+
+To delete a certain key in an EFD table, the function
+``rte_efd_delete()`` can be used. The function returns zero upon success
+when the key has been found and deleted. Socket_id is the parameter to
+use to lookup the existing value, which is ideally the caller's socket id.
+The previous value associated with this key will be returned
+in the prev_value argument.
+
+.. _Efd_internals:
+
+Library Internals
+-----------------
+
+This section provides the brief high-level idea and an overview
+of the library internals to accompany the RFC. The intent of this
+section is to explain to readers the high-level implementation of
+insert, lookup and group rebalancing in the EFD library.
+
+Insert Function Internals
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As previously mentioned the EFD divides the whole set of keys into
+groups of a manageable size (e.g. 28 keys) and then searches for the
+perfect hash that satisfies the intended target value for each key. EFD
+stores two version of the <key,value> table:
+
+- Offline Version (in memory): Only used for the insertion/update
+ operation, which is less frequent than the lookup operation. In the
+ offline version the exact keys for each group is stored. When a new
+ key is added, the hash function is updated that will satisfy the
+ value for the new key together with the all old keys already inserted
+ in this group.
+
+- Online Version (in cache): Used for the frequent lookup operation. In
+ the online version, as previously mentioned, the keys are not stored
+ but rather only the hash index for each group.
+
+.. _figure_efd7:
+
+.. figure:: img/efd_i7.*
+
+ Group Assignment
+
+:numref:`figure_efd7` depicts the group assignment for 7 flow keys as an example.
+Given a flow key, a hash function (in our implementation CRC hash) is
+used to get the group id. As shown in the figure, the groups can be
+unbalanced. (We highlight group rebalancing further below).
+
+.. _figure_efd8:
+
+.. figure:: img/efd_i8.*
+
+ Perfect Hash Search - Assigned Keys & Target Value
+
+Focusing on one group that has four keys, :numref:`figure_efd8` depicts the search
+algorithm to find the perfect hash function. Assuming that the target
+value bit for the keys is as shown in the figure, then the online EFD
+table will store a 16 bit hash index and 16 bit lookup table per group
+per value bit.
+
+.. _figure_efd9:
+
+.. figure:: img/efd_i9.*
+
+ Perfect Hash Search - Satisfy Target Values
+
+For a given keyX, a hash function ``(h(keyX, seed1) + index * h(keyX, seed2))``
+is used to point to certain bit index in the 16bit lookup_table value,
+as shown in :numref:`figure_efd9`.
+The insert function will brute force search for all possible values for the
+hash index until a non conflicting lookup_table is found.
+
+.. _figure_efd10:
+
+.. figure:: img/efd_i10.*
+
+ Finding Hash Index for Conflict Free lookup_table
+
+For example, since both key3 and key7 have a target bit value of 1, it
+is okay if the hash function of both keys point to the same bit in the
+lookup table. A conflict will occur if a hash index is used that maps
+both Key4 and Key7 to the same index in the lookup_table,
+as shown in :numref:`figure_efd10`, since their target value bit are not the same.
+Once a hash index is found that produces a lookup_table with no
+contradictions, this index is stored for this group. This procedure is
+repeated for each bit of target value.
+
+Lookup Function Internals
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The design principle of EFD is that lookups are much more frequent than
+inserts, and hence, EFD's design optimizes for the lookups which are
+faster and much simpler than the slower insert procedure (inserts are
+slow, because of perfect hash search as previously discussed).
+
+.. _figure_efd11:
+
+.. figure:: img/efd_i11.*
+
+ EFD Lookup Operation
+
+:numref:`figure_efd11` depicts the lookup operation for EFD. Given an input key,
+the group id is computed (using CRC hash) and then the hash index for this
+group is retrieved from the EFD table. Using the retrieved hash index,
+the hash function ``h(key, seed1) + index *h(key, seed2)`` is used which will
+result in an index in the lookup_table, the bit corresponding to this
+index will be the target value bit. This procedure is repeated for each
+bit of the target value.
+
+Group Rebalancing Function Internals
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When discussing EFD inserts and lookups, the discussion is simplified by
+assuming that a group id is simply a result of hash function. However,
+since hashing in general is not perfect and will not always produce a
+uniform output, this simplified assumption will lead to unbalanced
+groups, i.e., some group will have more keys than other groups.
+Typically, and to minimize insert time with an increasing number of keys,
+it is preferable that all groups will have a balanced number of keys, so
+the brute force search for the perfect hash terminates with a valid hash
+index. In order to achieve this target, groups are rebalanced during
+runtime inserts, and keys are moved around from a busy group to a less
+crowded group as the more keys are inserted.
+
+.. _figure_efd12:
+
+.. figure:: img/efd_i12.*
+
+ Runtime Group Rebalancing
+
+:numref:`figure_efd12` depicts the high level idea of group rebalancing, given an
+input key the hash result is split into two parts a chunk id and 8-bit
+bin id. A chunk contains 64 different groups and 256 bins (i.e. for any
+given bin it can map to 4 distinct groups). When a key is inserted, the
+bin id is computed, for example in :numref:`figure_efd12` bin_id=2,
+and since each bin can be mapped to one of four different groups (2 bit storage),
+the four possible mappings are evaluated and the one that will result in a
+balanced key distribution across these four is selected the mapping result
+is stored in these two bits.
+
+
+References
+-----------
+
+1- EFD is based on collaborative research work between Intel and
+Carnegie Mellon University (CMU), interested readers can refer to the paper
+“Scaling Up Clustered Network Appliances with ScaleBricks;” Dong Zhou et al.
+at SIGCOMM 2015 (`http://conferences.sigcomm.org/sigcomm/2015/pdf/papers/p241.pdf`)
+for more information.
diff --git a/doc/guides/prog_guide/img/efd_i1.svg b/doc/guides/prog_guide/img/efd_i1.svg
new file mode 100644
index 0000000..7f8fcb3
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i1.svg
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i1.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="3.25609in" height="3.375in"
+ viewBox="0 0 234.439 243" xml:space="preserve" color-interpolation-filters="sRGB" class="st10">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st5 {marker-end:url(#mrkr5-12);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:6}
+ .st6 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.70422535211268}
+ .st7 {stroke:#5b9bd5;stroke-dasharray:2.25,4.5;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.25}
+ .st8 {marker-end:url(#mrkr5-39);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st9 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st10 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-12" class="st6" v:arrowType="5" v:arrowSize="2" v:setback="2.485" refX="-2.485" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-1.42,-1.42) "/>
+ </marker>
+ <marker id="mrkr5-39" class="st9" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(77.718,-113.348)">
+ <title>Square</title>
+ <desc>LB</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="18" cy="225" width="36" height="36"/>
+ <g id="shadow2-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="207" width="36" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="207" width="36" height="36" class="st3"/>
+ <text x="13.18" y="228" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>LB</text> </g>
+ <g id="shape3-7" v:mID="3" v:groupContext="shape" transform="translate(37.0513,-131.348)">
+ <title>Sheet.3</title>
+ <path d="M0 243 L25.76 243" class="st5"/>
+ </g>
+ <g id="shape4-13" v:mID="4" v:groupContext="shape" transform="translate(167.718,-178.598)">
+ <title>Square.4</title>
+ <desc>Target 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="220.5" width="45" height="45"/>
+ <g id="shadow4-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="198" width="45" height="45" class="st2"/>
+ </g>
+ <rect x="0" y="198" width="45" height="45" class="st3"/>
+ <text x="5.74" y="223.5" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target 1</text> </g>
+ <g id="shape5-19" v:mID="5" v:groupContext="shape" transform="translate(167.718,-121.005)">
+ <title>Square.5</title>
+ <desc>Target 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="220.5" width="45" height="45"/>
+ <g id="shadow5-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="198" width="45" height="45" class="st2"/>
+ </g>
+ <rect x="0" y="198" width="45" height="45" class="st3"/>
+ <text x="5.74" y="223.5" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target 2</text> </g>
+ <g id="shape7-25" v:mID="7" v:groupContext="shape" transform="translate(167.718,-23.3478)">
+ <title>Square.7</title>
+ <desc>Target N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="220.5" width="45" height="45"/>
+ <g id="shadow7-26" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="198" width="45" height="45" class="st2"/>
+ </g>
+ <rect x="0" y="198" width="45" height="45" class="st3"/>
+ <text x="5.05" y="223.5" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target N</text> </g>
+ <g id="shape8-31" v:mID="8" v:groupContext="shape" transform="translate(433.218,132.402) rotate(90)">
+ <title>Sheet.8</title>
+ <path d="M0 243 L34.59 243" class="st7"/>
+ </g>
+ <g id="shape9-34" v:mID="9" v:groupContext="shape" transform="translate(-78.4279,-37.1059) rotate(-52.2532)">
+ <title>Sheet.9</title>
+ <path d="M0 243 L81.18 243" class="st8"/>
+ </g>
+ <g id="shape11-40" v:mID="11" v:groupContext="shape" transform="translate(60.3469,-125.414) rotate(-12.6875)">
+ <title>Sheet.11</title>
+ <path d="M0 243 L48.32 243" class="st8"/>
+ </g>
+ <g id="shape12-45" v:mID="12" v:groupContext="shape" transform="translate(319.172,-18.1081) rotate(57.7244)">
+ <title>Sheet.12</title>
+ <path d="M0 243 L94.09 243" class="st8"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i10.svg b/doc/guides/prog_guide/img/efd_i10.svg
new file mode 100644
index 0000000..d26ec61
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i10.svg
@@ -0,0 +1,384 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i11.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="9.76715in" height="2.82917in"
+ viewBox="0 0 703.234 203.701" xml:space="preserve" color-interpolation-filters="sRGB" class="st15">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st4 {fill:#000000;font-family:Arial;font-size:0.998566em}
+ .st5 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st6 {fill:#000000;font-family:Arial;font-size:0.918686em;font-style:italic}
+ .st7 {fill:#000000;font-family:Arial;font-size:0.918686em}
+ .st8 {fill:#7e8d96;font-family:Arial;font-size:0.998566em;font-weight:bold}
+ .st9 {fill:#00b050;font-family:Arial;font-size:0.998566em;font-weight:bold}
+ .st10 {fill:#ff0000;font-family:Arial;font-size:0.998566em;font-weight:bold}
+ .st11 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st12 {fill:#ffffff;font-family:Arial;font-size:1.49785em}
+ .st13 {stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st14 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st15 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(19.0195,-93.4328)">
+ <title>Sheet.3</title>
+ <path d="M0 182.93 C0 180.64 1.87 178.78 4.16 178.78 L109.18 178.78 C111.47 178.78 113.33 180.64 113.33 182.93 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.16 203.7 C1.87 203.7 0 201.84 0 199.55 L0 182.93 Z"
+ class="st1"/>
+ </g>
+ <g id="shape4-3" v:mID="4" v:groupContext="shape" transform="translate(19.0195,-93.4328)">
+ <title>Sheet.4</title>
+ <path d="M0 182.93 C0 180.64 1.87 178.78 4.16 178.78 L109.18 178.78 C111.47 178.78 113.33 180.64 113.33 182.93 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.16 203.7 C1.87 203.7 0 201.84 0 199.55 L0 182.93 Z"
+ class="st2"/>
+ </g>
+ <g id="shape5-5" v:mID="5" v:groupContext="shape" transform="translate(19.0195,-96.9057)">
+ <title>Sheet.5</title>
+ <desc>Key1: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.0575" cy="196.509" width="116.12" height="14.3829"/>
+ <path d="M116.11 189.32 L0 189.32 L0 203.7 L116.11 203.7 L116.11 189.32" class="st3"/>
+ <text x="15.59" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1: Value = 0</text> </g>
+ <g id="shape6-9" v:mID="6" v:groupContext="shape" transform="translate(19.0195,-68.6284)">
+ <title>Sheet.6</title>
+ <path d="M0 182.93 C0 180.64 1.87 178.78 4.16 178.78 L109.18 178.78 C111.47 178.78 113.33 180.64 113.33 182.93 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.16 203.7 C1.87 203.7 0 201.84 0 199.55 L0 182.93 Z"
+ class="st1"/>
+ </g>
+ <g id="shape7-11" v:mID="7" v:groupContext="shape" transform="translate(19.0195,-68.6284)">
+ <title>Sheet.7</title>
+ <path d="M0 182.93 C0 180.64 1.87 178.78 4.16 178.78 L109.18 178.78 C111.47 178.78 113.33 180.64 113.33 182.93 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.16 203.7 C1.87 203.7 0 201.84 0 199.55 L0 182.93 Z"
+ class="st2"/>
+ </g>
+ <g id="shape8-13" v:mID="8" v:groupContext="shape" transform="translate(19.0195,-72.0832)">
+ <title>Sheet.8</title>
+ <desc>Key3: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.0575" cy="196.509" width="116.12" height="14.3829"/>
+ <path d="M116.11 189.32 L0 189.32 L0 203.7 L116.11 203.7 L116.11 189.32" class="st3"/>
+ <text x="15.59" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3: Value = 1</text> </g>
+ <g id="shape9-17" v:mID="9" v:groupContext="shape" transform="translate(19.0195,-43.5843)">
+ <title>Sheet.9</title>
+ <path d="M0 182.84 C-0 180.53 1.88 178.66 4.19 178.66 L109.15 178.66 C111.46 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.53 C113.33 201.84 111.46 203.7 109.15 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.53 L0 182.84 Z"
+ class="st1"/>
+ </g>
+ <g id="shape10-19" v:mID="10" v:groupContext="shape" transform="translate(19.0195,-43.5843)">
+ <title>Sheet.10</title>
+ <path d="M0 182.84 C-0 180.53 1.88 178.66 4.19 178.66 L109.15 178.66 C111.46 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.53 C113.33 201.84 111.46 203.7 109.15 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.53 L0 182.84 Z"
+ class="st2"/>
+ </g>
+ <g id="shape11-21" v:mID="11" v:groupContext="shape" transform="translate(19.0195,-47.1109)">
+ <title>Sheet.11</title>
+ <desc>Key4: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.0575" cy="196.509" width="116.12" height="14.3829"/>
+ <path d="M116.11 189.32 L0 189.32 L0 203.7 L116.11 203.7 L116.11 189.32" class="st3"/>
+ <text x="15.59" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4: Value = 0</text> </g>
+ <g id="shape12-25" v:mID="12" v:groupContext="shape" transform="translate(19.0195,-19.0195)">
+ <title>Sheet.12</title>
+ <path d="M0 182.84 C-0 180.53 1.88 178.66 4.19 178.66 L109.15 178.66 C111.46 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.53 C113.33 201.84 111.46 203.7 109.15 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.53 L0 182.84 Z"
+ class="st1"/>
+ </g>
+ <g id="shape13-27" v:mID="13" v:groupContext="shape" transform="translate(19.0195,-19.0195)">
+ <title>Sheet.13</title>
+ <path d="M0 182.84 C-0 180.53 1.88 178.66 4.19 178.66 L109.15 178.66 C111.46 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.53 C113.33 201.84 111.46 203.7 109.15 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.53 L0 182.84 Z"
+ class="st2"/>
+ </g>
+ <g id="shape14-29" v:mID="14" v:groupContext="shape" transform="translate(19.0195,-22.5475)">
+ <title>Sheet.14</title>
+ <desc>Key7: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.0575" cy="196.509" width="116.12" height="14.3829"/>
+ <path d="M116.11 189.32 L0 189.32 L0 203.7 L116.11 203.7 L116.11 189.32" class="st3"/>
+ <text x="15.59" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7: Value = 1</text> </g>
+ <g id="shape15-33" v:mID="15" v:groupContext="shape" transform="translate(141.656,-45.5615)">
+ <title>Sheet.15</title>
+ <path d="M0 169.01 L22.75 169.01 L22.75 157.45 L45.5 180.57 L22.75 203.7 L22.75 192.14 L0 192.14 L0 169.01 Z"
+ class="st5"/>
+ </g>
+ <g id="shape16-35" v:mID="16" v:groupContext="shape" transform="translate(193.22,-56.0464)">
+ <title>Sheet.16</title>
+ <path d="M0 182.84 C0 180.53 1.88 178.66 4.19 178.66 L96.55 178.66 C98.87 178.66 100.73 180.53 100.73 182.84 L100.73
+ 199.54 C100.73 201.84 98.87 203.7 96.55 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.54 L0 182.84 Z"
+ class="st1"/>
+ </g>
+ <g id="shape17-37" v:mID="17" v:groupContext="shape" transform="translate(193.22,-56.0464)">
+ <title>Sheet.17</title>
+ <path d="M0 182.84 C0 180.53 1.88 178.66 4.19 178.66 L96.55 178.66 C98.87 178.66 100.73 180.53 100.73 182.84 L100.73
+ 199.54 C100.73 201.84 98.87 203.7 96.55 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.54 L0 182.84 Z"
+ class="st2"/>
+ </g>
+ <g id="shape18-39" v:mID="18" v:groupContext="shape" transform="translate(228.157,-66.9545)">
+ <title>Sheet.18</title>
+ <desc>F</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.63538" cy="197.084" width="11.28" height="13.2327"/>
+ <path d="M11.27 190.47 L0 190.47 L0 203.7 L11.27 203.7 L11.27 190.47" class="st3"/>
+ <text x="2.27" y="200.39" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>F</text> </g>
+ <g id="shape19-43" v:mID="19" v:groupContext="shape" transform="translate(234.88,-66.9545)">
+ <title>Sheet.19</title>
+ <desc>(key,</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.261" cy="197.084" width="34.53" height="13.2327"/>
+ <path d="M34.52 190.47 L0 190.47 L0 203.7 L34.52 203.7 L34.52 190.47" class="st3"/>
+ <text x="5.32" y="200.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(key, </text> </g>
+ <g id="shape20-47" v:mID="20" v:groupContext="shape" transform="translate(198.215,-53.7734)">
+ <title>Sheet.20</title>
+ <desc>hash_index =</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="41.4128" cy="197.084" width="82.83" height="13.2327"/>
+ <path d="M82.83 190.47 L0 190.47 L0 203.7 L82.83 203.7 L82.83 190.47" class="st3"/>
+ <text x="8.47" y="200.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash_index = </text> </g>
+ <g id="shape21-51" v:mID="21" v:groupContext="shape" transform="translate(274.858,-53.7734)">
+ <title>Sheet.21</title>
+ <desc>i)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.28241" cy="197.084" width="10.57" height="13.2327"/>
+ <path d="M10.56 190.47 L0 190.47 L0 203.7 L10.56 203.7 L10.56 190.47" class="st3"/>
+ <text x="2.22" y="200.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>i)</text> </g>
+ <g id="shape22-55" v:mID="22" v:groupContext="shape" transform="translate(351.453,-93.7923)">
+ <title>Sheet.22</title>
+ <path d="M0 182.84 C0 180.53 1.88 178.66 4.19 178.66 L109.16 178.66 C111.47 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.54 C113.33 201.84 111.47 203.7 109.16 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.54 L0 182.84 Z"
+ class="st1"/>
+ </g>
+ <g id="shape23-57" v:mID="23" v:groupContext="shape" transform="translate(351.453,-93.7923)">
+ <title>Sheet.23</title>
+ <path d="M0 182.84 C0 180.53 1.88 178.66 4.19 178.66 L109.16 178.66 C111.47 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.54 C113.33 201.84 111.47 203.7 109.16 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.54 L0 182.84 Z"
+ class="st2"/>
+ </g>
+ <g id="shape24-59" v:mID="24" v:groupContext="shape" transform="translate(355.798,-97.3147)">
+ <title>Sheet.24</title>
+ <desc>Key1: Position 4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="51.7083" cy="196.509" width="103.42" height="14.3829"/>
+ <path d="M103.42 189.32 L0 189.32 L0 203.7 L103.42 203.7 L103.42 189.32" class="st3"/>
+ <text x="8.41" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1: Position 4</text> </g>
+ <g id="shape25-63" v:mID="25" v:groupContext="shape" transform="translate(351.453,-68.9879)">
+ <title>Sheet.25</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape26-65" v:mID="26" v:groupContext="shape" transform="translate(351.453,-68.9879)">
+ <title>Sheet.26</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape27-67" v:mID="27" v:groupContext="shape" transform="translate(355.798,-72.4921)">
+ <title>Sheet.27</title>
+ <desc>Key3: Position 6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="51.7083" cy="196.509" width="103.42" height="14.3829"/>
+ <path d="M103.42 189.32 L0 189.32 L0 203.7 L103.42 203.7 L103.42 189.32" class="st3"/>
+ <text x="8.41" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3: Position 6</text> </g>
+ <g id="shape28-71" v:mID="28" v:groupContext="shape" transform="translate(351.453,-44.0636)">
+ <title>Sheet.28</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape29-73" v:mID="29" v:groupContext="shape" transform="translate(351.453,-44.0636)">
+ <title>Sheet.29</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape30-75" v:mID="30" v:groupContext="shape" transform="translate(351.215,-47.5198)">
+ <title>Sheet.30</title>
+ <desc>Key4: Position 14</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="55.5403" cy="196.509" width="111.09" height="14.3829"/>
+ <path d="M111.08 189.32 L0 189.32 L0 203.7 L111.08 203.7 L111.08 189.32" class="st3"/>
+ <text x="8.91" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4: Position 14</text> </g>
+ <g id="shape31-79" v:mID="31" v:groupContext="shape" transform="translate(351.453,-19.4988)">
+ <title>Sheet.31</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape32-81" v:mID="32" v:groupContext="shape" transform="translate(351.453,-19.4988)">
+ <title>Sheet.32</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape33-83" v:mID="33" v:groupContext="shape" transform="translate(351.215,-22.9565)">
+ <title>Sheet.33</title>
+ <desc>Key7: Position 14</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="55.5403" cy="196.509" width="111.09" height="14.3829"/>
+ <path d="M111.08 189.32 L0 189.32 L0 203.7 L111.08 203.7 L111.08 189.32" class="st3"/>
+ <text x="8.91" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7: Position 14</text> </g>
+ <g id="shape34-87" v:mID="34" v:groupContext="shape" transform="translate(299.89,-46.0408)">
+ <title>Sheet.34</title>
+ <path d="M0 169.01 L22.75 169.01 L22.75 157.45 L45.5 180.57 L22.75 203.7 L22.75 192.14 L0 192.14 L0 169.01 Z"
+ class="st5"/>
+ </g>
+ <g id="shape35-89" v:mID="35" v:groupContext="shape" transform="translate(528.896,-117.518)">
+ <title>Sheet.35</title>
+ <path d="M0 182.94 C0 180.66 1.89 178.78 4.17 178.78 L137.64 178.78 C139.92 178.78 141.79 180.66 141.79 182.94 L141.79
+ 199.57 C141.79 201.84 139.92 203.7 137.64 203.7 L4.17 203.7 C1.89 203.7 0 201.84 0 199.57 L0 182.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape36-91" v:mID="36" v:groupContext="shape" transform="translate(528.896,-117.518)">
+ <title>Sheet.36</title>
+ <path d="M0 182.94 C0 180.66 1.89 178.78 4.17 178.78 L137.64 178.78 C139.92 178.78 141.79 180.66 141.79 182.94 L141.79
+ 199.57 C141.79 201.84 139.92 203.7 137.64 203.7 L4.17 203.7 C1.89 203.7 0 201.84 0 199.57 L0 182.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape37-93" v:mID="37" v:groupContext="shape" transform="translate(530.056,-121.017)">
+ <title>Sheet.37</title>
+ <desc>0000</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="19.1585" cy="196.509" width="38.32" height="14.3829"/>
+ <path d="M38.32 189.32 L0 189.32 L0 203.7 L38.32 203.7 L38.32 189.32" class="st3"/>
+ <text x="5.83" y="200.1" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0000 </text> </g>
+ <g id="shape38-97" v:mID="38" v:groupContext="shape" transform="translate(567.215,-121.017)">
+ <title>Sheet.38</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.7483" cy="196.509" width="11.5" height="14.3829"/>
+ <path d="M11.5 189.32 L0 189.32 L0 203.7 L11.5 203.7 L11.5 189.32" class="st3"/>
+ <text x="2.42" y="200.1" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape39-101" v:mID="39" v:groupContext="shape" transform="translate(576.215,-121.017)">
+ <title>Sheet.39</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.7483" cy="196.509" width="11.5" height="14.3829"/>
+ <path d="M11.5 189.32 L0 189.32 L0 203.7 L11.5 203.7 L11.5 189.32" class="st3"/>
+ <text x="2.42" y="200.1" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape40-105" v:mID="40" v:groupContext="shape" transform="translate(584.486,-121.017)">
+ <title>Sheet.40</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.7483" cy="196.509" width="11.5" height="14.3829"/>
+ <path d="M11.5 189.32 L0 189.32 L0 203.7 L11.5 203.7 L11.5 189.32" class="st3"/>
+ <text x="2.42" y="200.1" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape41-109" v:mID="41" v:groupContext="shape" transform="translate(588.646,-121.017)">
+ <title>Sheet.41</title>
+ <desc>0 0000 00</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="32.5687" cy="196.509" width="65.14" height="14.3829"/>
+ <path d="M65.14 189.32 L0 189.32 L0 203.7 L65.14 203.7 L65.14 189.32" class="st3"/>
+ <text x="5.91" y="200.1" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0 0000 00</text> </g>
+ <g id="shape42-113" v:mID="42" v:groupContext="shape" transform="translate(644.965,-121.017)">
+ <title>Sheet.42</title>
+ <desc>?</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="6.12511" cy="196.509" width="12.26" height="14.3829"/>
+ <path d="M12.25 189.32 L0 189.32 L0 203.7 L12.25 203.7 L12.25 189.32" class="st3"/>
+ <text x="2.47" y="200.1" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>?</text> </g>
+ <g id="shape43-117" v:mID="43" v:groupContext="shape" transform="translate(654.718,-121.017)">
+ <title>Sheet.43</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.7483" cy="196.509" width="11.5" height="14.3829"/>
+ <path d="M11.5 189.32 L0 189.32 L0 203.7 L11.5 203.7 L11.5 189.32" class="st3"/>
+ <text x="2.42" y="200.1" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape44-121" v:mID="44" v:groupContext="shape" transform="translate(464.786,-105.296)">
+ <title>Sheet.44</title>
+ <path d="M0 203.7 L108.29 203.7 C108.86 203.7 109.31 203.22 109.31 202.68 L109.31 189.5 L107.27 189.5 L107.27 202.68
+ L108.29 201.66 L0 201.66 L0 203.7 ZM111.35 190.52 L108.29 184.41 L105.23 190.55 L111.35 190.52 Z"
+ class="st11"/>
+ </g>
+ <g id="shape45-123" v:mID="45" v:groupContext="shape" transform="translate(464.786,-80.4315)">
+ <title>Sheet.45</title>
+ <path d="M0 203.7 L123.63 203.7 C124.2 203.7 124.65 203.25 124.65 202.68 L124.65 164.28 L122.61 164.28 L122.61 202.68
+ L123.63 201.66 L0 201.66 L0 203.7 ZM126.69 165.3 L123.6 159.18 L120.57 165.33 L126.69 165.3 Z"
+ class="st11"/>
+ </g>
+ <g id="shape46-125" v:mID="46" v:groupContext="shape" transform="translate(464.786,-55.4772)">
+ <title>Sheet.46</title>
+ <path d="M0 203.7 L186.48 203.7 C186.75 203.7 186.99 203.61 187.2 203.4 C187.38 203.22 187.5 202.95 187.5 202.68 L187.41
+ 139.32 L185.37 139.32 L185.46 202.68 L186.48 201.66 L0 201.66 L0 203.7 ZM189.51 140.07 L185.94 134.23 L183.41
+ 140.61 L189.51 140.07 Z" class="st11"/>
+ </g>
+ <g id="shape47-127" v:mID="47" v:groupContext="shape" transform="translate(464.786,-30.9125)">
+ <title>Sheet.47</title>
+ <path d="M0 203.7 L186.48 203.7 C186.75 203.7 186.99 203.61 187.2 203.4 C187.38 203.22 187.5 202.95 187.5 202.68 L187.41
+ 114.76 L185.37 114.76 L185.46 202.68 L186.48 201.66 L0 201.66 L0 203.7 ZM189.51 115.51 L185.94 109.67 L183.41
+ 116.05 L189.51 115.51 Z" class="st11"/>
+ </g>
+ <g id="shape48-129" v:mID="48" v:groupContext="shape" transform="translate(442.996,-151.106)">
+ <title>Sheet.48</title>
+ <path d="M0 179.56 C0 176.89 2.19 174.7 4.86 174.7 L70.8 174.7 C73.47 174.7 75.64 176.89 75.64 179.56 L75.64 198.88 C75.64
+ 201.54 73.47 203.7 70.8 203.7 L4.86 203.7 C2.19 203.7 0 201.54 0 198.88 L0 179.56 Z" class="st5"/>
+ </g>
+ <g id="shape49-131" v:mID="49" v:groupContext="shape" transform="translate(443.529,-155.018)">
+ <title>Sheet.49</title>
+ <desc>Values</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="37.8175" cy="192.914" width="75.64" height="21.5726"/>
+ <path d="M75.64 182.13 L0 182.13 L0 203.7 L75.64 203.7 L75.64 182.13" class="st3"/>
+ <text x="10.34" y="198.31" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Values</text> </g>
+ <g id="shape50-135" v:mID="50" v:groupContext="shape" transform="translate(102.458,-122.192)">
+ <title>Sheet.50</title>
+ <path d="M0 203.7 C-0 199.21 0.62 195.55 1.37 195.55 L11.67 195.55 C12.42 195.55 13.03 191.9 13.03 187.4 C13.03 191.9
+ 13.64 195.55 14.39 195.55 L24.69 195.55 C25.44 195.55 26.05 199.21 26.05 203.7" class="st13"/>
+ </g>
+ <g id="shape51-138" v:mID="51" v:groupContext="shape" transform="translate(115.454,-137.5)">
+ <title>Sheet.51</title>
+ <path d="M0.2 203.7 L322.66 174.12 L322.48 172.1 L0 201.68 L0.2 203.7 L0.2 203.7 ZM321.84 176.24 L327.66 172.64 L321.28
+ 170.16 L321.84 176.24 L321.84 176.24 Z" class="st14"/>
+ </g>
+ <g id="shape52-140" v:mID="52" v:groupContext="shape" transform="translate(518.211,-142.473)">
+ <title>Sheet.52</title>
+ <path d="M0.99 176.74 L44.78 200.38 L43.82 202.17 L0 178.51 L0.99 176.74 L0.99 176.74 ZM44.87 198.1 L48.8 203.7 L41.96
+ 203.46 L44.87 198.1 L44.87 198.1 Z" class="st11"/>
+ </g>
+ <g id="shape53-142" v:mID="53" v:groupContext="shape" transform="translate(518.331,-141.963)">
+ <title>Sheet.53</title>
+ <path d="M0.75 176.17 L60.09 200.32 L59.34 202.2 L0 178.06 L0.75 176.17 L0.75 176.17 ZM59.91 198.04 L64.44 203.19 L57.6
+ 203.7 L59.91 198.04 L59.91 198.04 Z" class="st11"/>
+ </g>
+ <g id="shape54-144" v:mID="54" v:groupContext="shape" transform="translate(576.558,-153.706)">
+ <title>Sheet.54</title>
+ <path d="M0 177.04 C0 174.1 2.4 171.71 5.34 171.71 L101.51 171.71 C104.48 171.71 106.85 174.1 106.85 177.04 L106.85 198.37
+ C106.85 201.33 104.48 203.7 101.51 203.7 L5.34 203.7 C2.4 203.7 0 201.33 0 198.37 L0 177.04 Z" class="st1"/>
+ </g>
+ <g id="shape55-146" v:mID="55" v:groupContext="shape" transform="translate(577.365,-151.611)">
+ <title>Sheet.55</title>
+ <path d="M0 177.04 C0 174.1 2.4 171.71 5.34 171.71 L101.51 171.71 C104.48 171.71 106.85 174.1 106.85 177.04 L106.85 198.37
+ C106.85 201.33 104.48 203.7 101.51 203.7 L5.34 203.7 C2.4 203.7 0 201.33 0 198.37 L0 177.04 Z" class="st2"/>
+ </g>
+ <g id="shape56-148" v:mID="56" v:groupContext="shape" transform="translate(593.952,-167.894)">
+ <title>Sheet.56</title>
+ <desc>Lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.2942" cy="196.509" width="86.59" height="14.3829"/>
+ <path d="M86.59 189.32 L0 189.32 L0 203.7 L86.59 203.7 L86.59 189.32" class="st3"/>
+ <text x="7.31" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Lookup_table</text> </g>
+ <g id="shape57-152" v:mID="57" v:groupContext="shape" transform="translate(608.239,-153.515)">
+ <title>Sheet.57</title>
+ <desc>(16 bits)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="26.8054" cy="196.509" width="53.62" height="14.3829"/>
+ <path d="M53.61 189.32 L0 189.32 L0 203.7 L53.61 203.7 L53.61 189.32" class="st3"/>
+ <text x="5.16" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(16 bits)</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i11.svg b/doc/guides/prog_guide/img/efd_i11.svg
new file mode 100644
index 0000000..f2cc656
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i11.svg
@@ -0,0 +1,319 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i12.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="10.2783in" height="4.28958in"
+ viewBox="0 0 740.039 308.85" xml:space="preserve" color-interpolation-filters="sRGB" class="st21">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st4 {fill:#000000;font-family:Arial;font-size:0.998566em}
+ .st5 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st6 {stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st7 {stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st8 {fill:#000000;font-family:Arial;font-size:0.918686em;font-weight:bold}
+ .st9 {fill:#00b050;font-size:1em}
+ .st10 {fill:#c00000;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st11 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.00749637}
+ .st12 {fill:#ffffff;font-family:Arial;font-size:1.16833em}
+ .st13 {fill:#2e75b5;stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st14 {fill:#ffffff;font-family:Arial;font-size:1.16666em}
+ .st15 {font-size:1em}
+ .st16 {fill:none;stroke:none;stroke-width:0.25}
+ .st17 {fill:#000000;font-family:Calibri;font-size:1.00001em}
+ .st18 {marker-end:url(#mrkr5-121);stroke:#5b9bd5;stroke-dasharray:1.5,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st19 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st20 {fill:#000000;font-family:Calibri;font-size:1.16666em}
+ .st21 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-121" class="st19" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape5-1" v:mID="5" v:groupContext="shape" transform="translate(36.0674,-256.878)">
+ <title>Sheet.5</title>
+ <path d="M0 291.88 C0 290 1.52 288.48 3.41 288.48 L68.51 288.48 C70.4 288.48 71.91 290 71.91 291.88 L71.91 305.46 C71.91
+ 307.33 70.4 308.85 68.51 308.85 L3.41 308.85 C1.52 308.85 0 307.33 0 305.46 L0 291.88 Z" class="st1"/>
+ </g>
+ <g id="shape6-3" v:mID="6" v:groupContext="shape" transform="translate(36.0674,-256.878)">
+ <title>Sheet.6</title>
+ <path d="M0 291.88 C0 290 1.52 288.48 3.41 288.48 L68.51 288.48 C70.4 288.48 71.91 290 71.91 291.88 L71.91 305.46 C71.91
+ 307.33 70.4 308.85 68.51 308.85 L3.41 308.85 C1.52 308.85 0 307.33 0 305.46 L0 291.88 Z" class="st2"/>
+ </g>
+ <g id="shape7-5" v:mID="7" v:groupContext="shape" transform="translate(61.6502,-258.089)">
+ <title>Sheet.7</title>
+ <desc>Key</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="13.7891" cy="301.658" width="27.58" height="14.3829"/>
+ <path d="M27.58 294.47 L0 294.47 L0 308.85 L27.58 308.85 L27.58 294.47" class="st3"/>
+ <text x="3.46" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key</text> </g>
+ <g id="shape8-9" v:mID="8" v:groupContext="shape" transform="translate(51.9748,-236.328)">
+ <title>Sheet.8</title>
+ <path d="M0 298.54 L9.81 298.54 L9.81 288.24 L29.44 288.24 L29.44 298.54 L39.26 298.54 L19.63 308.85 L0 298.54 Z"
+ class="st5"/>
+ </g>
+ <g id="shape9-11" v:mID="9" v:groupContext="shape" transform="translate(36.0674,-215.298)">
+ <title>Sheet.9</title>
+ <path d="M0 291.77 C0 289.89 1.54 288.36 3.42 288.36 L68.49 288.36 C70.38 288.36 71.91 289.89 71.91 291.77 L71.91 305.43
+ C71.91 307.32 70.38 308.85 68.49 308.85 L3.42 308.85 C1.54 308.85 0 307.32 0 305.43 L0 291.77 Z"
+ class="st1"/>
+ </g>
+ <g id="shape10-13" v:mID="10" v:groupContext="shape" transform="translate(36.0674,-215.298)">
+ <title>Sheet.10</title>
+ <path d="M0 291.77 C0 289.89 1.54 288.36 3.42 288.36 L68.49 288.36 C70.38 288.36 71.91 289.89 71.91 291.77 L71.91 305.43
+ C71.91 307.32 70.38 308.85 68.49 308.85 L3.42 308.85 C1.54 308.85 0 307.32 0 305.43 L0 291.77 Z"
+ class="st2"/>
+ </g>
+ <g id="shape11-15" v:mID="11" v:groupContext="shape" transform="translate(58.8889,-216.57)">
+ <title>Sheet.11</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.8573" cy="301.658" width="33.72" height="14.3829"/>
+ <path d="M33.71 294.47 L0 294.47 L0 308.85 L33.71 308.85 L33.71 294.47" class="st3"/>
+ <text x="3.86" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape12-19" v:mID="12" v:groupContext="shape" transform="translate(27.3033,-174.437)">
+ <title>Sheet.12</title>
+ <path d="M0 292.58 C0 290.78 1.46 289.32 3.26 289.32 L87.15 289.32 C88.95 289.32 90.4 290.78 90.4 292.58 L90.4 305.6
+ C90.4 307.4 88.95 308.85 87.15 308.85 L3.26 308.85 C1.46 308.85 0 307.4 0 305.6 L0 292.58 Z" class="st1"/>
+ </g>
+ <g id="shape13-21" v:mID="13" v:groupContext="shape" transform="translate(27.3033,-174.437)">
+ <title>Sheet.13</title>
+ <path d="M0 292.58 C0 290.78 1.46 289.32 3.26 289.32 L87.15 289.32 C88.95 289.32 90.4 290.78 90.4 292.58 L90.4 305.6
+ C90.4 307.4 88.95 308.85 87.15 308.85 L3.26 308.85 C1.46 308.85 0 307.4 0 305.6 L0 292.58 Z" class="st2"/>
+ </g>
+ <g id="shape14-23" v:mID="14" v:groupContext="shape" transform="translate(36.0515,-175.256)">
+ <title>Sheet.14</title>
+ <desc>0x0102ABCD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.6644" cy="301.658" width="87.33" height="14.3829"/>
+ <path d="M87.33 294.47 L0 294.47 L0 308.85 L87.33 308.85 L87.33 294.47" class="st3"/>
+ <text x="7.36" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102ABCD</text> </g>
+ <g id="shape15-27" v:mID="15" v:groupContext="shape" transform="translate(51.9748,-194.029)">
+ <title>Sheet.15</title>
+ <path d="M0 298.48 L9.81 298.48 L9.81 288.12 L29.44 288.12 L29.44 298.48 L39.26 298.48 L19.63 308.85 L0 298.48 Z"
+ class="st5"/>
+ </g>
+ <g id="shape16-29" v:mID="16" v:groupContext="shape" transform="translate(48.9133,-159.818)">
+ <title>Sheet.16</title>
+ <path d="M26.41 296.87 C26.41 300.18 25.97 302.86 25.41 302.86 L14.21 302.86 C13.66 302.86 13.21 305.55 13.21 308.85
+ C13.21 305.55 12.76 302.86 12.21 302.86 L1.01 302.86 C0.45 302.86 0 300.18 0 296.87" class="st6"/>
+ </g>
+ <g id="shape17-32" v:mID="17" v:groupContext="shape" transform="translate(19.0195,-19.0195)">
+ <title>Sheet.17</title>
+ <path d="M0 196.93 L0 308.85 L145.15 308.85 L145.15 196.93 L0 196.93 L0 196.93 Z" class="st1"/>
+ </g>
+ <g id="shape18-34" v:mID="18" v:groupContext="shape" transform="translate(19.0195,-19.0195)">
+ <title>Sheet.18</title>
+ <path d="M0 196.93 L145.15 196.93 L145.15 308.85 L0 308.85 L0 196.93" class="st7"/>
+ </g>
+ <g id="shape19-37" v:mID="19" v:groupContext="shape" transform="translate(28.2638,-70.6655)">
+ <title>Sheet.19</title>
+ <path d="M0 280.69 C0 277.58 2.53 275.06 5.64 275.06 L124.14 275.06 C127.26 275.06 129.78 277.58 129.78 280.69 L129.78
+ 303.22 C129.78 306.33 127.26 308.85 124.14 308.85 L5.64 308.85 C2.53 308.85 0 306.33 0 303.22 L0 280.69
+ Z" class="st1"/>
+ </g>
+ <g id="shape20-39" v:mID="20" v:groupContext="shape" transform="translate(28.2638,-70.6655)">
+ <title>Sheet.20</title>
+ <path d="M0 280.69 C0 277.58 2.53 275.06 5.64 275.06 L124.14 275.06 C127.26 275.06 129.78 277.58 129.78 280.69 L129.78
+ 303.22 C129.78 306.33 127.26 308.85 124.14 308.85 L5.64 308.85 C2.53 308.85 0 306.33 0 303.22 L0 280.69
+ Z" class="st2"/>
+ </g>
+ <g id="shape21-41" v:mID="21" v:groupContext="shape" transform="translate(57.4514,-85.7513)">
+ <title>Sheet.21</title>
+ <desc>hash_index =</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="45.0133" cy="301.658" width="90.03" height="14.3829"/>
+ <path d="M90.03 294.47 L0 294.47 L0 308.85 L90.03 308.85 L90.03 294.47" class="st3"/>
+ <text x="9.2" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash_index = </text> </g>
+ <g id="shape22-45" v:mID="22" v:groupContext="shape" transform="translate(76.3001,-71.3719)">
+ <title>Sheet.22</title>
+ <desc>38123</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.0762" cy="301.658" width="42.16" height="14.3829"/>
+ <path d="M42.15 294.47 L0 294.47 L0 308.85 L42.15 308.85 L42.15 294.47" class="st3"/>
+ <text x="4.42" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>38123</text> </g>
+ <g id="shape23-49" v:mID="23" v:groupContext="shape" transform="translate(28.2638,-27.048)">
+ <title>Sheet.23</title>
+ <path d="M0 280.69 C0 277.59 2.54 275.06 5.64 275.06 L124.14 275.06 C127.26 275.06 129.78 277.59 129.78 280.69 L129.78
+ 303.22 C129.78 306.33 127.26 308.85 124.14 308.85 L5.64 308.85 C2.54 308.85 0 306.33 0 303.22 L0 280.69
+ Z" class="st1"/>
+ </g>
+ <g id="shape24-51" v:mID="24" v:groupContext="shape" transform="translate(28.2638,-27.048)">
+ <title>Sheet.24</title>
+ <path d="M0 280.69 C0 277.59 2.54 275.06 5.64 275.06 L124.14 275.06 C127.26 275.06 129.78 277.59 129.78 280.69 L129.78
+ 303.22 C129.78 306.33 127.26 308.85 124.14 308.85 L5.64 308.85 C2.54 308.85 0 306.33 0 303.22 L0 280.69
+ Z" class="st2"/>
+ </g>
+ <g id="shape25-53" v:mID="25" v:groupContext="shape" transform="translate(54.0924,-41.564)">
+ <title>Sheet.25</title>
+ <desc>lookup_table =</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="46.931" cy="301.658" width="93.87" height="14.3829"/>
+ <path d="M93.86 294.47 L0 294.47 L0 308.85 L93.86 308.85 L93.86 294.47" class="st3"/>
+ <text x="7.79" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table =</text> </g>
+ <g id="shape26-57" v:mID="26" v:groupContext="shape" transform="translate(28.0195,-28.5506)">
+ <title>Sheet.26</title>
+ <desc>0110 1100 0101 1101</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="64.89" cy="302.233" width="129.79" height="13.2327"/>
+ <path d="M129.78 295.62 L0 295.62 L0 308.85 L129.78 308.85 L129.78 295.62" class="st3"/>
+ <text x="11.25" y="305.54" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0110 11<tspan
+ class="st9">0</tspan>0 0101 1101</text> </g>
+ <g id="shape27-62" v:mID="27" v:groupContext="shape" transform="translate(26.2461,-113.863)">
+ <title>Sheet.27</title>
+ <desc>Group ID: 0x0102</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="48.6286" cy="302.881" width="97.26" height="11.9384"/>
+ <path d="M97.26 296.91 L0 296.91 L0 308.85 L97.26 308.85 L97.26 296.91" class="st3"/>
+ <text x="7.73" y="305.86" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group ID: 0x0102</text> </g>
+ <g id="shape28-66" v:mID="28" v:groupContext="shape" transform="translate(42.3703,-135.313)">
+ <title>Sheet.28</title>
+ <path d="M0 298.48 L9.84 298.48 L9.84 288.12 L29.53 288.12 L29.53 298.48 L39.38 298.48 L19.69 308.85 L0 298.48 Z"
+ class="st5"/>
+ </g>
+ <g id="shape29-68" v:mID="29" v:groupContext="shape" transform="translate(117.645,-244.476)">
+ <title>Sheet.29</title>
+ <path d="M0 274.07 L22.75 274.07 L22.75 262.48 L45.5 285.66 L22.75 308.85 L22.75 297.26 L0 297.26 L0 274.07 Z"
+ class="st5"/>
+ </g>
+ <g id="shape30-70" v:mID="30" v:groupContext="shape" transform="translate(169.209,-251.966)">
+ <title>Sheet.30</title>
+ <path d="M0 283.69 C0 280.91 2.27 278.65 5.04 278.65 L111.77 278.65 C114.56 278.65 116.81 280.91 116.81 283.69 L116.81
+ 303.82 C116.81 306.6 114.56 308.85 111.77 308.85 L5.04 308.85 C2.27 308.85 0 306.6 0 303.82 L0 283.69 Z"
+ class="st1"/>
+ </g>
+ <g id="shape31-72" v:mID="31" v:groupContext="shape" transform="translate(169.209,-251.966)">
+ <title>Sheet.31</title>
+ <path d="M0 283.69 C0 280.91 2.27 278.65 5.04 278.65 L111.77 278.65 C114.56 278.65 116.81 280.91 116.81 283.69 L116.81
+ 303.82 C116.81 306.6 114.56 308.85 111.77 308.85 L5.04 308.85 C2.27 308.85 0 306.6 0 303.82 L0 283.69 Z"
+ class="st2"/>
+ </g>
+ <g id="shape35-74" v:mID="35" v:groupContext="shape" transform="translate(291.966,-244.476)">
+ <title>Sheet.35</title>
+ <path d="M0 274.07 L22.69 274.07 L22.69 262.48 L45.38 285.66 L22.69 308.85 L22.69 297.26 L0 297.26 L0 274.07 Z"
+ class="st5"/>
+ </g>
+ <g id="shape36-76" v:mID="36" v:groupContext="shape" transform="translate(343.17,-254.482)">
+ <title>Sheet.36</title>
+ <path d="M0 288.09 C0 285.8 1.88 283.93 4.17 283.93 L109.18 283.93 C111.47 283.93 113.33 285.8 113.33 288.09 L113.33
+ 304.7 C113.33 306.99 111.47 308.85 109.18 308.85 L4.17 308.85 C1.88 308.85 0 306.99 0 304.7 L0 288.09 Z"
+ class="st1"/>
+ </g>
+ <g id="shape37-78" v:mID="37" v:groupContext="shape" transform="translate(343.17,-254.482)">
+ <title>Sheet.37</title>
+ <path d="M0 288.09 C0 285.8 1.88 283.93 4.17 283.93 L109.18 283.93 C111.47 283.93 113.33 285.8 113.33 288.09 L113.33
+ 304.7 C113.33 306.99 111.47 308.85 109.18 308.85 L4.17 308.85 C1.88 308.85 0 306.99 0 304.7 L0 288.09 Z"
+ class="st2"/>
+ </g>
+ <g id="shape38-80" v:mID="38" v:groupContext="shape" transform="translate(368.337,-257.958)">
+ <title>Sheet.38</title>
+ <desc>Position = 6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="38.1131" cy="301.658" width="76.23" height="14.3829"/>
+ <path d="M76.23 294.47 L0 294.47 L0 308.85 L76.23 308.85 L76.23 294.47" class="st3"/>
+ <text x="6.64" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Position = 6</text> </g>
+ <g id="shape39-84" v:mID="39" v:groupContext="shape" transform="translate(158.044,-86.5202)">
+ <title>Sheet.39</title>
+ <path d="M0 308.85 L69.59 308.85 C70.16 308.85 70.62 308.39 70.62 307.83 L70.62 148.5 L68.57 148.5 L68.57 307.83 L69.59
+ 306.81 L0 306.81 L0 308.85 ZM72.66 149.52 L69.59 143.4 L66.53 149.52 L72.66 149.52 Z" class="st11"/>
+ </g>
+ <g id="shape41-86" v:mID="41" v:groupContext="shape" transform="translate(335.112,-199.647)">
+ <title>Sheet.41</title>
+ <desc>Apply the equation</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="71.2648" cy="300.436" width="142.53" height="16.8275"/>
+ <path d="M142.53 292.02 L0 292.02 L0 308.85 L142.53 308.85 L142.53 292.02" class="st3"/>
+ <text x="13.19" y="304.64" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Apply the equation </text> </g>
+ <g id="shape42-90" v:mID="42" v:groupContext="shape" transform="translate(341.115,-182.871)">
+ <title>Sheet.42</title>
+ <desc>to retrieve the bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="64.5256" cy="300.436" width="129.06" height="16.8275"/>
+ <path d="M129.05 292.02 L0 292.02 L0 308.85 L129.05 308.85 L129.05 292.02" class="st3"/>
+ <text x="12.31" y="304.64" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>to retrieve the bit </text> </g>
+ <g id="shape43-94" v:mID="43" v:groupContext="shape" transform="translate(349.999,-166.095)">
+ <title>Sheet.43</title>
+ <desc>position in the</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="54.2285" cy="300.436" width="108.46" height="16.8275"/>
+ <path d="M108.46 292.02 L0 292.02 L0 308.85 L108.46 308.85 L108.46 292.02" class="st3"/>
+ <text x="10.97" y="304.64" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>position in the </text> </g>
+ <g id="shape44-98" v:mID="44" v:groupContext="shape" transform="translate(353.361,-149.319)">
+ <title>Sheet.44</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="47.9619" cy="300.436" width="95.93" height="16.8275"/>
+ <path d="M95.92 292.02 L0 292.02 L0 308.85 L95.92 308.85 L95.92 292.02" class="st3"/>
+ <text x="8.21" y="304.64" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape47-102" v:mID="47" v:groupContext="shape" transform="translate(115.17,255.2) rotate(-90)">
+ <title>1-D word balloon</title>
+ <desc>Retrieve the value “0' from the specified location in the loo...</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ <v:ud v:nameU="Scale" v:val="VT0(1):26"/>
+ <v:ud v:nameU="AntiScale" v:val="VT0(1):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="29.2016" cy="218.85" width="180" height="58.4032" transform="rotate(90)"/>
+ <path d="M0 308.85 L58.4 308.85 L58.4 128.85 L0 128.85 L0 204.67 L-11.87 38.85 L-7.09 233.03 L0 233.03 L0 308.85 Z"
+ class="st13"/>
+ <text x="136.98" y="-41.8" transform="rotate(90)" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Retrieve the value “0' from <tspan
+ x="134.41" dy="1.2em" class="st15">the specified location in the </tspan><tspan x="181.1" dy="1.2em"
+ class="st15">lookup table</tspan></text> </g>
+ <g id="shape48-107" v:mID="48" v:groupContext="shape" transform="translate(169.209,-251.966)">
+ <title>Sheet.48</title>
+ <desc>F(Key, hash_index = 38123</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54.2285" cy="295.35" width="108.46" height="27"/>
+ <rect x="0" y="281.85" width="108.457" height="27" class="st16"/>
+ <text x="5.86" y="291.75" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>F(Key, hash_index = <tspan
+ x="39.02" dy="1.2em" class="st15">38123</tspan></text> </g>
+ <g id="shape49-111" v:mID="49" v:groupContext="shape" transform="translate(553.962,99) rotate(90)">
+ <title>1-D word balloon.49</title>
+ <desc>Apply the equation to retrieve the bit position in the lookup...</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ <v:ud v:nameU="Scale" v:val="VT0(1):26"/>
+ <v:ud v:nameU="AntiScale" v:val="VT0(1):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="29.2016" cy="218.85" width="180" height="58.4032" transform="rotate(-90)"/>
+ <path d="M0 308.85 L58.4 308.85 L58.4 128.85 L0 128.85 L0 204.67 L-51.13 299.85 L0 233.03 L0 308.85 Z" class="st13"/>
+ <text x="-284.62" y="16.6" transform="rotate(-90)" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Apply the equation to <tspan
+ x="-296.67" dy="1.2em" class="st15">retrieve the bit position in </tspan><tspan x="-270.22" dy="1.2em"
+ class="st15">the lookup</tspan>_table</text> </g>
+ <g id="shape50-116" v:mID="50" v:groupContext="shape" transform="translate(640.132,-104.709) rotate(44.1224)">
+ <title>Sheet.50</title>
+ <path d="M0 308.85 L54.13 308.85" class="st18"/>
+ </g>
+ <g id="shape51-122" v:mID="51" v:groupContext="shape" transform="translate(433.02,-122.267)">
+ <title>Sheet.51</title>
+ <desc>(Hash(key,seed1)+38123*hash(key,seed2))%16</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="144" cy="295.35" width="288" height="27"/>
+ <rect x="0" y="281.85" width="288" height="27" class="st2"/>
+ <text x="9.86" y="299.55" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(Hash(key,seed1)+38123*hash(key,seed2))%16</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i12.svg b/doc/guides/prog_guide/img/efd_i12.svg
new file mode 100644
index 0000000..a309d58
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i12.svg
@@ -0,0 +1,1008 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i13.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="10.2932in" height="5.27505in"
+ viewBox="0 0 741.108 379.804" xml:space="preserve" color-interpolation-filters="sRGB" class="st30">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st2 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st3 {fill:#004280;font-family:Arial;font-size:0.828804em}
+ .st4 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st5 {stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st6 {fill:#7030a0;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st7 {fill:#d0d6d9;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st8 {fill:#006fc5;stroke:#006fc5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.00749637}
+ .st9 {fill:#006fc5;stroke:#006fc5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st10 {fill:#d0d6d9;stroke:#d0d6d9;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st11 {fill:#d0d6d9;stroke:#d0d6d9;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st12 {fill:#004280;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st13 {fill:#00b050;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st14 {fill:#ff0000;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st15 {fill:#00b050;stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st16 {fill:#c00000;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st17 {fill:#000000;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st18 {fill:#7f6d00;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st19 {fill:#ff0000;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st20 {fill:#7e8d96;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st21 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st22 {fill:#000000;font-family:Arial;font-size:0.998566em}
+ .st23 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st24 {stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st25 {fill:#ffffff;font-family:Arial;font-size:0.998566em}
+ .st26 {fill:#ff6600;stroke:#ff6600;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.00749637}
+ .st27 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st28 {fill:#ff0000;stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st29 {fill:#ffffff;font-family:Arial;font-size:1.49785em}
+ .st30 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(304.703,-329.32)">
+ <title>Sheet.3</title>
+ <path d="M0 379.8 C-0 375.37 0.6 371.78 1.35 371.78 L205.05 371.78 C205.78 371.78 206.38 368.18 206.38 363.75 C206.38
+ 368.18 206.98 371.78 207.73 371.78 L411.43 371.78 C412.15 371.78 412.75 375.37 412.75 379.8" class="st1"/>
+ </g>
+ <g id="shape4-4" v:mID="4" v:groupContext="shape" transform="translate(219.943,-329.32)">
+ <title>Sheet.4</title>
+ <path d="M0 379.8 C0 375.64 0.57 372.25 1.26 372.25 L29.77 372.25 C30.48 372.25 31.03 368.88 31.03 364.71 C31.03 368.88
+ 31.6 372.25 32.29 372.25 L60.81 372.25 C61.51 372.25 62.07 375.64 62.07 379.8" class="st1"/>
+ </g>
+ <g id="shape5-7" v:mID="5" v:groupContext="shape" transform="translate(241.175,-343.9)">
+ <title>Sheet.5</title>
+ <desc>Bins</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="12.7158" cy="373.835" width="25.44" height="11.9384"/>
+ <path d="M25.43 367.87 L0 367.87 L0 379.8 L25.43 379.8 L25.43 367.87" class="st2"/>
+ <text x="3.04" y="376.82" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Bins</text> </g>
+ <g id="shape6-11" v:mID="6" v:groupContext="shape" transform="translate(496.212,-344.504)">
+ <title>Sheet.6</title>
+ <desc>Groups</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="20.3447" cy="373.835" width="40.69" height="11.9384"/>
+ <path d="M40.69 367.87 L0 367.87 L0 379.8 L40.69 379.8 L40.69 367.87" class="st2"/>
+ <text x="4.04" y="376.82" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Groups</text> </g>
+ <g id="shape7-15" v:mID="7" v:groupContext="shape" transform="translate(131.823,-260.299)">
+ <title>Sheet.7</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape8-17" v:mID="8" v:groupContext="shape" transform="translate(131.823,-260.299)">
+ <title>Sheet.8</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape9-20" v:mID="9" v:groupContext="shape" transform="translate(134.706,-310.738)">
+ <title>Sheet.9</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape10-24" v:mID="10" v:groupContext="shape" transform="translate(122.218,-329.32)">
+ <title>Sheet.10</title>
+ <path d="M0 379.8 C-0 375.64 0.57 372.25 1.26 372.25 L29.77 372.25 C30.47 372.25 31.03 368.88 31.03 364.71 C31.03 368.88
+ 31.6 372.25 32.29 372.25 L60.81 372.25 C61.51 372.25 62.07 375.64 62.07 379.8" class="st1"/>
+ </g>
+ <g id="shape11-27" v:mID="11" v:groupContext="shape" transform="translate(137.598,-343.9)">
+ <title>Sheet.11</title>
+ <desc>Chunks</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="20.9813" cy="373.835" width="41.97" height="11.9384"/>
+ <path d="M41.96 367.87 L0 367.87 L0 379.8 L41.96 379.8 L41.96 367.87" class="st2"/>
+ <text x="4.12" y="376.82" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Chunks</text> </g>
+ <g id="shape12-31" v:mID="12" v:groupContext="shape" transform="translate(131.823,-195.232)">
+ <title>Sheet.12</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape13-33" v:mID="13" v:groupContext="shape" transform="translate(131.823,-195.232)">
+ <title>Sheet.13</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape14-36" v:mID="14" v:groupContext="shape" transform="translate(134.706,-245.682)">
+ <title>Sheet.14</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape15-40" v:mID="15" v:groupContext="shape" transform="translate(131.823,-130.525)">
+ <title>Sheet.15</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st7"/>
+ </g>
+ <g id="shape16-42" v:mID="16" v:groupContext="shape" transform="translate(131.823,-130.525)">
+ <title>Sheet.16</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape17-45" v:mID="17" v:groupContext="shape" transform="translate(134.706,-180.952)">
+ <title>Sheet.17</title>
+ <desc>…</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.30974" cy="373.835" width="14.62" height="11.9384"/>
+ <path d="M14.62 367.87 L0 367.87 L0 379.8 L14.62 379.8 L14.62 367.87" class="st2"/>
+ <text x="2.34" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>…</text> </g>
+ <g id="shape18-49" v:mID="18" v:groupContext="shape" transform="translate(131.823,-65.4584)">
+ <title>Sheet.18</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape19-51" v:mID="19" v:groupContext="shape" transform="translate(131.823,-65.4584)">
+ <title>Sheet.19</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape20-54" v:mID="20" v:groupContext="shape" transform="translate(130.403,-115.896)">
+ <title>Sheet.20</title>
+ <desc>variable</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="24.7986" cy="373.835" width="49.6" height="11.9384"/>
+ <path d="M49.6 367.87 L0 367.87 L0 379.8 L49.6 379.8 L49.6 367.87" class="st2"/>
+ <text x="6" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>variable </text> </g>
+ <g id="shape21-58" v:mID="21" v:groupContext="shape" transform="translate(130.403,-103.913)">
+ <title>Sheet.21</title>
+ <desc># of</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="13.347" cy="373.835" width="26.7" height="11.9384"/>
+ <path d="M26.69 367.87 L0 367.87 L0 379.8 L26.69 379.8 L26.69 367.87" class="st2"/>
+ <text x="4.51" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/># of </text> </g>
+ <g id="shape22-62" v:mID="22" v:groupContext="shape" transform="translate(130.403,-91.93)">
+ <title>Sheet.22</title>
+ <desc>chunks</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.6122" cy="373.835" width="43.23" height="11.9384"/>
+ <path d="M43.22 367.87 L0 367.87 L0 379.8 L43.22 379.8 L43.22 367.87" class="st2"/>
+ <text x="4.2" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>chunks</text> </g>
+ <g id="shape23-66" v:mID="23" v:groupContext="shape" transform="translate(130.403,-79.9472)">
+ <title>Sheet.23</title>
+ <desc>(power</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.9251" cy="373.835" width="43.86" height="11.9384"/>
+ <path d="M43.85 367.87 L0 367.87 L0 379.8 L43.85 379.8 L43.85 367.87" class="st2"/>
+ <text x="5.62" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(power </text> </g>
+ <g id="shape24-70" v:mID="24" v:groupContext="shape" transform="translate(130.403,-67.9643)">
+ <title>Sheet.24</title>
+ <desc>of 2)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="13.6626" cy="373.835" width="27.33" height="11.9384"/>
+ <path d="M27.33 367.87 L0 367.87 L0 379.8 L27.33 379.8 L27.33 367.87" class="st2"/>
+ <text x="3.17" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>of 2)</text> </g>
+ <g id="shape25-74" v:mID="25" v:groupContext="shape" transform="translate(172.289,-260.838)">
+ <title>Sheet.25</title>
+ <path d="M1.43 379.8 L3.29 375.51 L1.85 374.9 L0 379.19 L1.43 379.8 L1.43 379.8 ZM3.9 374.08 L5.76 369.79 L4.32 369.18
+ L2.47 373.47 L3.9 374.08 L3.9 374.08 ZM6.37 368.36 L8.22 364.07 L6.79 363.45 L4.94 367.75 L6.37 368.36 L6.37
+ 368.36 ZM8.84 362.64 L10.69 358.35 L9.26 357.73 L7.41 362.02 L8.84 362.64 L8.84 362.64 ZM11.31 356.92 L13.16
+ 352.62 L11.73 352 L9.87 356.3 L11.31 356.92 L11.31 356.92 ZM13.78 351.19 L15.63 346.9 L14.2 346.28 L12.34
+ 350.57 L13.78 351.19 L13.78 351.19 ZM16.25 345.47 L18.1 341.17 L16.67 340.56 L14.81 344.85 L16.25 345.47
+ L16.25 345.47 ZM18.71 339.74 L20.57 335.45 L19.13 334.84 L17.28 339.13 L18.71 339.74 L18.71 339.74 ZM21.18
+ 334.02 L23.04 329.73 L21.6 329.12 L19.75 333.41 L21.18 334.02 L21.18 334.02 ZM23.65 328.3 L25.5 324.01 L24.07
+ 323.39 L22.22 327.68 L23.65 328.3 L23.65 328.3 ZM26.12 322.58 L27.97 318.28 L26.54 317.67 L24.69 321.96
+ L26.12 322.58 L26.12 322.58 ZM28.59 316.85 L29.44 314.87 L28.01 314.25 L27.16 316.24 L28.59 316.85 L28.59
+ 316.85 Z" class="st8"/>
+ </g>
+ <g id="shape26-76" v:mID="26" v:groupContext="shape" transform="translate(172.476,-20.463)">
+ <title>Sheet.26</title>
+ <path d="M1.55 203.84 L2.28 208.45 L0.74 208.7 L0 204.09 L1.55 203.84 L1.55 203.84 ZM2.52 209.99 L3.27 214.61 L1.73 214.86
+ L0.99 210.23 L2.52 209.99 L2.52 209.99 ZM3.51 216.15 L4.25 220.76 L2.7 221 L1.97 216.39 L3.51 216.15 L3.51
+ 216.15 ZM4.49 222.3 L5.24 226.92 L3.69 227.16 L2.96 222.54 L4.49 222.3 L4.49 222.3 ZM5.48 228.45 L6.21 233.07
+ L4.67 233.31 L3.93 228.7 L5.48 228.45 L5.48 228.45 ZM6.47 234.6 L7.2 239.22 L5.66 239.47 L4.92 234.86 L6.47
+ 234.6 L6.47 234.6 ZM7.44 240.76 L8.18 245.37 L6.65 245.63 L5.9 241 L7.44 240.76 L7.44 240.76 ZM8.43 246.91
+ L9.17 251.53 L7.62 251.77 L6.89 247.15 L8.43 246.91 L8.43 246.91 ZM9.41 253.07 L10.14 257.68 L8.61 257.92
+ L7.88 253.31 L9.41 253.07 L9.41 253.07 ZM10.4 259.21 L11.14 263.84 L9.59 264.08 L8.85 259.47 L10.4 259.21
+ L10.4 259.21 ZM11.38 265.37 L12.13 269.98 L10.58 270.24 L9.84 265.62 L11.38 265.37 L11.38 265.37 ZM12.37
+ 271.52 L13.1 276.14 L11.56 276.39 L10.82 271.76 L12.37 271.52 L12.37 271.52 ZM13.34 277.68 L14.09 282.29
+ L12.55 282.53 L11.81 277.92 L13.34 277.68 L13.34 277.68 ZM14.33 283.84 L15.07 288.45 L13.52 288.69 L12.79
+ 284.08 L14.33 283.84 L14.33 283.84 ZM15.32 289.99 L16.06 294.61 L14.51 294.85 L13.78 290.23 L15.32 289.99
+ L15.32 289.99 ZM16.3 296.13 L17.03 300.75 L15.5 301 L14.75 296.39 L16.3 296.13 L16.3 296.13 ZM17.29 302.29
+ L18.02 306.9 L16.48 307.16 L15.74 302.53 L17.29 302.29 L17.29 302.29 ZM18.26 308.45 L19 313.06 L17.47 313.3
+ L16.73 308.69 L18.26 308.45 L18.26 308.45 ZM19.25 314.6 L19.99 319.22 L18.44 319.46 L17.71 314.84 L19.25
+ 314.6 L19.25 314.6 ZM20.23 320.76 L20.96 325.37 L19.43 325.61 L18.7 321 L20.23 320.76 L20.23 320.76 ZM21.22
+ 326.9 L21.96 331.51 L20.41 331.77 L19.67 327.15 L21.22 326.9 L21.22 326.9 ZM22.2 333.06 L22.95 337.67 L21.4
+ 337.92 L20.66 333.31 L22.2 333.06 L22.2 333.06 ZM23.19 339.21 L23.92 343.83 L22.38 344.07 L21.64 339.45
+ L23.19 339.21 L23.19 339.21 ZM24.18 345.37 L24.91 349.98 L23.37 350.22 L22.63 345.61 L24.18 345.37 L24.18
+ 345.37 ZM25.15 351.52 L25.89 356.14 L24.36 356.38 L23.61 351.76 L25.15 351.52 L25.15 351.52 ZM26.14 357.67
+ L26.88 362.28 L25.33 362.53 L24.6 357.92 L26.14 357.67 L26.14 357.67 ZM27.12 363.82 L27.85 368.44 L26.32
+ 368.69 L25.59 364.08 L27.12 363.82 L27.12 363.82 ZM28.11 369.98 L28.84 374.59 L27.3 374.83 L26.56 370.22
+ L28.11 369.98 L28.11 369.98 ZM29.08 376.13 L29.64 379.55 L28.09 379.8 L27.55 376.37 L29.08 376.13 L29.08
+ 376.13 Z" class="st9"/>
+ </g>
+ <g id="shape27-78" v:mID="27" v:groupContext="shape" transform="translate(276.159,-233.368)">
+ <title>Sheet.27</title>
+ <path d="M0.45 294.85 L354.04 376.06 L353.59 378.04 L0 296.85 L0.45 294.85 L0.45 294.85 ZM353.5 373.84 L358.79 378.19
+ L352.12 379.8 L353.5 373.84 L353.5 373.84 Z" class="st10"/>
+ </g>
+ <g id="shape28-80" v:mID="28" v:groupContext="shape" transform="translate(275.859,-178.726)">
+ <title>Sheet.28</title>
+ <path d="M1.05 240.32 L231.44 376.33 L230.39 378.1 L0 242.09 L1.05 240.32 L1.05 240.32 ZM231.59 374.05 L235.31 379.8
+ L228.47 379.32 L231.59 374.05 L231.59 374.05 Z" class="st10"/>
+ </g>
+ <g id="shape29-82" v:mID="29" v:groupContext="shape" transform="translate(275.379,-87.6866)">
+ <title>Sheet.29</title>
+ <path d="M2 149.94 L50.05 374.61 L48.05 375.04 L0 150.38 L2 149.94 L2 149.94 ZM51.83 373.18 L50.12 379.8 L45.85 374.47
+ L51.83 373.18 L51.83 373.18 Z" class="st11"/>
+ </g>
+ <g id="shape30-84" v:mID="30" v:groupContext="shape" transform="translate(276.279,-177.108)">
+ <title>Sheet.30</title>
+ <path d="M0.21 353.74 L229.55 375.85 L229.34 377.89 L0 355.75 L0.21 353.74 L0.21 353.74 ZM228.71 373.72 L234.53 377.35
+ L228.14 379.8 L228.71 373.72 L228.71 373.72 Z" class="st10"/>
+ </g>
+ <g id="shape31-86" v:mID="31" v:groupContext="shape" transform="translate(275.919,-213.926)">
+ <title>Sheet.31</title>
+ <path d="M0.45 308.72 L312.65 376.06 L312.2 378.04 L0 310.72 L0.45 308.72 L0.45 308.72 ZM312.08 373.84 L317.43 378.13
+ L310.79 379.8 L312.08 373.84 L312.08 373.84 Z" class="st10"/>
+ </g>
+ <g id="shape32-88" v:mID="32" v:groupContext="shape" transform="translate(275.439,-143.377)">
+ <title>Sheet.32</title>
+ <path d="M1.4 238.41 L150.34 375.59 L148.96 377.09 L0 239.9 L1.4 238.41 L1.4 238.41 ZM150.98 373.41 L153.4 379.8 L146.83
+ 377.9 L150.98 373.41 L150.98 373.41 Z" class="st11"/>
+ </g>
+ <g id="shape33-90" v:mID="33" v:groupContext="shape" transform="translate(275.274,-108.821)">
+ <title>Sheet.33</title>
+ <path d="M1.73 236.53 L90.79 374.97 L89.08 376.07 L0 237.63 L1.73 236.53 L1.73 236.53 ZM91.96 373 L92.7 379.8 L86.82
+ 376.31 L91.96 373 L91.96 373 Z" class="st11"/>
+ </g>
+ <g id="shape34-92" v:mID="34" v:groupContext="shape" transform="translate(275.364,-124.069)">
+ <title>Sheet.34</title>
+ <path d="M1.55 251.66 L108.22 375.28 L106.67 376.61 L0 253 L1.55 251.66 L1.55 251.66 ZM109.1 373.18 L110.78 379.8 L104.46
+ 377.17 L109.1 373.18 L109.1 373.18 Z" class="st11"/>
+ </g>
+ <g id="shape35-94" v:mID="35" v:groupContext="shape" transform="translate(275.154,-87.7165)">
+ <title>Sheet.35</title>
+ <path d="M1.97 215.68 L49.85 374.64 L47.9 375.22 L0 216.27 L1.97 215.68 L1.97 215.68 ZM51.52 373.08 L50.35 379.8 L45.65
+ 374.83 L51.52 373.08 L51.52 373.08 Z" class="st11"/>
+ </g>
+ <g id="shape36-96" v:mID="36" v:groupContext="shape" transform="translate(276.009,-143.736)">
+ <title>Sheet.36</title>
+ <path d="M0.74 320.41 L147.92 376.36 L147.2 378.26 L0 322.32 L0.74 320.41 L0.74 320.41 ZM147.7 374.08 L152.34 379.11
+ L145.52 379.8 L147.7 374.08 L147.7 374.08 Z" class="st11"/>
+ </g>
+ <g id="shape37-98" v:mID="37" v:groupContext="shape" transform="translate(275.649,-108.821)">
+ <title>Sheet.37</title>
+ <path d="M1.46 285.74 L89.46 375.45 L88 376.87 L0 287.16 L1.46 285.74 L1.46 285.74 ZM90.21 373.29 L92.29 379.8 L85.82
+ 377.57 L90.21 373.29 L90.21 373.29 Z" class="st11"/>
+ </g>
+ <g id="shape38-100" v:mID="38" v:groupContext="shape" transform="translate(275.934,-108.686)">
+ <title>Sheet.38</title>
+ <path d="M0.89 335.24 L87.85 376.57 L86.97 378.41 L0 337.09 L0.89 335.24 L0.89 335.24 ZM87.81 374.29 L92.01 379.67 L85.16
+ 379.8 L87.81 374.29 L87.81 374.29 Z" class="st11"/>
+ </g>
+ <g id="shape39-102" v:mID="39" v:groupContext="shape" transform="translate(275.574,-89.454)">
+ <title>Sheet.39</title>
+ <path d="M1.61 316.29 L48.49 375.18 L46.88 376.45 L0 317.57 L1.61 316.29 L1.61 316.29 ZM49.45 373.11 L50.86 379.8 L44.65
+ 376.91 L49.45 373.11 L49.45 373.11 Z" class="st11"/>
+ </g>
+ <g id="shape40-104" v:mID="40" v:groupContext="shape" transform="translate(276.324,-141.744)">
+ <title>Sheet.40</title>
+ <path d="M0.11 368.21 L146.74 375.79 L146.62 377.83 L0 370.23 L0.11 368.21 L0.11 368.21 ZM145.82 373.71 L151.78 377.08
+ L145.51 379.8 L145.82 373.71 L145.82 373.71 Z" class="st11"/>
+ </g>
+ <g id="shape41-106" v:mID="41" v:groupContext="shape" transform="translate(230.508,-309.069)">
+ <title>Sheet.41</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st4"/>
+ </g>
+ <g id="shape42-108" v:mID="42" v:groupContext="shape" transform="translate(230.508,-309.069)">
+ <title>Sheet.42</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape43-111" v:mID="43" v:groupContext="shape" transform="translate(233.39,-309.868)">
+ <title>Sheet.43</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape44-115" v:mID="44" v:groupContext="shape" transform="translate(263.764,-309.869)">
+ <title>Sheet.44</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape45-119" v:mID="45" v:groupContext="shape" transform="translate(230.508,-292.413)">
+ <title>Sheet.45</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape46-121" v:mID="46" v:groupContext="shape" transform="translate(230.508,-292.413)">
+ <title>Sheet.46</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape47-124" v:mID="47" v:groupContext="shape" transform="translate(233.39,-293.221)">
+ <title>Sheet.47</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape48-128" v:mID="48" v:groupContext="shape" transform="translate(230.508,-275.757)">
+ <title>Sheet.48</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st4"/>
+ </g>
+ <g id="shape49-130" v:mID="49" v:groupContext="shape" transform="translate(230.508,-275.757)">
+ <title>Sheet.49</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape50-133" v:mID="50" v:groupContext="shape" transform="translate(233.39,-276.574)">
+ <title>Sheet.50</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape51-137" v:mID="51" v:groupContext="shape" transform="translate(252.478,-276.574)">
+ <title>Sheet.51</title>
+ <desc>3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>3</text> </g>
+ <g id="shape52-141" v:mID="52" v:groupContext="shape" transform="translate(258.001,-276.574)">
+ <title>Sheet.52</title>
+ <desc>+1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="8.11122" cy="373.835" width="16.23" height="11.9384"/>
+ <path d="M16.22 367.87 L0 367.87 L0 379.8 L16.22 379.8 L16.22 367.87" class="st2"/>
+ <text x="2.44" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>+1</text> </g>
+ <g id="shape53-145" v:mID="53" v:groupContext="shape" transform="translate(230.508,-259.7)">
+ <title>Sheet.53</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape54-147" v:mID="54" v:groupContext="shape" transform="translate(230.508,-259.7)">
+ <title>Sheet.54</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape55-150" v:mID="55" v:groupContext="shape" transform="translate(233.39,-260.497)">
+ <title>Sheet.55</title>
+ <desc>3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>3</text> </g>
+ <g id="shape56-154" v:mID="56" v:groupContext="shape" transform="translate(230.508,-243.164)">
+ <title>Sheet.56</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st4"/>
+ </g>
+ <g id="shape57-156" v:mID="57" v:groupContext="shape" transform="translate(230.508,-243.164)">
+ <title>Sheet.57</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape58-159" v:mID="58" v:groupContext="shape" transform="translate(233.39,-244.053)">
+ <title>Sheet.58</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape59-163" v:mID="59" v:groupContext="shape" transform="translate(263.764,-244.053)">
+ <title>Sheet.59</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape60-167" v:mID="60" v:groupContext="shape" transform="translate(230.508,-227.107)">
+ <title>Sheet.60</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape61-169" v:mID="61" v:groupContext="shape" transform="translate(230.508,-227.107)">
+ <title>Sheet.61</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape62-172" v:mID="62" v:groupContext="shape" transform="translate(233.39,-227.976)">
+ <title>Sheet.62</title>
+ <desc>5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>5</text> </g>
+ <g id="shape63-176" v:mID="63" v:groupContext="shape" transform="translate(230.508,-210.211)">
+ <title>Sheet.63</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape64-178" v:mID="64" v:groupContext="shape" transform="translate(230.508,-210.211)">
+ <title>Sheet.64</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape65-181" v:mID="65" v:groupContext="shape" transform="translate(233.39,-211.085)">
+ <title>Sheet.65</title>
+ <desc>6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>6</text> </g>
+ <g id="shape66-185" v:mID="66" v:groupContext="shape" transform="translate(230.508,-193.794)">
+ <title>Sheet.66</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st4"/>
+ </g>
+ <g id="shape67-187" v:mID="67" v:groupContext="shape" transform="translate(230.508,-193.794)">
+ <title>Sheet.67</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape68-190" v:mID="68" v:groupContext="shape" transform="translate(233.39,-194.681)">
+ <title>Sheet.68</title>
+ <desc>7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>7</text> </g>
+ <g id="shape69-194" v:mID="69" v:groupContext="shape" transform="translate(263.764,-194.681)">
+ <title>Sheet.69</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape70-198" v:mID="70" v:groupContext="shape" transform="translate(230.508,-177.258)">
+ <title>Sheet.70</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape71-200" v:mID="71" v:groupContext="shape" transform="translate(230.508,-177.258)">
+ <title>Sheet.71</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape72-203" v:mID="72" v:groupContext="shape" transform="translate(233.39,-178.117)">
+ <title>Sheet.72</title>
+ <desc>8</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>8</text> </g>
+ <g id="shape73-207" v:mID="73" v:groupContext="shape" transform="translate(230.508,-160.602)">
+ <title>Sheet.73</title>
+ <path d="M0 363.15 L0 379.8 L41.18 379.8 L41.18 363.15 L0 363.15 L0 363.15 Z" class="st7"/>
+ </g>
+ <g id="shape74-209" v:mID="74" v:groupContext="shape" transform="translate(230.508,-160.602)">
+ <title>Sheet.74</title>
+ <path d="M0 363.15 L41.18 363.15 L41.18 379.8 L0 379.8 L0 363.15" class="st5"/>
+ </g>
+ <g id="shape75-212" v:mID="75" v:groupContext="shape" transform="translate(233.39,-161.505)">
+ <title>Sheet.75</title>
+ <desc>9</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>9</text> </g>
+ <g id="shape76-216" v:mID="76" v:groupContext="shape" transform="translate(230.508,-143.946)">
+ <title>Sheet.76</title>
+ <path d="M0 363.15 L0 379.8 L41.18 379.8 L41.18 363.15 L0 363.15 L0 363.15 Z" class="st4"/>
+ </g>
+ <g id="shape77-218" v:mID="77" v:groupContext="shape" transform="translate(230.508,-143.946)">
+ <title>Sheet.77</title>
+ <path d="M0 363.15 L41.18 363.15 L41.18 379.8 L0 379.8 L0 363.15" class="st5"/>
+ </g>
+ <g id="shape78-221" v:mID="78" v:groupContext="shape" transform="translate(233.39,-144.841)">
+ <title>Sheet.78</title>
+ <desc>10</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>10</text> </g>
+ <g id="shape79-225" v:mID="79" v:groupContext="shape" transform="translate(263.764,-144.841)">
+ <title>Sheet.79</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape80-229" v:mID="80" v:groupContext="shape" transform="translate(230.508,-127.529)">
+ <title>Sheet.80</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape81-231" v:mID="81" v:groupContext="shape" transform="translate(230.508,-127.529)">
+ <title>Sheet.81</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape82-234" v:mID="82" v:groupContext="shape" transform="translate(233.39,-128.329)">
+ <title>Sheet.82</title>
+ <desc>11</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>11</text> </g>
+ <g id="shape83-238" v:mID="83" v:groupContext="shape" transform="translate(230.508,-110.754)">
+ <title>Sheet.83</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape84-240" v:mID="84" v:groupContext="shape" transform="translate(230.508,-110.754)">
+ <title>Sheet.84</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape85-243" v:mID="85" v:groupContext="shape" transform="translate(233.39,-111.64)">
+ <title>Sheet.85</title>
+ <desc>12</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>12</text> </g>
+ <g id="shape86-247" v:mID="86" v:groupContext="shape" transform="translate(230.508,-94.9362)">
+ <title>Sheet.86</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape87-249" v:mID="87" v:groupContext="shape" transform="translate(230.508,-94.9362)">
+ <title>Sheet.87</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape88-252" v:mID="88" v:groupContext="shape" transform="translate(233.39,-95.7375)">
+ <title>Sheet.88</title>
+ <desc>…</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.30974" cy="373.835" width="14.62" height="11.9384"/>
+ <path d="M14.62 367.87 L0 367.87 L0 379.8 L14.62 379.8 L14.62 367.87" class="st2"/>
+ <text x="2.34" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>…</text> </g>
+ <g id="shape89-256" v:mID="89" v:groupContext="shape" transform="translate(230.508,-78.999)">
+ <title>Sheet.89</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape90-258" v:mID="90" v:groupContext="shape" transform="translate(230.508,-78.999)">
+ <title>Sheet.90</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape91-261" v:mID="91" v:groupContext="shape" transform="translate(233.39,-79.8525)">
+ <title>Sheet.91</title>
+ <desc>255</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1326" cy="373.835" width="22.27" height="11.9384"/>
+ <path d="M22.27 367.87 L0 367.87 L0 379.8 L22.27 379.8 L22.27 367.87" class="st2"/>
+ <text x="2.84" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>255</text> </g>
+ <g id="shape92-265" v:mID="92" v:groupContext="shape" transform="translate(276.219,-250.503)">
+ <title>Sheet.92</title>
+ <path d="M0.33 311.98 L396.81 375.94 L396.48 377.95 L0 313.99 L0.33 311.98 L0.33 311.98 ZM396.12 373.75 L401.68 377.74
+ L395.16 379.8 L396.12 373.75 L396.12 373.75 Z" class="st15"/>
+ </g>
+ <g id="shape93-267" v:mID="93" v:groupContext="shape" transform="translate(275.859,-178.426)">
+ <title>Sheet.93</title>
+ <path d="M0.57 305.72 L230.93 376.21 L230.33 378.16 L0 307.67 L0.57 305.72 L0.57 305.72 ZM230.57 373.96 L235.52 378.67
+ L228.77 379.8 L230.57 373.96 L230.57 373.96 Z" class="st15"/>
+ </g>
+ <g id="shape94-269" v:mID="94" v:groupContext="shape" transform="translate(276.279,-151.285)">
+ <title>Sheet.94</title>
+ <path d="M0.21 379.8 L230.12 353.17 L229.88 351.14 L0 377.8 L0.21 379.8 L0.21 379.8 ZM229.34 355.3 L235.07 351.55 L228.65
+ 349.25 L229.34 355.3 L229.34 355.3 Z" class="st15"/>
+ </g>
+ <g id="shape95-271" v:mID="95" v:groupContext="shape" transform="translate(276.009,-232.679)">
+ <title>Sheet.95</title>
+ <path d="M0.27 327.47 L354.22 375.91 L353.95 377.92 L0 329.48 L0.27 327.47 L0.27 327.47 ZM353.5 373.75 L359.15 377.62
+ L352.66 379.8 L353.5 373.75 L353.5 373.75 Z" class="st10"/>
+ </g>
+ <g id="shape96-273" v:mID="96" v:groupContext="shape" transform="translate(276.279,-201.134)">
+ <title>Sheet.96</title>
+ <path d="M0.21 379.8 L353.86 348.14 L353.68 346.1 L0 377.77 L0.21 379.8 L0.21 379.8 ZM353.05 350.24 L358.88 346.64 L352.48
+ 344.16 L353.05 350.24 L353.05 350.24 Z" class="st15"/>
+ </g>
+ <g id="shape97-275" v:mID="97" v:groupContext="shape" transform="translate(346.482,-41.2531)">
+ <title>Sheet.97</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st7"/>
+ </g>
+ <g id="shape98-277" v:mID="98" v:groupContext="shape" transform="translate(346.482,-41.2531)">
+ <title>Sheet.98</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape99-280" v:mID="99" v:groupContext="shape" transform="translate(349.371,-91.6514)">
+ <title>Sheet.99</title>
+ <desc>…</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.30974" cy="373.835" width="14.62" height="11.9384"/>
+ <path d="M14.62 367.87 L0 367.87 L0 379.8 L14.62 379.8 L14.62 367.87" class="st2"/>
+ <text x="2.34" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>…</text> </g>
+ <g id="shape100-284" v:mID="100" v:groupContext="shape" transform="translate(470.019,-94.337)">
+ <title>Sheet.100</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st7"/>
+ </g>
+ <g id="shape101-286" v:mID="101" v:groupContext="shape" transform="translate(470.019,-94.337)">
+ <title>Sheet.101</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape102-289" v:mID="102" v:groupContext="shape" transform="translate(472.925,-144.778)">
+ <title>Sheet.102</title>
+ <desc>5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>5</text> </g>
+ <g id="shape103-293" v:mID="103" v:groupContext="shape" transform="translate(511.558,-113.749)">
+ <title>Sheet.103</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape104-295" v:mID="104" v:groupContext="shape" transform="translate(511.558,-113.749)">
+ <title>Sheet.104</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape105-298" v:mID="105" v:groupContext="shape" transform="translate(514.441,-164.138)">
+ <title>Sheet.105</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape106-302" v:mID="106" v:groupContext="shape" transform="translate(542.148,-164.138)">
+ <title>Sheet.106</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape107-306" v:mID="107" v:groupContext="shape" transform="translate(542.148,-152.155)">
+ <title>Sheet.107</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape108-310" v:mID="108" v:groupContext="shape" transform="translate(536.626,-140.172)">
+ <title>Sheet.108</title>
+ <desc>10</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>10</text> </g>
+ <g id="shape109-314" v:mID="109" v:groupContext="shape" transform="translate(514.201,-114.441)">
+ <title>Sheet.109</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape110-318" v:mID="110" v:groupContext="shape" transform="translate(519.723,-114.441)">
+ <title>Sheet.110</title>
+ <desc>+4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="8.11122" cy="373.835" width="16.23" height="11.9384"/>
+ <path d="M16.22 367.87 L0 367.87 L0 379.8 L16.22 379.8 L16.22 367.87" class="st2"/>
+ <text x="2.44" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>+4</text> </g>
+ <g id="shape111-322" v:mID="111" v:groupContext="shape" transform="translate(552.257,-130.525)">
+ <title>Sheet.111</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st7"/>
+ </g>
+ <g id="shape112-324" v:mID="112" v:groupContext="shape" transform="translate(552.257,-130.525)">
+ <title>Sheet.112</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape113-327" v:mID="113" v:groupContext="shape" transform="translate(555.203,-180.952)">
+ <title>Sheet.113</title>
+ <desc>3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>3</text> </g>
+ <g id="shape114-331" v:mID="114" v:groupContext="shape" transform="translate(634.615,-169.11)">
+ <title>Sheet.114</title>
+ <path d="M0 313.9 L0 379.8 L41.18 379.8 L41.18 313.9 L0 313.9 L0 313.9 Z" class="st4"/>
+ </g>
+ <g id="shape115-333" v:mID="115" v:groupContext="shape" transform="translate(634.615,-169.11)">
+ <title>Sheet.115</title>
+ <path d="M0 313.9 L41.18 313.9 L41.18 379.8 L0 379.8 L0 313.9" class="st5"/>
+ </g>
+ <g id="shape116-336" v:mID="116" v:groupContext="shape" transform="translate(637.526,-219.595)">
+ <title>Sheet.116</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape117-340" v:mID="117" v:groupContext="shape" transform="translate(665.234,-219.595)">
+ <title>Sheet.117</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape118-344" v:mID="118" v:groupContext="shape" transform="translate(665.2,-225.489)">
+ <title>Sheet.118</title>
+ <path d="M0 379.32 L0 379.8 L5.52 379.8 L5.52 379.32 L0 379.32 L0 379.32 Z" class="st19"/>
+ </g>
+ <g id="shape119-346" v:mID="119" v:groupContext="shape" transform="translate(665.234,-207.612)">
+ <title>Sheet.119</title>
+ <desc>7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>7</text> </g>
+ <g id="shape120-350" v:mID="120" v:groupContext="shape" transform="translate(637.286,-169.898)">
+ <title>Sheet.120</title>
+ <desc>5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>5</text> </g>
+ <g id="shape121-354" v:mID="121" v:groupContext="shape" transform="translate(642.809,-169.898)">
+ <title>Sheet.121</title>
+ <desc>-</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="3.49545" cy="373.835" width="7" height="11.9384"/>
+ <path d="M6.99 367.87 L0 367.87 L0 379.8 L6.99 379.8 L6.99 367.87" class="st2"/>
+ <text x="1.84" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>-</text> </g>
+ <g id="shape122-358" v:mID="122" v:groupContext="shape" transform="translate(646.17,-169.898)">
+ <title>Sheet.122</title>
+ <desc>3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>3</text> </g>
+ <g id="shape123-362" v:mID="123" v:groupContext="shape" transform="translate(676.275,-186.725)">
+ <title>Sheet.123</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape124-364" v:mID="124" v:groupContext="shape" transform="translate(676.275,-186.725)">
+ <title>Sheet.124</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape125-367" v:mID="125" v:groupContext="shape" transform="translate(679.141,-237.17)">
+ <title>Sheet.125</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape126-371" v:mID="126" v:groupContext="shape" transform="translate(706.849,-237.17)">
+ <title>Sheet.126</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape127-375" v:mID="127" v:groupContext="shape" transform="translate(678.901,-187.474)">
+ <title>Sheet.127</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape128-379" v:mID="128" v:groupContext="shape" transform="translate(304.943,-21.841)">
+ <title>Sheet.128</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape129-381" v:mID="129" v:groupContext="shape" transform="translate(304.943,-21.841)">
+ <title>Sheet.129</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape130-384" v:mID="130" v:groupContext="shape" transform="translate(307.855,-72.2917)">
+ <title>Sheet.130</title>
+ <desc>64</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>64</text> </g>
+ <g id="shape131-388" v:mID="131" v:groupContext="shape" transform="translate(330.041,-72.2917)">
+ <title>Sheet.131</title>
+ <desc>96</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>96</text> </g>
+ <g id="shape132-392" v:mID="132" v:groupContext="shape" transform="translate(307.616,-22.5952)">
+ <title>Sheet.132</title>
+ <desc>7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>7</text> </g>
+ <g id="shape133-396" v:mID="133" v:groupContext="shape" transform="translate(428.72,-77.4413)">
+ <title>Sheet.133</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape134-398" v:mID="134" v:groupContext="shape" transform="translate(428.72,-77.4413)">
+ <title>Sheet.134</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape135-401" v:mID="135" v:groupContext="shape" transform="translate(431.648,-127.825)">
+ <title>Sheet.135</title>
+ <desc>6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>6</text> </g>
+ <g id="shape136-405" v:mID="136" v:groupContext="shape" transform="translate(453.834,-127.825)">
+ <title>Sheet.136</title>
+ <desc>98</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>98</text> </g>
+ <g id="shape137-409" v:mID="137" v:groupContext="shape" transform="translate(431.409,-78.1289)">
+ <title>Sheet.137</title>
+ <desc>5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>5</text> </g>
+ <g id="shape138-413" v:mID="138" v:groupContext="shape" transform="translate(593.796,-149.818)">
+ <title>Sheet.138</title>
+ <path d="M0 313.9 L0 379.8 L41.18 379.8 L41.18 313.9 L0 313.9 L0 313.9 Z" class="st4"/>
+ </g>
+ <g id="shape139-415" v:mID="139" v:groupContext="shape" transform="translate(593.796,-149.818)">
+ <title>Sheet.139</title>
+ <path d="M0 313.9 L41.18 313.9 L41.18 379.8 L0 379.8 L0 313.9" class="st5"/>
+ </g>
+ <g id="shape140-418" v:mID="140" v:groupContext="shape" transform="translate(596.718,-200.312)">
+ <title>Sheet.140</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape141-422" v:mID="141" v:groupContext="shape" transform="translate(618.904,-200.312)">
+ <title>Sheet.141</title>
+ <desc>99</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>99</text> </g>
+ <g id="shape142-426" v:mID="142" v:groupContext="shape" transform="translate(596.478,-150.615)">
+ <title>Sheet.142</title>
+ <desc>9</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>9</text> </g>
+ <g id="shape143-430" v:mID="143" v:groupContext="shape" transform="translate(387.181,-58.0291)">
+ <title>Sheet.143</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape144-432" v:mID="144" v:groupContext="shape" transform="translate(387.181,-58.0291)">
+ <title>Sheet.144</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape145-435" v:mID="145" v:groupContext="shape" transform="translate(390.133,-108.466)">
+ <title>Sheet.145</title>
+ <desc>7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>7</text> </g>
+ <g id="shape146-439" v:mID="146" v:groupContext="shape" transform="translate(412.318,-108.466)">
+ <title>Sheet.146</title>
+ <desc>97</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>97</text> </g>
+ <g id="shape147-443" v:mID="147" v:groupContext="shape" transform="translate(389.893,-58.7692)">
+ <title>Sheet.147</title>
+ <desc>6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>6</text> </g>
+ <g id="shape148-447" v:mID="148" v:groupContext="shape" transform="translate(31.8163,-277.674)">
+ <title>Sheet.148</title>
+ <path d="M0 362.83 C0 360.95 1.52 359.43 3.41 359.43 L68.51 359.43 C70.4 359.43 71.91 360.95 71.91 362.83 L71.91 376.41
+ C71.91 378.28 70.4 379.8 68.51 379.8 L3.41 379.8 C1.52 379.8 0 378.28 0 376.41 L0 362.83 Z" class="st4"/>
+ </g>
+ <g id="shape149-449" v:mID="149" v:groupContext="shape" transform="translate(31.8163,-277.674)">
+ <title>Sheet.149</title>
+ <path d="M0 362.83 C0 360.95 1.52 359.43 3.41 359.43 L68.51 359.43 C70.4 359.43 71.91 360.95 71.91 362.83 L71.91 376.41
+ C71.91 378.28 70.4 379.8 68.51 379.8 L3.41 379.8 C1.52 379.8 0 378.28 0 376.41 L0 362.83 Z" class="st21"/>
+ </g>
+ <g id="shape150-451" v:mID="150" v:groupContext="shape" transform="translate(36,-278.851)">
+ <title>Sheet.150</title>
+ <desc>Insert key</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="35.613" cy="372.612" width="71.23" height="14.3829"/>
+ <path d="M71.23 365.42 L0 365.42 L0 379.8 L71.23 379.8 L71.23 365.42" class="st2"/>
+ <text x="9.64" y="376.21" class="st22" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Insert key </text> </g>
+ <g id="shape151-455" v:mID="151" v:groupContext="shape" transform="translate(47.7236,-257.004)">
+ <title>Sheet.151</title>
+ <path d="M0 369.44 L9.81 369.44 L9.81 359.07 L29.44 359.07 L29.44 369.44 L39.26 369.44 L19.63 379.8 L0 369.44 Z"
+ class="st23"/>
+ </g>
+ <g id="shape152-457" v:mID="152" v:groupContext="shape" transform="translate(31.8163,-236.094)">
+ <title>Sheet.152</title>
+ <path d="M0 362.73 C0 360.85 1.54 359.31 3.42 359.31 L68.49 359.31 C70.38 359.31 71.91 360.85 71.91 362.73 L71.91 376.39
+ C71.91 378.28 70.38 379.8 68.49 379.8 L3.42 379.8 C1.54 379.8 0 378.28 0 376.39 L0 362.73 Z" class="st4"/>
+ </g>
+ <g id="shape153-459" v:mID="153" v:groupContext="shape" transform="translate(31.8163,-236.094)">
+ <title>Sheet.153</title>
+ <path d="M0 362.73 C0 360.85 1.54 359.31 3.42 359.31 L68.49 359.31 C70.38 359.31 71.91 360.85 71.91 362.73 L71.91 376.39
+ C71.91 378.28 70.38 379.8 68.49 379.8 L3.42 379.8 C1.54 379.8 0 378.28 0 376.39 L0 362.73 Z" class="st21"/>
+ </g>
+ <g id="shape154-461" v:mID="154" v:groupContext="shape" transform="translate(54.6845,-237.332)">
+ <title>Sheet.154</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.8573" cy="372.612" width="33.72" height="14.3829"/>
+ <path d="M33.71 365.42 L0 365.42 L0 379.8 L33.71 379.8 L33.71 365.42" class="st2"/>
+ <text x="3.86" y="376.21" class="st22" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape155-465" v:mID="155" v:groupContext="shape" transform="translate(23.0522,-195.232)">
+ <title>Sheet.155</title>
+ <path d="M0 363.53 C0 361.73 1.46 360.27 3.26 360.27 L87.15 360.27 C88.95 360.27 90.4 361.73 90.4 363.53 L90.4 376.55
+ C90.4 378.35 88.95 379.8 87.15 379.8 L3.26 379.8 C1.46 379.8 0 378.35 0 376.55 L0 363.53 Z" class="st4"/>
+ </g>
+ <g id="shape156-467" v:mID="156" v:groupContext="shape" transform="translate(23.0522,-195.232)">
+ <title>Sheet.156</title>
+ <path d="M0 363.53 C0 361.73 1.46 360.27 3.26 360.27 L87.15 360.27 C88.95 360.27 90.4 361.73 90.4 363.53 L90.4 376.55
+ C90.4 378.35 88.95 379.8 87.15 379.8 L3.26 379.8 C1.46 379.8 0 378.35 0 376.55 L0 363.53 Z" class="st21"/>
+ </g>
+ <g id="shape157-469" v:mID="157" v:groupContext="shape" transform="translate(27,-196.017)">
+ <title>Sheet.157</title>
+ <desc>0x0102ABCD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.6644" cy="372.612" width="87.33" height="14.3829"/>
+ <path d="M87.33 365.42 L0 365.42 L0 379.8 L87.33 379.8 L87.33 365.42" class="st2"/>
+ <text x="7.36" y="376.21" class="st22" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102ABCD</text> </g>
+ <g id="shape158-473" v:mID="158" v:groupContext="shape" transform="translate(47.7236,-214.824)">
+ <title>Sheet.158</title>
+ <path d="M0 369.5 L9.81 369.5 L9.81 359.19 L29.44 359.19 L29.44 369.5 L39.26 369.5 L19.63 379.8 L0 369.5 Z"
+ class="st23"/>
+ </g>
+ <g id="shape159-475" v:mID="159" v:groupContext="shape" transform="translate(49.8539,-181.212)">
+ <title>Sheet.159</title>
+ <path d="M11.89 368.42 C11.89 371.57 11.47 374.11 10.94 374.11 L6.9 374.11 C6.37 374.11 5.94 376.67 5.94 379.8 C5.94
+ 376.67 5.52 374.11 5 374.11 L0.95 374.11 C0.43 374.11 0 371.57 0 368.42" class="st24"/>
+ </g>
+ <g id="shape160-478" v:mID="160" v:groupContext="shape" transform="translate(64.2606,-180.973)">
+ <title>Sheet.160</title>
+ <path d="M9.54 368.54 C9.54 371.66 9.21 374.17 8.79 374.17 L5.53 374.17 C5.11 374.17 4.77 376.7 4.77 379.8 C4.77 376.7
+ 4.43 374.17 4.02 374.17 L0.76 374.17 C0.34 374.17 0 371.66 0 368.54" class="st24"/>
+ </g>
+ <g id="shape161-481" v:mID="161" v:groupContext="shape" transform="translate(18.19,-60.9649)">
+ <title>Sheet.161</title>
+ <path d="M0 354.74 C0 351.97 2.25 349.73 5.03 349.73 L10.77 349.73 L30.27 267.14 L26.92 349.73 L59.58 349.73 C62.35 349.73
+ 64.59 351.97 64.59 354.74 L64.59 354.74 L64.59 362.26 L64.59 374.8 C64.59 377.57 62.35 379.8 59.58 379.8
+ L26.92 379.8 L10.77 379.8 L10.77 379.8 L5.03 379.8 C2.25 379.8 0 377.57 0 374.8 L0 362.26 L0 354.74 L0 354.74
+ Z" class="st23"/>
+ </g>
+ <g id="shape162-483" v:mID="162" v:groupContext="shape" transform="translate(28.141,-66.9569)">
+ <title>Sheet.162</title>
+ <desc>chunk id</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="27.5794" cy="372.612" width="55.16" height="14.3829"/>
+ <path d="M55.16 365.42 L0 365.42 L0 379.8 L55.16 379.8 L55.16 365.42" class="st2"/>
+ <text x="5.26" y="376.21" class="st25" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>chunk id</text> </g>
+ <g id="shape163-487" v:mID="163" v:groupContext="shape" transform="translate(50.8451,-112.132)">
+ <title>Sheet.163</title>
+ <path d="M0 354.64 C0 351.87 2.27 349.61 5.04 349.61 L10.74 349.61 L16.27 313.66 L26.86 349.61 L59.43 349.61 C62.22 349.61
+ 64.47 351.87 64.47 354.64 L64.47 354.64 L64.47 362.19 L64.47 374.77 C64.47 377.56 62.22 379.8 59.43 379.8
+ L26.86 379.8 L10.74 379.8 L10.74 379.8 L5.04 379.8 C2.27 379.8 0 377.56 0 374.77 L0 362.19 L0 354.64 L0
+ 354.64 Z" class="st23"/>
+ </g>
+ <g id="shape164-489" v:mID="164" v:groupContext="shape" transform="translate(68.8168,-118.181)">
+ <title>Sheet.164</title>
+ <desc>bin id</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="18.3881" cy="372.612" width="36.78" height="14.3829"/>
+ <path d="M36.78 365.42 L0 365.42 L0 379.8 L36.78 379.8 L36.78 365.42" class="st2"/>
+ <text x="4.06" y="376.21" class="st25" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bin id</text> </g>
+ <g id="shape165-493" v:mID="165" v:groupContext="shape" transform="translate(113.454,-225.085)">
+ <title>Sheet.165</title>
+ <path d="M0.01 375.68 L13.23 375.73 L13.22 377.77 L0 377.72 L0.01 375.68 L0.01 375.68 ZM12.22 373.69 L18.33 376.76 L12.2
+ 379.8 L12.22 373.69 L12.22 373.69 Z" class="st26"/>
+ </g>
+ <g id="shape166-495" v:mID="166" v:groupContext="shape" transform="translate(200.975,-280.969)">
+ <title>Sheet.166</title>
+ <path d="M0 375.73 L20.11 375.73 L20.11 377.77 L0 377.77 L0 375.73 L0 375.73 ZM19.09 373.69 L25.21 376.75 L19.09 379.8
+ L19.09 373.69 L19.09 373.69 Z" class="st26"/>
+ </g>
+ <g id="shape167-497" v:mID="167" v:groupContext="shape" transform="translate(275.739,-179.745)">
+ <title>Sheet.167</title>
+ <path d="M0.81 274.59 L231.38 376.48 L230.54 378.37 L0 276.48 L0.81 274.59 L0.81 274.59 ZM231.26 374.2 L235.64 379.47
+ L228.8 379.8 L231.26 374.2 L231.26 374.2 Z" class="st27"/>
+ </g>
+ <g id="shape168-499" v:mID="168" v:groupContext="shape" transform="translate(521.823,-96.8834)">
+ <title>Sheet.168</title>
+ <path d="M127.17 309.02 L127.17 378.79 C127.17 379.35 126.72 379.8 126.15 379.8 L3.06 379.8 C2.52 379.8 2.04 379.35 2.04
+ 378.79 L2.04 369.59 L4.08 369.59 L4.08 378.79 L3.06 377.77 L126.15 377.77 L125.13 378.79 L125.13 309.02
+ L127.17 309.02 ZM0 370.61 L3.06 364.5 L6.12 370.61 L0 370.61 Z" class="st28"/>
+ </g>
+ <g id="shape169-501" v:mID="169" v:groupContext="shape" transform="translate(478.603,-39.7553)">
+ <title>Sheet.169</title>
+ <path d="M0 347.57 C0 344.01 2.91 341.1 6.48 341.1 L237.86 341.1 C241.43 341.1 244.31 344.01 244.31 347.57 L244.31 373.36
+ C244.31 376.93 241.43 379.8 237.86 379.8 L6.48 379.8 C2.91 379.8 0 376.93 0 373.36 L0 347.57 Z"
+ class="st23"/>
+ </g>
+ <g id="shape170-503" v:mID="170" v:groupContext="shape" transform="translate(487.717,-45.5378)">
+ <title>Sheet.170</title>
+ <desc>Move bin from group 1 to 4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="126.387" cy="369.018" width="252.78" height="21.5726"/>
+ <path d="M252.77 358.23 L0 358.23 L0 379.8 L252.77 379.8 L252.77 358.23" class="st2"/>
+ <text x="18.98" y="374.41" class="st29" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Move bin from group 1 to 4</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i2.svg b/doc/guides/prog_guide/img/efd_i2.svg
new file mode 100644
index 0000000..a5f43f9
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i2.svg
@@ -0,0 +1,280 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i2.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="2.85156in" height="2.98777in"
+ viewBox="0 0 205.313 215.12" xml:space="preserve" color-interpolation-filters="sRGB" class="st18">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#deebf6;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st5 {fill:#ff0000;stroke:#c7c8c8;stroke-width:0.25}
+ .st6 {fill:none;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st7 {fill:none;stroke:#0070c0;stroke-width:1.5}
+ .st8 {marker-end:url(#mrkr5-91);stroke:#0070c0;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st9 {fill:#0070c0;fill-opacity:1;stroke:#0070c0;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st10 {fill:none;stroke:none;stroke-width:0.25}
+ .st11 {fill:#ff0000;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st12 {font-size:1em}
+ .st13 {marker-end:url(#mrkr5-101);stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st14 {fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st15 {fill:#5b9bd5;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st16 {marker-end:url(#mrkr5-110);stroke:#41719c;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st17 {fill:#41719c;fill-opacity:1;stroke:#41719c;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st18 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-91" class="st9" v:arrowType="5" v:arrowSize="2" v:setback="4.45" refX="-4.45" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ <marker id="mrkr5-101" class="st14" v:arrowType="5" v:arrowSize="2" v:setback="6.16" refX="-6.16" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ <marker id="mrkr5-110" class="st17" v:arrowType="5" v:arrowSize="2" v:setback="6.16" refX="-6.16" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(24.4044,-42.7174)">
+ <title>Circle</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow2-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 138.62 A76.5 76.5 0 0 1 153 138.62 A76.5 76.5 0 1 1 0 138.62 Z" class="st2"/>
+ </g>
+ <path d="M0 138.62 A76.5 76.5 0 0 1 153 138.62 A76.5 76.5 0 1 1 0 138.62 Z" class="st3"/>
+ </g>
+ <g id="shape3-6" v:mID="3" v:groupContext="shape" transform="translate(24.4044,-144.53)">
+ <title>Circle.3</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow3-7" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape4-11" v:mID="4" v:groupContext="shape" transform="translate(21.0294,-102.342)">
+ <title>Circle.4</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow4-12" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape5-16" v:mID="5" v:groupContext="shape" transform="translate(69.4044,-183.342)">
+ <title>Circle.5</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow5-17" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape6-21" v:mID="6" v:groupContext="shape" transform="translate(117.217,-183.342)">
+ <title>Circle.6</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow6-22" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st5"/>
+ </g>
+ <g id="shape7-26" v:mID="7" v:groupContext="shape" transform="translate(171.217,-104.03)">
+ <title>Circle.7</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow7-27" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st5"/>
+ </g>
+ <g id="shape8-31" v:mID="8" v:groupContext="shape" transform="translate(109.904,-38.2174)">
+ <title>Circle.8</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow8-32" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st5"/>
+ </g>
+ <g id="shape9-36" v:mID="9" v:groupContext="shape" transform="translate(21.0294,-124.842)">
+ <title>Circle.9</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow9-37" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st5"/>
+ </g>
+ <g id="shape10-41" v:mID="10" v:groupContext="shape" transform="translate(147.029,-168.717)">
+ <title>Circle.10</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow10-42" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape11-46" v:mID="11" v:groupContext="shape" transform="translate(138.029,-48.3424)">
+ <title>Circle.11</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow11-47" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape12-51" v:mID="12" v:groupContext="shape" transform="translate(160.529,-74.2174)">
+ <title>Circle.12</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow12-52" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape13-56" v:mID="13" v:groupContext="shape" transform="translate(40.7169,-57.3424)">
+ <title>Circle.13</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow13-57" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape14-61" v:mID="14" v:groupContext="shape" transform="translate(42.4044,-168.717)">
+ <title>Circle.14</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow14-62" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape15-66" v:mID="15" v:groupContext="shape" transform="translate(66.0294,-42.7174)">
+ <title>Circle.15</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow15-67" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape16-71" v:mID="16" v:groupContext="shape" transform="translate(25.5294,-79.8424)">
+ <title>Circle.16</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow16-72" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape17-76" v:mID="17" v:groupContext="shape" transform="translate(165.029,-143.405)">
+ <title>Circle.17</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow17-77" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape18-81" v:mID="18" v:groupContext="shape" transform="translate(276.618,4.50201) rotate(45)">
+ <title>Ellipse</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow18-82" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,1.63935,1.1506)" class="st1">
+ <path d="M0 187.01 A14.7383 28.1086 0 1 1 29.48 187.01 A14.7383 28.1086 0 1 1 0 187.01 Z" class="st6"/>
+ </g>
+ <path d="M0 187.01 A14.7383 28.1086 0 1 1 29.48 187.01 A14.7383 28.1086 0 1 1 0 187.01 Z" class="st7"/>
+ </g>
+ <g id="shape19-86" v:mID="19" v:groupContext="shape" transform="translate(251.273,355.436) rotate(156.038)">
+ <title>Sheet.19</title>
+ <path d="M-0 215.12 A73.4538 31.2572 85.43 0 1 40.92 208.96 L41.1 209.27" class="st8"/>
+ </g>
+ <g id="shape20-92" v:mID="20" v:groupContext="shape" transform="translate(62.705,-78.7174)">
+ <title>Sheet.20</title>
+ <desc>Target Hashed Value</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="42.6994" cy="203.87" width="85.4" height="22.5"/>
+ <rect x="0" y="192.62" width="85.3987" height="22.5" class="st10"/>
+ <text x="6.73" y="200.27" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target Hashed <tspan
+ x="28.48" dy="1.2em" class="st12">Value</tspan></text> </g>
+ <g id="shape21-96" v:mID="21" v:groupContext="shape" transform="translate(314.101,88.728) rotate(75.9638)">
+ <title>Sheet.21</title>
+ <path d="M0 215.12 L16.92 215.12" class="st13"/>
+ </g>
+ <g id="shape23-102" v:mID="23" v:groupContext="shape" transform="translate(60.4044,-138.342)">
+ <title>Sheet.23</title>
+ <desc>Keys</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="24.75" cy="203.87" width="49.5" height="22.5"/>
+ <rect x="0" y="192.62" width="49.5" height="22.5" class="st10"/>
+ <text x="13.21" y="207.47" class="st15" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Keys</text> </g>
+ <g id="shape24-105" v:mID="24" v:groupContext="shape" transform="translate(-125.293,114.034) rotate(-104.574)">
+ <title>Sheet.24</title>
+ <path d="M0 215.12 L22.9 215.12" class="st16"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i3.svg b/doc/guides/prog_guide/img/efd_i3.svg
new file mode 100644
index 0000000..ae22903
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i3.svg
@@ -0,0 +1,634 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i3.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="6.56036in" height="5.44284in"
+ viewBox="0 0 472.346 391.884" xml:space="preserve" color-interpolation-filters="sRGB" class="st22">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false"/>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st5 {marker-end:url(#mrkr5-24);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st6 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st7 {fill:none;stroke:#2e75b5;stroke-width:1}
+ .st8 {fill:#5b9bd5;font-family:Calibri;font-size:1.00001em}
+ .st9 {font-size:1em}
+ .st10 {fill:none;stroke:none;stroke-width:1}
+ .st11 {fill:#feffff;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st12 {fill:#5b9bd5;fill-opacity:0.25;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.25}
+ .st13 {fill:#4f87bb;stroke:#40709c;stroke-width:0.75}
+ .st14 {fill:#feffff;font-family:Calibri;font-size:0.75em}
+ .st15 {fill:none;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st16 {fill:none;stroke:#2e75b5;stroke-width:2.25}
+ .st17 {stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st18 {fill:#305497;stroke:#2e75b5;stroke-width:1}
+ .st19 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:none}
+ .st20 {fill:#92d050;fill-opacity:0.3;stroke:none;stroke-width:0.25}
+ .st21 {fill:#feffff;font-family:Calibri;font-size:1.16666em}
+ .st22 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-24" class="st6" v:arrowType="5" v:arrowSize="2" v:setback="6.16" refX="-6.16" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <v:layer v:name="Connector" v:index="0"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(111.25,-354.482)">
+ <title>Rectangle</title>
+ <desc>Packet Header</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="42.75" cy="382.884" width="85.5" height="18"/>
+ <g id="shadow2-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="85.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="85.5" height="18" class="st3"/>
+ <text x="13.24" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Packet Header</text> </g>
+ <g id="shape3-7" v:mID="3" v:groupContext="shape" transform="translate(192.25,-354.482)">
+ <title>Rectangle.3</title>
+ <desc>Payload</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="382.884" width="108" height="18"/>
+ <g id="shadow3-8" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="108" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="108" height="18" class="st3"/>
+ <text x="37.95" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Payload</text> </g>
+ <g id="shape4-13" v:mID="4" v:groupContext="shape" transform="translate(136,-311.232)">
+ <title>Rectangle.4</title>
+ <desc>Flow Key</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="382.884" width="54" height="18"/>
+ <g id="shadow4-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="54" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="54" height="18" class="st3"/>
+ <text x="8.87" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Flow Key</text> </g>
+ <g id="shape5-19" v:mID="5" v:groupContext="shape" transform="translate(465.501,-160.057) rotate(59.7436)">
+ <title>Sheet.5</title>
+ <path d="M0 391.88 L25.1 391.88" class="st5"/>
+ </g>
+ <g id="shape8-25" v:mID="8" v:groupContext="shape" transform="translate(219.25,-320.169)">
+ <title>Sheet.8</title>
+ <desc>Fields of the packet are used to form a flow Key</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="377.822" width="135" height="28.125"/>
+ <rect x="0" y="363.759" width="135" height="28.125" class="st7"/>
+ <text x="10.7" y="374.22" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Fields of the packet are <tspan
+ x="9.67" dy="1.2em" class="st9">used to form a flow Key</tspan></text> </g>
+ <g id="group13-29" transform="translate(120.25,-266.897)" v:mID="13" v:groupContext="group">
+ <title>Sheet.13</title>
+ <g id="shape11-30" v:mID="11" v:groupContext="shape" transform="translate(85.5,751.143) rotate(180)">
+ <title>Trapezoid</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow11-31" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,-0.345598,-1.97279)" class="st1">
+ <path d="M0 391.88 L85.5 391.88 L60.19 359.26 L25.31 359.26 L0 391.88 Z" class="st2"/>
+ </g>
+ <path d="M0 391.88 L85.5 391.88 L60.19 359.26 L25.31 359.26 L0 391.88 Z" class="st3"/>
+ </g>
+ <g id="shape12-35" v:mID="12" v:groupContext="shape" transform="translate(13.5,-6.525)">
+ <title>Sheet.12</title>
+ <desc>H(..)</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="381.689" width="54" height="20.3906"/>
+ <rect x="0" y="371.494" width="54" height="20.3906" class="st10"/>
+ <text x="16.27" y="385.29" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H(..)</text> </g>
+ </g>
+ <g id="shape14-38" v:mID="14" v:groupContext="shape" transform="translate(-229.872,96.3648) rotate(-90.0429)">
+ <title>Simple Arrow</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ArrowType" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <g id="shadow14-39" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,-1.97305,0.344122)" class="st1">
+ <path d="M0 391.88 L10.18 387.38 L10.18 389.63 L16.71 389.63 L16.71 391.88 L16.71 394.13 L10.18 394.13 L10.18 396.38
+ L0 391.88 Z" class="st12"/>
+ </g>
+ <path d="M0 391.88 L10.18 387.38 L10.18 389.63 L16.71 389.63 L16.71 391.88 L16.71 394.13 L10.18 394.13 L10.18 396.38
+ L0 391.88 Z" class="st13"/>
+ </g>
+ <g id="shape15-43" v:mID="15" v:groupContext="shape" transform="translate(212.5,-271.46)">
+ <title>Sheet.15</title>
+ <desc>Hash function is used to create a flow table index</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="377.822" width="135" height="28.125"/>
+ <rect x="0" y="363.759" width="135" height="28.125" class="st7"/>
+ <text x="9.05" y="374.22" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Hash function is used to <tspan
+ x="7.39" dy="1.2em" class="st9">create a flow table index</tspan></text> </g>
+ <g id="shape58-47" v:mID="58" v:groupContext="shape" transform="translate(199,-221.397)">
+ <title>Rectangle.58</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow58-48" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape59-53" v:mID="59" v:groupContext="shape" transform="translate(232.75,-221.397)">
+ <title>Rectangle.59</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow59-54" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape60-59" v:mID="60" v:groupContext="shape" transform="translate(280,-221.397)">
+ <title>Rectangle.60</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow60-60" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape61-65" v:mID="61" v:groupContext="shape" transform="translate(313.75,-221.397)">
+ <title>Rectangle.61</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow61-66" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape62-71" v:mID="62" v:groupContext="shape" transform="translate(361,-221.397)">
+ <title>Rectangle.62</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow62-72" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape63-76" v:mID="63" v:groupContext="shape" transform="translate(394.75,-221.397)">
+ <title>Rectangle.63</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow63-77" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape64-81" v:mID="64" v:groupContext="shape" transform="translate(199,-198.897)">
+ <title>Rectangle.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow64-82" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape65-86" v:mID="65" v:groupContext="shape" transform="translate(232.75,-198.897)">
+ <title>Rectangle.65</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow65-87" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape66-91" v:mID="66" v:groupContext="shape" transform="translate(280,-198.897)">
+ <title>Rectangle.66</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow66-92" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape67-96" v:mID="67" v:groupContext="shape" transform="translate(313.75,-198.897)">
+ <title>Rectangle.67</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow67-97" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape68-101" v:mID="68" v:groupContext="shape" transform="translate(361,-198.897)">
+ <title>Rectangle.68</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow68-102" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape69-106" v:mID="69" v:groupContext="shape" transform="translate(394.75,-198.897)">
+ <title>Rectangle.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow69-107" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape70-111" v:mID="70" v:groupContext="shape" transform="translate(199,-162.897)">
+ <title>Rectangle.70</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow70-112" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape71-117" v:mID="71" v:groupContext="shape" transform="translate(232.75,-162.897)">
+ <title>Rectangle.71</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow71-118" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape72-123" v:mID="72" v:groupContext="shape" transform="translate(280,-162.897)">
+ <title>Rectangle.72</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow72-124" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape73-129" v:mID="73" v:groupContext="shape" transform="translate(313.75,-162.897)">
+ <title>Rectangle.73</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow73-130" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape74-135" v:mID="74" v:groupContext="shape" transform="translate(361,-162.897)">
+ <title>Rectangle.74</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow74-136" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape75-141" v:mID="75" v:groupContext="shape" transform="translate(394.75,-162.897)">
+ <title>Rectangle.75</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow75-142" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape76-147" v:mID="76" v:groupContext="shape" transform="translate(199,-126.397)">
+ <title>Rectangle.76</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow76-148" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape77-152" v:mID="77" v:groupContext="shape" transform="translate(232.75,-126.397)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow77-153" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape78-157" v:mID="78" v:groupContext="shape" transform="translate(280,-126.397)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow78-158" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape79-162" v:mID="79" v:groupContext="shape" transform="translate(313.75,-126.397)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow79-163" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape80-167" v:mID="80" v:groupContext="shape" transform="translate(361,-126.397)">
+ <title>Rectangle.80</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow80-168" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="385.58" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape81-173" v:mID="81" v:groupContext="shape" transform="translate(394.75,-126.397)">
+ <title>Rectangle.81</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow81-174" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="385.58" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape82-179" v:mID="82" v:groupContext="shape" transform="translate(196.75,-117.397)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow82-180" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="256.384" width="245.25" height="135.5" class="st15"/>
+ </g>
+ <rect x="0" y="256.384" width="245.25" height="135.5" class="st16"/>
+ </g>
+ <g id="shape83-184" v:mID="83" v:groupContext="shape" transform="translate(554.884,123.862) rotate(90)">
+ <title>Sheet.83</title>
+ <path d="M0 391.88 L99 391.88" class="st17"/>
+ </g>
+ <g id="shape84-187" v:mID="84" v:groupContext="shape" transform="translate(208,-248.397)">
+ <title>Sheet.84</title>
+ <desc>Load Balancing Flow Table</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="91.75" cy="386.259" width="183.5" height="11.25"/>
+ <rect x="0" y="380.634" width="183.5" height="11.25" class="st18"/>
+ <text x="26.14" y="389.86" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Load Balancing Flow Table</text> </g>
+ <g id="shape85-190" v:mID="85" v:groupContext="shape" transform="translate(190,-157.835)">
+ <title>Rectangle.85</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow85-191" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="363.759" width="261" height="28.125" class="st19"/>
+ </g>
+ <rect x="0" y="363.759" width="261" height="28.125" class="st20"/>
+ </g>
+ <g id="shape86-195" v:mID="86" v:groupContext="shape" transform="translate(163,-169.022)">
+ <title>Sheet.86</title>
+ <path d="M0 391.88 L18.76 391.88" class="st5"/>
+ </g>
+ <g id="shape87-200" v:mID="87" v:groupContext="shape" transform="translate(19,-198.107)">
+ <title>Sheet.87</title>
+ <desc>Hash value used to index Flow table</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="377.822" width="135" height="28.125"/>
+ <rect x="0" y="363.759" width="135" height="28.125" class="st7"/>
+ <text x="6.79" y="374.22" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Hash value used to index <tspan
+ x="42.16" dy="1.2em" class="st9">Flow table</tspan></text> </g>
+ <g id="shape88-204" v:mID="88" v:groupContext="shape" transform="translate(551.381,21.2928) rotate(87.9001)">
+ <title>Sheet.88</title>
+ <path d="M0 391.88 L20.86 391.88" class="st5"/>
+ </g>
+ <g id="shape89-209" v:mID="89" v:groupContext="shape" transform="translate(494.785,297.309) rotate(131.987)">
+ <title>Sheet.89</title>
+ <path d="M0 391.88 L30.84 391.88" class="st5"/>
+ </g>
+ <g id="shape90-214" v:mID="90" v:groupContext="shape" transform="translate(228.25,-92.5847)">
+ <title>Rectangle.90</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow90-215" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape91-220" v:mID="91" v:groupContext="shape" transform="translate(340.75,-92.5847)">
+ <title>Rectangle.91</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow91-221" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="group96-226" transform="translate(253,-51.4597)" v:mID="96" v:groupContext="group">
+ <title>Sheet.96</title>
+ <g id="shape97-227" v:mID="97" v:groupContext="shape" transform="translate(85.5,751.143) rotate(180)">
+ <title>Trapezoid</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow97-228" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,-0.345598,-1.97279)" class="st1">
+ <path d="M0 391.88 L85.5 391.88 L60.19 359.26 L25.31 359.26 L0 391.88 Z" class="st2"/>
+ </g>
+ <path d="M0 391.88 L85.5 391.88 L60.19 359.26 L25.31 359.26 L0 391.88 Z" class="st3"/>
+ </g>
+ <g id="shape98-232" v:mID="98" v:groupContext="shape" transform="translate(13.5,-6.525)">
+ <title>Sheet.98</title>
+ <desc>Match</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="381.689" width="54" height="20.3906"/>
+ <rect x="0" y="371.494" width="54" height="20.3906" class="st10"/>
+ <text x="10.98" y="385.29" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Match</text> </g>
+ </g>
+ <g id="shape99-235" v:mID="99" v:groupContext="shape" transform="translate(532.137,0.00916548) rotate(54.6508)">
+ <title>Sheet.99</title>
+ <path d="M0 391.88 L93.23 391.88" class="st5"/>
+ </g>
+ <g id="shape100-240" v:mID="100" v:groupContext="shape" transform="translate(683.134,224.487) rotate(90)">
+ <title>Sheet.100</title>
+ <path d="M0 391.88 L77.15 391.88" class="st5"/>
+ </g>
+ <g id="shape101-245" v:mID="101" v:groupContext="shape" transform="translate(692.213,476.024) rotate(129.078)">
+ <title>Sheet.101</title>
+ <path d="M0 391.88 L95.37 391.88" class="st5"/>
+ </g>
+ <g id="shape102-250" v:mID="102" v:groupContext="shape" transform="translate(293.5,-97.0847)">
+ <title>Rectangle.102</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow102-251" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape103-256" v:mID="103" v:groupContext="shape" transform="translate(169.75,-55.9597)">
+ <title>Rectangle.103</title>
+ <desc>Flow Key</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="382.884" width="54" height="18"/>
+ <g id="shadow103-257" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="54" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="54" height="18" class="st3"/>
+ <text x="8.87" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Flow Key</text> </g>
+ <g id="shape104-262" v:mID="104" v:groupContext="shape" transform="translate(226,-64.9597)">
+ <title>Sheet.104</title>
+ <path d="M0 391.88 L34.34 391.88" class="st5"/>
+ </g>
+ <g id="shape105-267" v:mID="105" v:groupContext="shape" transform="translate(54,-82.4597)">
+ <title>Sheet.105</title>
+ <desc>Retrieved keys are matched with input key</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="377.822" width="135" height="28.125"/>
+ <rect x="0" y="363.759" width="135" height="28.125" class="st7"/>
+ <text x="22.51" y="374.22" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Retrieved keys are <tspan
+ x="9.83" dy="1.2em" class="st9">matched with input key</tspan></text> </g>
+ <g id="shape106-271" v:mID="106" v:groupContext="shape" transform="translate(271,-23.9597)">
+ <title>Rectangle.106</title>
+ <desc>Action</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="382.884" width="54" height="18"/>
+ <g id="shadow106-272" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="54" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="54" height="18" class="st3"/>
+ <text x="8.67" y="387.08" class="st21" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action</text> </g>
+ <g id="shape111-277" v:mID="111" v:groupContext="shape" transform="translate(-94.8716,350.902) rotate(-90.0429)">
+ <title>Simple Arrow.111</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ArrowType" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <g id="shadow111-278" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,-1.97305,0.344122)" class="st1">
+ <path d="M0 391.88 L10.18 387.38 L10.18 389.63 L16.71 389.63 L16.71 391.88 L16.71 394.13 L10.18 394.13 L10.18 396.38
+ L0 391.88 Z" class="st12"/>
+ </g>
+ <path d="M0 391.88 L10.18 387.38 L10.18 389.63 L16.71 389.63 L16.71 391.88 L16.71 394.13 L10.18 394.13 L10.18 396.38
+ L0 391.88 Z" class="st13"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i4.svg b/doc/guides/prog_guide/img/efd_i4.svg
new file mode 100644
index 0000000..5be5ccd
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i4.svg
@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i4.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="2.78993in" height="1.78151in"
+ viewBox="0 0 200.875 128.269" xml:space="preserve" color-interpolation-filters="sRGB" class="st19">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:none;stroke:none;stroke-width:0.25}
+ .st2 {fill:#5b9bd5;font-family:Calibri;font-size:0.75em}
+ .st3 {font-size:1em}
+ .st4 {fill:#5b9bd5;font-family:Calibri;font-size:0.75em;font-weight:bold}
+ .st5 {fill:#deebf6;stroke:none;stroke-width:0.25}
+ .st6 {stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st7 {stroke:#5b9bd5;stroke-dasharray:0.75,1.5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st8 {fill:#ff0000;font-size:1em}
+ .st9 {baseline-shift:-28.8834%;font-size:0.577667em}
+ .st10 {fill:#ff0000;font-family:Calibri;font-size:0.75em}
+ .st11 {fill:#5b9bd5;font-size:1em}
+ .st12 {visibility:visible}
+ .st13 {fill:#5b9bd5;fill-opacity:0.25;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.25}
+ .st14 {fill:url(#grad0-73);stroke:#40709c;stroke-width:0.75}
+ .st15 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st16 {fill:#00fefe;font-size:1em}
+ .st17 {fill:#00b050}
+ .st18 {stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st19 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Patterns_And_Gradients">
+ <linearGradient id="grad0-73" x1="0" y1="0" x2="1" y2="0" gradientTransform="rotate(250 0.5 0.5)">
+ <stop offset="0" stop-color="#4f87bb" stop-opacity="1"/>
+ <stop offset="0.48" stop-color="#4f87bb" stop-opacity="1"/>
+ <stop offset="0.82" stop-color="#5b9bd5" stop-opacity="1"/>
+ </linearGradient>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(18.25,-59.3478)">
+ <title>Sheet.2</title>
+ <desc>Key 1 Key 2 ... Key 28</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="18" cy="121.519" width="36" height="13.5"/>
+ <rect x="0" y="114.769" width="36" height="13.5" class="st1"/>
+ <text x="8.09" y="108.02" class="st2" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1<v:newlineChar/><tspan
+ x="8.09" dy="1.2em" class="st3">Key </tspan>2<v:newlineChar/><tspan x="14.59" dy="1.2em" class="st3">...<v:newlineChar/></tspan><tspan
+ x="5.81" dy="1.2em" class="st3">Key </tspan>28</text> </g>
+ <g id="shape9-7" v:mID="9" v:groupContext="shape" transform="translate(52,-91.9728)">
+ <title>Sheet.9</title>
+ <desc>Target Value</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="17.4375" cy="122.644" width="34.88" height="11.25"/>
+ <rect x="0" y="117.019" width="34.875" height="11.25" class="st1"/>
+ <text x="5.43" y="119.94" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target <tspan x="6.77"
+ dy="1.2em" class="st3">Value</tspan></text> </g>
+ <g id="shape11-11" v:mID="11" v:groupContext="shape" transform="translate(52,-42.4728)">
+ <title>Sheet.11</title>
+ <desc>0 1 0</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="17.4375" cy="105.769" width="34.88" height="45"/>
+ <rect x="0" y="83.2689" width="34.875" height="45" class="st5"/>
+ <text x="15.16" y="92.27" class="st2" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0<v:newlineChar/><tspan
+ x="15.16" dy="1.2em" class="st3">1<v:newlineChar/><v:newlineChar/></tspan><tspan x="15.16" dy="2.4em"
+ class="st3">0</tspan></text> </g>
+ <g id="shape8-16" v:mID="8" v:groupContext="shape" transform="translate(180.269,21.6711) rotate(90)">
+ <title>Sheet.8</title>
+ <path d="M0 128.27 L69.75 128.27" class="st6"/>
+ </g>
+ <g id="shape10-19" v:mID="10" v:groupContext="shape" transform="translate(215.144,21.6711) rotate(90)">
+ <title>Sheet.10</title>
+ <path d="M0 128.27 L69.75 128.27" class="st6"/>
+ </g>
+ <g id="shape4-22" v:mID="4" v:groupContext="shape" transform="translate(22.75,-77.3478)">
+ <title>Sheet.4</title>
+ <path d="M0 128.27 L157.5 128.27" class="st7"/>
+ </g>
+ <g id="shape5-25" v:mID="5" v:groupContext="shape" transform="translate(23.875,-66.0978)">
+ <title>Sheet.5</title>
+ <path d="M0 128.27 L158.62 128.27" class="st7"/>
+ </g>
+ <g id="shape6-28" v:mID="6" v:groupContext="shape" transform="translate(22.75,-54.8478)">
+ <title>Sheet.6</title>
+ <path d="M0 128.27 L159.75 128.27" class="st7"/>
+ </g>
+ <g id="shape7-31" v:mID="7" v:groupContext="shape" transform="translate(22.75,-87.4728)">
+ <title>Sheet.7</title>
+ <path d="M0 128.27 L155.25 128.27" class="st6"/>
+ </g>
+ <g id="shape12-34" v:mID="12" v:groupContext="shape" transform="translate(91.9375,-42.4728)">
+ <title>Sheet.12</title>
+ <desc>0 0 0</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="8.4375" cy="105.769" width="16.88" height="45"/>
+ <rect x="0" y="83.2689" width="16.875" height="45" class="st1"/>
+ <text x="6.16" y="92.27" class="st2" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0<v:newlineChar/><tspan
+ x="6.16" dy="1.2em" class="st8">0<v:newlineChar/><v:newlineChar/></tspan><tspan x="6.16" dy="2.4em"
+ class="st3">0</tspan></text> </g>
+ <g id="shape26-39" v:mID="26" v:groupContext="shape" transform="translate(86.875,-88.5978)">
+ <title>Sheet.26</title>
+ <desc>H1(x)</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="14.0625" cy="122.644" width="28.13" height="11.25"/>
+ <rect x="0" y="117.019" width="28.125" height="11.25" class="st1"/>
+ <text x="5.03" y="125.34" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st9" v:baseFontSize="8">1</tspan><tspan dy="0.164em" class="st3">(</tspan>x)</text> </g>
+ <g id="shape27-44" v:mID="27" v:groupContext="shape" transform="translate(115,-42.4728)">
+ <title>Sheet.27</title>
+ <desc>1 1 0</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="8.4375" cy="105.769" width="16.88" height="45"/>
+ <rect x="0" y="83.2689" width="16.875" height="45" class="st1"/>
+ <text x="6.16" y="92.27" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1<v:newlineChar/><tspan
+ x="6.16" dy="1.2em" class="st11">1<v:newlineChar/><v:newlineChar/></tspan><tspan x="6.16" dy="2.4em"
+ class="st11">0</tspan></text> </g>
+ <g id="shape28-49" v:mID="28" v:groupContext="shape" transform="translate(109.938,-88.5978)">
+ <title>Sheet.28</title>
+ <desc>H2(x)</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="14.0625" cy="122.644" width="28.13" height="11.25"/>
+ <rect x="0" y="117.019" width="28.125" height="11.25" class="st1"/>
+ <text x="5.03" y="125.34" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st9" v:baseFontSize="8">2</tspan><tspan dy="0.164em" class="st3">(</tspan>x)</text> </g>
+ <g id="shape29-54" v:mID="29" v:groupContext="shape" transform="translate(155.5,-42.4728)">
+ <title>Sheet.29</title>
+ <desc>0 1 0</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="8.4375" cy="105.769" width="16.88" height="45"/>
+ <rect x="0" y="83.2689" width="16.875" height="45" class="st1"/>
+ <text x="6.16" y="92.27" class="st2" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0<v:newlineChar/><tspan
+ x="6.16" dy="1.2em" class="st3">1<v:newlineChar/><v:newlineChar/></tspan><tspan x="6.16" dy="2.4em"
+ class="st3">0</tspan></text> </g>
+ <g id="shape30-59" v:mID="30" v:groupContext="shape" transform="translate(150.438,-88.5978)">
+ <title>Sheet.30</title>
+ <desc>Hm(x)</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="14.0625" cy="122.644" width="28.13" height="11.25"/>
+ <rect x="0" y="117.019" width="28.125" height="11.25" class="st1"/>
+ <text x="4.24" y="125.34" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st9" v:baseFontSize="8">m</tspan><tspan dy="0.164em" class="st3">(</tspan>x)</text> </g>
+ <g id="shape31-64" v:mID="31" v:groupContext="shape" transform="translate(130.188,-89.7228)">
+ <title>Sheet.31</title>
+ <desc>…..</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="14.0625" cy="122.644" width="28.13" height="11.25"/>
+ <rect x="0" y="117.019" width="28.125" height="11.25" class="st1"/>
+ <text x="8.46" y="125.34" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>…..</text> </g>
+ <g id="shape32-67" v:mID="32" v:groupContext="shape" transform="translate(34,-23.3478)">
+ <title>Sheet.32</title>
+ <desc>Store m for this group of keys</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="66.375" cy="122.644" width="132.75" height="11.25"/>
+ <g id="shadow32-68" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st12">
+ <rect x="0" y="117.019" width="132.75" height="11.25" class="st13"/>
+ </g>
+ <rect x="0" y="117.019" width="132.75" height="11.25" class="st14"/>
+ <text x="6.32" y="125.64" class="st15" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Store <tspan
+ class="st16">m</tspan> for this group of keys</text> </g>
+ <g id="shape36-76" v:mID="36" v:groupContext="shape" transform="translate(159.381,-100.964)">
+ <title>Sheet.36</title>
+ <path d="M3.45 125.81 L6.87 119.34 L7.99 120.16 L3.87 128.27 L0 124.35 L0.86 123.13 L3.45 125.81 Z" class="st17"/>
+ </g>
+ <g id="group44-79" transform="translate(97.5625,-100.086)" v:mID="44" v:groupContext="group">
+ <title>Sheet.44</title>
+ <g id="shape42-80" v:mID="42" v:groupContext="shape" transform="translate(85.4972,28.6255) rotate(41.8011)">
+ <title>Sheet.42</title>
+ <path d="M0 128.27 L6.04 128.27" class="st18"/>
+ </g>
+ <g id="shape43-83" v:mID="43" v:groupContext="shape" transform="translate(-87.9035,34.8564) rotate(-43.2597)">
+ <title>Sheet.43</title>
+ <path d="M0 128.27 L5.87 128.27" class="st18"/>
+ </g>
+ </g>
+ <g id="group45-86" transform="translate(120.625,-100.086)" v:mID="45" v:groupContext="group">
+ <title>Sheet.45</title>
+ <g id="shape46-87" v:mID="46" v:groupContext="shape" transform="translate(85.4972,28.6255) rotate(41.8011)">
+ <title>Sheet.46</title>
+ <path d="M0 128.27 L6.04 128.27" class="st18"/>
+ </g>
+ <g id="shape47-90" v:mID="47" v:groupContext="shape" transform="translate(-87.9035,34.8564) rotate(-43.2597)">
+ <title>Sheet.47</title>
+ <path d="M0 128.27 L5.87 128.27" class="st18"/>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i5.svg b/doc/guides/prog_guide/img/efd_i5.svg
new file mode 100644
index 0000000..b6540ba
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i5.svg
@@ -0,0 +1,183 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i5.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="8.34375in" height="2.86443in"
+ viewBox="0 0 600.75 206.239" xml:space="preserve" color-interpolation-filters="sRGB" class="st14">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:1.5em}
+ .st5 {fill:#feffff;font-family:Calibri;font-size:1.16666em}
+ .st6 {marker-end:url(#mrkr5-36);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st7 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st8 {stroke:#5b9bd5;stroke-dasharray:1.5,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st9 {fill:none;stroke:none;stroke-width:0.25}
+ .st10 {fill:#5b9bd5;font-family:Calibri;font-size:1.5em;font-weight:bold}
+ .st11 {baseline-shift:-32.4951%;font-size:0.649902em}
+ .st12 {fill:#deebf6;stroke:#0070c0;stroke-width:1}
+ .st13 {fill:#5b9bd5;font-family:Calibri;font-size:1.5em}
+ .st14 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-36" class="st7" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(93.0294,-158.5)">
+ <title>Rectangle</title>
+ <desc>All Keys</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="216" cy="192.739" width="432" height="27"/>
+ <g id="shadow2-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="179.239" width="432" height="27" class="st2"/>
+ </g>
+ <rect x="0" y="179.239" width="432" height="27" class="st3"/>
+ <text x="187.88" y="198.14" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>All Keys</text> </g>
+ <g id="shape3-7" v:mID="3" v:groupContext="shape" transform="translate(21.0294,-77.5)">
+ <title>Rectangle.3</title>
+ <desc>Group 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="188.239" width="108" height="36"/>
+ <g id="shadow3-8" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="170.239" width="108" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="170.239" width="108" height="36" class="st3"/>
+ <text x="30.97" y="192.44" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group 1</text> </g>
+ <g id="shape4-13" v:mID="4" v:groupContext="shape" transform="translate(156.029,-77.5)">
+ <title>Rectangle.4</title>
+ <desc>Group 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="188.239" width="108" height="36"/>
+ <g id="shadow4-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="170.239" width="108" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="170.239" width="108" height="36" class="st3"/>
+ <text x="30.97" y="192.44" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group 2</text> </g>
+ <g id="shape5-19" v:mID="5" v:groupContext="shape" transform="translate(291.029,-77.5)">
+ <title>Rectangle.5</title>
+ <desc>Group 3</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="188.239" width="108" height="36"/>
+ <g id="shadow5-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="170.239" width="108" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="170.239" width="108" height="36" class="st3"/>
+ <text x="30.97" y="192.44" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group 3</text> </g>
+ <g id="shape6-25" v:mID="6" v:groupContext="shape" transform="translate(471.029,-77.5)">
+ <title>Rectangle.6</title>
+ <desc>Group X</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="188.239" width="108" height="36"/>
+ <g id="shadow6-26" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="170.239" width="108" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="170.239" width="108" height="36" class="st3"/>
+ <text x="30.88" y="192.44" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group X</text> </g>
+ <g id="shape7-31" v:mID="7" v:groupContext="shape" transform="translate(359.05,247.819) rotate(165.964)">
+ <title>Sheet.7</title>
+ <path d="M0 206.24 L178.5 206.24" class="st6"/>
+ </g>
+ <g id="shape8-37" v:mID="8" v:groupContext="shape" transform="translate(428.903,215.562) rotate(144.462)">
+ <title>Sheet.8</title>
+ <path d="M0 206.24 L70.39 206.24" class="st6"/>
+ </g>
+ <g id="shape9-42" v:mID="9" v:groupContext="shape" transform="translate(470.075,-81.0976) rotate(51.3402)">
+ <title>Sheet.9</title>
+ <path d="M0 206.24 L50.59 206.24" class="st6"/>
+ </g>
+ <g id="shape10-47" v:mID="10" v:groupContext="shape" transform="translate(364.228,-150.976) rotate(15.5241)">
+ <title>Sheet.10</title>
+ <path d="M0 206.24 L161.1 206.24" class="st6"/>
+ </g>
+ <g id="shape11-52" v:mID="11" v:groupContext="shape" transform="translate(408.029,-95.5)">
+ <title>Sheet.11</title>
+ <path d="M0 206.24 L45 206.24" class="st8"/>
+ </g>
+ <g id="shape12-55" v:mID="12" v:groupContext="shape" transform="translate(48.0294,-50.5)">
+ <title>Sheet.12</title>
+ <desc>H7</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="192.739" width="45" height="27"/>
+ <rect x="0" y="179.239" width="45" height="27" class="st9"/>
+ <text x="13.86" y="198.14" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan
+ dy="-0.284em" class="st11" v:baseFontSize="18">7</tspan></text> </g>
+ <g id="shape13-59" v:mID="13" v:groupContext="shape" transform="translate(192.029,-50.5)">
+ <title>Sheet.13</title>
+ <desc>H267</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="192.739" width="45" height="27"/>
+ <rect x="0" y="179.239" width="45" height="27" class="st9"/>
+ <text x="7.93" y="198.14" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st11" v:baseFontSize="18">267</tspan></text> </g>
+ <g id="shape14-63" v:mID="14" v:groupContext="shape" transform="translate(318.029,-50.5)">
+ <title>Sheet.14</title>
+ <desc>H46</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="192.739" width="45" height="27"/>
+ <rect x="0" y="179.239" width="45" height="27" class="st9"/>
+ <text x="10.89" y="198.14" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan
+ dy="-0.284em" class="st11" v:baseFontSize="18">46</tspan></text> </g>
+ <g id="shape15-67" v:mID="15" v:groupContext="shape" transform="translate(502.529,-50.5)">
+ <title>Sheet.15</title>
+ <desc>H132</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="192.739" width="45" height="27"/>
+ <rect x="0" y="179.239" width="45" height="27" class="st9"/>
+ <text x="7.93" y="198.14" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st11" v:baseFontSize="18">132</tspan></text> </g>
+ <g id="shape16-71" v:mID="16" v:groupContext="shape" transform="translate(111.029,-19)">
+ <title>Sheet.16</title>
+ <desc>Store hash function index for each group of keys</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="189" cy="192.739" width="378" height="27"/>
+ <rect x="0" y="179.239" width="378" height="27" class="st12"/>
+ <text x="12.27" y="198.14" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Store hash function index for each group of keys</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i6.svg b/doc/guides/prog_guide/img/efd_i6.svg
new file mode 100644
index 0000000..9aee30b
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i6.svg
@@ -0,0 +1,1254 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i6.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="8.2496in" height="5.89673in"
+ viewBox="0 0 593.971 424.565" xml:space="preserve" color-interpolation-filters="sRGB" class="st27">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st5 {fill:#feffff;font-family:Calibri;font-size:0.75em}
+ .st6 {fill:none;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st7 {fill:none;stroke:#2e75b5;stroke-width:2.25}
+ .st8 {fill:#305497;stroke:#2e75b5;stroke-width:1}
+ .st9 {fill:#feffff;font-family:Calibri;font-size:0.833336em;font-weight:bold}
+ .st10 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2)}
+ .st11 {fill:#5b9bd5}
+ .st12 {stroke:#c7c8c8;stroke-width:0.25}
+ .st13 {fill:#acccea;stroke:#c7c8c8;stroke-width:0.25}
+ .st14 {fill:#feffff;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st15 {fill:#ed7d31;stroke:#c7c8c8;stroke-width:0.25}
+ .st16 {fill:#deebf6;stroke:#c7c8c8;stroke-width:0.25}
+ .st17 {marker-end:url(#mrkr5-212);stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st18 {fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st19 {fill:none;stroke:#2e75b5;stroke-width:1}
+ .st20 {fill:#5b9bd5;font-family:Calibri;font-size:1.00001em}
+ .st21 {fill:none;stroke:none;stroke-width:0.25}
+ .st22 {font-size:1em}
+ .st23 {fill:#ffffff}
+ .st24 {stroke:#5b9bd5;stroke-dasharray:1.5,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st25 {marker-end:url(#mrkr5-444);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st26 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st27 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-212" class="st18" v:arrowType="5" v:arrowSize="2" v:setback="5.8" refX="-5.8" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ <marker id="mrkr5-444" class="st26" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(319.501,-335.688)">
+ <title>Rectangle.58</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow3-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape4-7" v:mID="4" v:groupContext="shape" transform="translate(353.251,-335.688)">
+ <title>Rectangle.59</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow4-8" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape5-13" v:mID="5" v:groupContext="shape" transform="translate(400.501,-335.688)">
+ <title>Rectangle.60</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow5-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape6-19" v:mID="6" v:groupContext="shape" transform="translate(434.251,-335.688)">
+ <title>Rectangle.61</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow6-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape7-25" v:mID="7" v:groupContext="shape" transform="translate(481.501,-335.688)">
+ <title>Rectangle.62</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow7-26" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape8-30" v:mID="8" v:groupContext="shape" transform="translate(515.251,-335.688)">
+ <title>Rectangle.63</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow8-31" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape9-35" v:mID="9" v:groupContext="shape" transform="translate(319.501,-313.188)">
+ <title>Rectangle.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow9-36" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape10-40" v:mID="10" v:groupContext="shape" transform="translate(353.251,-313.188)">
+ <title>Rectangle.65</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow10-41" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape11-45" v:mID="11" v:groupContext="shape" transform="translate(400.501,-313.188)">
+ <title>Rectangle.66</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow11-46" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape12-50" v:mID="12" v:groupContext="shape" transform="translate(434.251,-313.188)">
+ <title>Rectangle.67</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow12-51" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape13-55" v:mID="13" v:groupContext="shape" transform="translate(481.501,-313.188)">
+ <title>Rectangle.68</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow13-56" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape14-60" v:mID="14" v:groupContext="shape" transform="translate(515.251,-313.188)">
+ <title>Rectangle.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow14-61" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape15-65" v:mID="15" v:groupContext="shape" transform="translate(319.501,-277.188)">
+ <title>Rectangle.70</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow15-66" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape16-71" v:mID="16" v:groupContext="shape" transform="translate(353.251,-277.188)">
+ <title>Rectangle.71</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow16-72" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape17-77" v:mID="17" v:groupContext="shape" transform="translate(400.501,-277.188)">
+ <title>Rectangle.72</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow17-78" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape18-83" v:mID="18" v:groupContext="shape" transform="translate(434.251,-277.188)">
+ <title>Rectangle.73</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow18-84" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape19-89" v:mID="19" v:groupContext="shape" transform="translate(481.501,-277.188)">
+ <title>Rectangle.74</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow19-90" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape20-95" v:mID="20" v:groupContext="shape" transform="translate(515.251,-277.188)">
+ <title>Rectangle.75</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow20-96" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape21-101" v:mID="21" v:groupContext="shape" transform="translate(319.501,-240.687)">
+ <title>Rectangle.76</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow21-102" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape22-106" v:mID="22" v:groupContext="shape" transform="translate(353.251,-240.687)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow22-107" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape23-111" v:mID="23" v:groupContext="shape" transform="translate(400.501,-240.687)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow23-112" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape24-116" v:mID="24" v:groupContext="shape" transform="translate(434.251,-240.687)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow24-117" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape25-121" v:mID="25" v:groupContext="shape" transform="translate(481.501,-240.687)">
+ <title>Rectangle.80</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow25-122" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape26-127" v:mID="26" v:groupContext="shape" transform="translate(515.251,-240.687)">
+ <title>Rectangle.81</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow26-128" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape27-133" v:mID="27" v:groupContext="shape" transform="translate(317.251,-231.687)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow27-134" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st6"/>
+ </g>
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st7"/>
+ </g>
+ <g id="shape28-138" v:mID="28" v:groupContext="shape" transform="translate(328.501,-362.688)">
+ <title>Sheet.28</title>
+ <desc>Local Table for N Specific Flows Serviced at Node 1</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="110.423" cy="418.94" width="220.85" height="11.25"/>
+ <rect x="0" y="413.315" width="220.846" height="11.25" class="st8"/>
+ <text x="5.77" y="421.94" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Local Table for N Specific Flows Serviced at Node 1</text> </g>
+ <g id="group34-141" transform="translate(66.0294,-165.569)" v:mID="34" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Device)"/>
+ <v:cp v:nameU="SubShapeType" v:lbl="SubShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Load balancer)"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Load balancer</title>
+ <g id="shape35-142" v:mID="35" v:groupContext="shape" transform="translate(0,-7.33146)">
+ <title>Sheet.35</title>
+ <g id="shadow35-143" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23 Z" class="st10"/>
+ <path d="M0 377.86 L72 377.86" class="st6"/>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23" class="st6"/>
+ </g>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23 Z" class="st11"/>
+ <path d="M0 377.86 L72 377.86" class="st12"/>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23" class="st12"/>
+ </g>
+ <g id="shape36-152" v:mID="36" v:groupContext="shape" transform="translate(8.03054,-12.9324)">
+ <title>Sheet.36</title>
+ <g id="shadow36-153" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M45.34 421.81 L41.2 422.66 L44.12 424.56 L49.68 423.16 L48.75 417.51 L45.8 415.59 L46.69 419.68 L36.97
+ 413.34 L35.6 415.45 L45.34 421.81 ZM50.83 405.36 L39.2 405.36 L39.2 407.88 L50.8 407.88 L47.82 410.83
+ L51.34 410.83 L55.21 406.61 L51.32 402.39 L47.83 402.39 L50.83 405.36 ZM46.49 392.01 L36.75 398.37
+ L38.13 400.48 L47.84 394.14 L46.96 398.23 L49.91 396.31 L50.84 390.66 L45.28 389.26 L42.36 391.16
+ L46.49 392.01 ZM27.71 397.16 C22.66 397.16 18.58 401.25 18.58 406.29 C18.58 411.33 22.66 415.42
+ 27.71 415.42 C32.75 415.42 36.84 411.33 36.84 406.29 C36.84 401.25 32.75 397.16 27.71 397.16 ZM27.71
+ 400.04 C31.15 400.04 33.96 402.84 33.96 406.29 C33.96 409.74 31.15 412.54 27.71 412.54 C24.26 412.54
+ 21.46 409.74 21.46 406.29 C21.46 402.84 24.26 400.04 27.71 400.04 ZM11.64 405.04 L0 405.04 L0 407.56
+ L11.6 407.56 L8.62 410.51 L12.14 410.51 L16.01 406.29 L12.12 402.07 L8.64 402.07 L11.64 405.04 Z"
+ class="st2"/>
+ </g>
+ <path d="M45.34 421.81 L41.2 422.66 L44.12 424.56 L49.68 423.16 L48.75 417.51 L45.8 415.59 L46.69 419.68 L36.97 413.34
+ L35.6 415.45 L45.34 421.81 ZM50.83 405.36 L39.2 405.36 L39.2 407.88 L50.8 407.88 L47.82 410.83 L51.34
+ 410.83 L55.21 406.61 L51.32 402.39 L47.83 402.39 L50.83 405.36 ZM46.49 392.01 L36.75 398.37 L38.13 400.48
+ L47.84 394.14 L46.96 398.23 L49.91 396.31 L50.84 390.66 L45.28 389.26 L42.36 391.16 L46.49 392.01 ZM27.71
+ 397.16 C22.66 397.16 18.58 401.25 18.58 406.29 C18.58 411.33 22.66 415.42 27.71 415.42 C32.75 415.42
+ 36.84 411.33 36.84 406.29 C36.84 401.25 32.75 397.16 27.71 397.16 ZM27.71 400.04 C31.15 400.04 33.96
+ 402.84 33.96 406.29 C33.96 409.74 31.15 412.54 27.71 412.54 C24.26 412.54 21.46 409.74 21.46 406.29
+ C21.46 402.84 24.26 400.04 27.71 400.04 ZM11.64 405.04 L0 405.04 L0 407.56 L11.6 407.56 L8.62 410.51
+ L12.14 410.51 L16.01 406.29 L12.12 402.07 L8.64 402.07 L11.64 405.04 Z" class="st13"/>
+ </g>
+ </g>
+ <g id="shape37-157" v:mID="37" v:groupContext="shape" transform="translate(21.0294,-45.4375)">
+ <title>Rectangle</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow37-158" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="336.433" width="135" height="88.1315" class="st2"/>
+ </g>
+ <rect x="0" y="336.433" width="135" height="88.1315" class="st3"/>
+ </g>
+ <g id="shape38-162" v:mID="38" v:groupContext="shape" transform="translate(34.693,-126.438)">
+ <title>Sheet.38</title>
+ <desc>EFD Table</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="49.3364" cy="415.565" width="98.68" height="18"/>
+ <rect x="0" y="406.565" width="98.6728" height="18" class="st8"/>
+ <text x="24.87" y="419.17" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>EFD Table</text> </g>
+ <g id="shape39-165" v:mID="39" v:groupContext="shape" transform="translate(30.0294,-99.4375)">
+ <title>Rectangle.39</title>
+ <desc>Group_id</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="26.9182" cy="415.565" width="53.84" height="18"/>
+ <g id="shadow39-166" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st15"/>
+ <text x="7.87" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group_id</text> </g>
+ <g id="shape40-171" v:mID="40" v:groupContext="shape" transform="translate(93.193,-99.4375)">
+ <title>Rectangle.40</title>
+ <desc>Hash index</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="26.9182" cy="415.565" width="53.84" height="18"/>
+ <g id="shadow40-172" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st15"/>
+ <text x="4.64" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Hash index</text> </g>
+ <g id="shape41-177" v:mID="41" v:groupContext="shape" transform="translate(30.193,-82.4275)">
+ <title>Rectangle.41</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow41-178" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape42-182" v:mID="42" v:groupContext="shape" transform="translate(30.193,-66.8125)">
+ <title>Rectangle.42</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow42-183" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape43-187" v:mID="43" v:groupContext="shape" transform="translate(30.1112,-52.1875)">
+ <title>Rectangle.43</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow43-188" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape44-192" v:mID="44" v:groupContext="shape" transform="translate(93.0294,-81.4375)">
+ <title>Rectangle.44</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow44-193" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape45-197" v:mID="45" v:groupContext="shape" transform="translate(93.193,-66.8125)">
+ <title>Rectangle.45</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow45-198" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape46-202" v:mID="46" v:groupContext="shape" transform="translate(93.193,-52.1875)">
+ <title>Rectangle.46</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow46-203" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape47-207" v:mID="47" v:groupContext="shape" transform="translate(374.924,544.022) rotate(135)">
+ <title>Sheet.47</title>
+ <path d="M-0 417.75 A40.674 18.0151 -156.2 0 0 40.24 422.15 L40.49 421.89" class="st17"/>
+ </g>
+ <g id="shape48-213" v:mID="48" v:groupContext="shape" transform="translate(21.0294,-19)">
+ <title>Sheet.48</title>
+ <desc>Supports X*N Flows</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="415.565" width="135" height="18"/>
+ <rect x="0" y="406.565" width="135" height="18" class="st19"/>
+ <text x="19.05" y="419.17" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Supports X*N Flows</text> </g>
+ <g id="shape49-216" v:mID="49" v:groupContext="shape" transform="translate(48.0294,-229.938)">
+ <title>Sheet.49</title>
+ <desc>Frontend Server or Load Balancer</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="400.94" width="108" height="47.25"/>
+ <rect x="0" y="377.315" width="108" height="47.25" class="st21"/>
+ <text x="14.56" y="397.34" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Frontend Server<v:newlineChar/><tspan
+ x="13.16" dy="1.2em" class="st22">or Load Balancer </tspan> </text> </g>
+ <g id="group51-220" transform="translate(223.876,-310.938)" v:mID="51" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server</title>
+ <g id="shape52-221" v:mID="52" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.52</title>
+ <g id="shadow52-222" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape53-226" v:mID="53" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.53</title>
+ <g id="shadow53-227" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape54-231" v:mID="54" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.54</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow54-232" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape59-239" v:mID="59" v:groupContext="shape" transform="translate(277.876,-373.938)">
+ <title>Sheet.59</title>
+ <path d="M-0 424.56 A111.108 53.2538 42.31 0 1 93.83 421.21 L94.14 421.41" class="st17"/>
+ </g>
+ <g id="shape60-244" v:mID="60" v:groupContext="shape" transform="translate(205.876,-283.938)">
+ <title>Sheet.60</title>
+ <desc>Backend Server 1</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.93" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server 1</text> </g>
+ <g id="group61-247" transform="translate(223.876,-207.438)" v:mID="61" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server.61</title>
+ <g id="shape62-248" v:mID="62" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.62</title>
+ <g id="shadow62-249" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape63-253" v:mID="63" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.63</title>
+ <g id="shadow63-254" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape64-258" v:mID="64" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow64-259" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape65-266" v:mID="65" v:groupContext="shape" transform="translate(205.876,-180.437)">
+ <title>Sheet.65</title>
+ <desc>Backend Server 2</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.93" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server 2</text> </g>
+ <g id="group66-269" transform="translate(219.029,-58.9375)" v:mID="66" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server.66</title>
+ <g id="shape67-270" v:mID="67" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.67</title>
+ <g id="shadow67-271" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape68-275" v:mID="68" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.68</title>
+ <g id="shadow68-276" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape69-280" v:mID="69" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow69-281" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape70-288" v:mID="70" v:groupContext="shape" transform="translate(201.029,-26.056)">
+ <title>Sheet.70</title>
+ <desc>Backend Server X</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.86" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server X</text> </g>
+ <g id="shape71-291" v:mID="71" v:groupContext="shape" transform="translate(684.44,239.627) rotate(90)">
+ <title>Sheet.71</title>
+ <path d="M0 424.56 L45 424.56" class="st24"/>
+ </g>
+ <g id="shape72-294" v:mID="72" v:groupContext="shape" transform="translate(6.85967,-22.443) rotate(-38.1076)">
+ <title>Sheet.72</title>
+ <path d="M-0 424.56 A96.1331 44.4001 55.03 0 1 68.24 420.56 L68.51 420.79" class="st17"/>
+ </g>
+ <g id="shape73-299" v:mID="73" v:groupContext="shape" transform="translate(328.501,-135.937)">
+ <title>Rectangle.73</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow73-300" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape74-305" v:mID="74" v:groupContext="shape" transform="translate(362.251,-135.937)">
+ <title>Rectangle.74</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow74-306" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape75-311" v:mID="75" v:groupContext="shape" transform="translate(409.501,-135.937)">
+ <title>Rectangle.75</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow75-312" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape76-317" v:mID="76" v:groupContext="shape" transform="translate(443.251,-135.937)">
+ <title>Rectangle.76</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow76-318" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape77-323" v:mID="77" v:groupContext="shape" transform="translate(490.501,-135.937)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow77-324" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape78-328" v:mID="78" v:groupContext="shape" transform="translate(524.251,-135.937)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow78-329" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape79-333" v:mID="79" v:groupContext="shape" transform="translate(328.501,-113.437)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow79-334" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape80-338" v:mID="80" v:groupContext="shape" transform="translate(362.251,-113.437)">
+ <title>Rectangle.80</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow80-339" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape81-343" v:mID="81" v:groupContext="shape" transform="translate(409.501,-113.437)">
+ <title>Rectangle.81</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow81-344" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape82-348" v:mID="82" v:groupContext="shape" transform="translate(443.251,-113.437)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow82-349" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape83-353" v:mID="83" v:groupContext="shape" transform="translate(490.501,-113.437)">
+ <title>Rectangle.83</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow83-354" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape84-358" v:mID="84" v:groupContext="shape" transform="translate(524.251,-113.437)">
+ <title>Rectangle.84</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow84-359" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape85-363" v:mID="85" v:groupContext="shape" transform="translate(328.501,-77.4375)">
+ <title>Rectangle.85</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow85-364" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape86-369" v:mID="86" v:groupContext="shape" transform="translate(362.251,-77.4375)">
+ <title>Rectangle.86</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow86-370" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape87-375" v:mID="87" v:groupContext="shape" transform="translate(409.501,-77.4375)">
+ <title>Rectangle.87</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow87-376" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape88-381" v:mID="88" v:groupContext="shape" transform="translate(443.251,-77.4375)">
+ <title>Rectangle.88</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow88-382" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape89-387" v:mID="89" v:groupContext="shape" transform="translate(490.501,-77.4375)">
+ <title>Rectangle.89</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow89-388" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape90-393" v:mID="90" v:groupContext="shape" transform="translate(524.251,-77.4375)">
+ <title>Rectangle.90</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow90-394" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape91-399" v:mID="91" v:groupContext="shape" transform="translate(328.501,-40.9375)">
+ <title>Rectangle.91</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow91-400" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape92-404" v:mID="92" v:groupContext="shape" transform="translate(362.251,-40.9375)">
+ <title>Rectangle.92</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow92-405" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape93-409" v:mID="93" v:groupContext="shape" transform="translate(409.501,-40.9375)">
+ <title>Rectangle.93</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow93-410" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape94-414" v:mID="94" v:groupContext="shape" transform="translate(443.251,-40.9375)">
+ <title>Rectangle.94</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow94-415" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape95-419" v:mID="95" v:groupContext="shape" transform="translate(490.501,-40.9375)">
+ <title>Rectangle.95</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow95-420" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape96-425" v:mID="96" v:groupContext="shape" transform="translate(524.251,-40.9375)">
+ <title>Rectangle.96</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow96-426" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape97-431" v:mID="97" v:groupContext="shape" transform="translate(326.251,-31.9375)">
+ <title>Rectangle.97</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow97-432" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st6"/>
+ </g>
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st7"/>
+ </g>
+ <g id="shape98-436" v:mID="98" v:groupContext="shape" transform="translate(337.501,-162.938)">
+ <title>Sheet.98</title>
+ <desc>Local Table for N Specific Flows Serviced at Node X</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="110.423" cy="418.94" width="220.85" height="11.25"/>
+ <rect x="0" y="413.315" width="220.846" height="11.25" class="st8"/>
+ <text x="5.55" y="421.94" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Local Table for N Specific Flows Serviced at Node X</text> </g>
+ <g id="shape99-439" v:mID="99" v:groupContext="shape" transform="translate(-204.342,-29.4449) rotate(-53.7462)">
+ <title>Sheet.99</title>
+ <path d="M0 424.56 L160.37 424.56" class="st25"/>
+ </g>
+ <g id="shape100-445" v:mID="100" v:groupContext="shape" transform="translate(-37.6568,-164.882) rotate(-24.444)">
+ <title>Sheet.100</title>
+ <path d="M0 424.56 L101.71 424.56" class="st25"/>
+ </g>
+ <g id="shape101-450" v:mID="101" v:groupContext="shape" transform="translate(464.049,-50.8578) rotate(50.099)">
+ <title>Sheet.101</title>
+ <path d="M0 424.56 L139.8 424.56" class="st25"/>
+ </g>
+ <g id="shape102-455" v:mID="102" v:groupContext="shape" transform="translate(372.376,-207.438)">
+ <title>Sheet.102</title>
+ <desc>Supports N Flows</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="415.565" width="135" height="18"/>
+ <rect x="0" y="406.565" width="135" height="18" class="st19"/>
+ <text x="25.15" y="419.17" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Supports N Flows</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i7.svg b/doc/guides/prog_guide/img/efd_i7.svg
new file mode 100644
index 0000000..98f8000
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i7.svg
@@ -0,0 +1,790 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i8.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="10.6168in" height="4.81965in"
+ viewBox="0 0 764.409 347.015" xml:space="preserve" color-interpolation-filters="sRGB" class="st27">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st4 {fill:#000000;font-family:Intel Clear;font-size:0.998566em}
+ .st5 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st6 {stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st7 {stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st8 {stroke:#ca8f02;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st9 {stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st10 {fill:#c00000;font-family:Intel Clear;font-size:0.828804em;font-weight:bold}
+ .st11 {fill:#7f6d00;font-family:Intel Clear;font-size:0.828804em;font-weight:bold}
+ .st12 {fill:#00b050;stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st13 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st14 {fill:#00b050;stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st15 {fill:#ca8f02;stroke:#ca8f02;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st16 {fill:#004280;font-family:Intel Clear;font-size:0.828804em}
+ .st17 {fill:#ffffff;font-family:Intel Clear;font-size:0.998566em}
+ .st18 {fill:#ffffff;font-family:Intel Clear;font-size:1.49785em}
+ .st19 {visibility:visible}
+ .st20 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st21 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st22 {fill:#feffff;font-family:Symbol;font-size:1.16666em}
+ .st23 {font-size:1em}
+ .st24 {font-family:Calibri;font-size:1em}
+ .st25 {fill:none;stroke:none;stroke-width:0.25}
+ .st26 {fill:#ffffff;font-family:Calibri;font-size:1.00001em}
+ .st27 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(27.7836,-307.505)">
+ <title>Sheet.3</title>
+ <path d="M0 329.94 C-0 328.06 1.54 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.06 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.54 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape4-3" v:mID="4" v:groupContext="shape" transform="translate(27.7836,-307.505)">
+ <title>Sheet.4</title>
+ <path d="M0 329.94 C-0 328.06 1.54 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.06 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.54 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape5-5" v:mID="5" v:groupContext="shape" transform="translate(50.1544,-309.121)">
+ <title>Sheet.5</title>
+ <desc>Key1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1</text> </g>
+ <g id="shape6-9" v:mID="6" v:groupContext="shape" transform="translate(43.6909,-286.954)">
+ <title>Sheet.6</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape7-11" v:mID="7" v:groupContext="shape" transform="translate(27.7836,-266.044)">
+ <title>Sheet.7</title>
+ <path d="M0 330.04 C0 328.16 1.52 326.64 3.41 326.64 L68.51 326.64 C70.4 326.64 71.91 328.16 71.91 330.04 L71.91 343.62
+ C71.91 345.49 70.4 347.02 68.51 347.02 L3.41 347.02 C1.52 347.02 0 345.49 0 343.62 L0 330.04 Z"
+ class="st1"/>
+ </g>
+ <g id="shape8-13" v:mID="8" v:groupContext="shape" transform="translate(27.7836,-266.044)">
+ <title>Sheet.8</title>
+ <path d="M0 330.04 C0 328.16 1.52 326.64 3.41 326.64 L68.51 326.64 C70.4 326.64 71.91 328.16 71.91 330.04 L71.91 343.62
+ C71.91 345.49 70.4 347.02 68.51 347.02 L3.41 347.02 C1.52 347.02 0 345.49 0 343.62 L0 330.04 Z"
+ class="st2"/>
+ </g>
+ <g id="shape9-15" v:mID="9" v:groupContext="shape" transform="translate(50.7572,-267.602)">
+ <title>Sheet.9</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape10-19" v:mID="10" v:groupContext="shape" transform="translate(19.0195,-225.183)">
+ <title>Sheet.10</title>
+ <path d="M0 330.74 C0 328.94 1.46 327.48 3.26 327.48 L87.15 327.48 C88.95 327.48 90.4 328.94 90.4 330.74 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.26 347.02 C1.46 347.02 0 345.56 0 343.76 L0 330.74 Z"
+ class="st1"/>
+ </g>
+ <g id="shape11-21" v:mID="11" v:groupContext="shape" transform="translate(19.0195,-225.183)">
+ <title>Sheet.11</title>
+ <path d="M0 330.74 C0 328.94 1.46 327.48 3.26 327.48 L87.15 327.48 C88.95 327.48 90.4 328.94 90.4 330.74 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.26 347.02 C1.46 347.02 0 345.56 0 343.76 L0 330.74 Z"
+ class="st2"/>
+ </g>
+ <g id="shape12-23" v:mID="12" v:groupContext="shape" transform="translate(28.0373,-226.287)">
+ <title>Sheet.12</title>
+ <desc>0x0102ABCD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.3615" cy="339.824" width="86.73" height="14.3829"/>
+ <path d="M86.72 332.63 L0 332.63 L0 347.02 L86.72 347.02 L86.72 332.63" class="st3"/>
+ <text x="7.12" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102ABCD</text> </g>
+ <g id="shape13-27" v:mID="13" v:groupContext="shape" transform="translate(43.6909,-244.775)">
+ <title>Sheet.13</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape14-29" v:mID="14" v:groupContext="shape" transform="translate(40.7496,-210.444)">
+ <title>Sheet.14</title>
+ <path d="M26.29 334.91 C26.29 338.26 25.84 340.96 25.29 340.96 L14.16 340.96 C13.6 340.96 13.15 343.67 13.15 347.02 C13.15
+ 343.67 12.7 340.96 12.14 340.96 L1.01 340.96 C0.46 340.96 0 338.26 0 334.91" class="st6"/>
+ </g>
+ <g id="shape15-32" v:mID="15" v:groupContext="shape" transform="translate(125.629,-307.625)">
+ <title>Sheet.15</title>
+ <path d="M0 330.04 C0 328.16 1.52 326.64 3.41 326.64 L68.63 326.64 C70.51 326.64 72.03 328.16 72.03 330.04 L72.03 343.62
+ C72.03 345.49 70.51 347.02 68.63 347.02 L3.41 347.02 C1.52 347.02 0 345.49 0 343.62 L0 330.04 Z"
+ class="st1"/>
+ </g>
+ <g id="shape16-34" v:mID="16" v:groupContext="shape" transform="translate(125.629,-307.625)">
+ <title>Sheet.16</title>
+ <path d="M0 330.04 C0 328.16 1.52 326.64 3.41 326.64 L68.63 326.64 C70.51 326.64 72.03 328.16 72.03 330.04 L72.03 343.62
+ C72.03 345.49 70.51 347.02 68.63 347.02 L3.41 347.02 C1.52 347.02 0 345.49 0 343.62 L0 330.04 Z"
+ class="st2"/>
+ </g>
+ <g id="shape17-36" v:mID="17" v:groupContext="shape" transform="translate(148.034,-309.155)">
+ <title>Sheet.17</title>
+ <desc>Key2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key2</text> </g>
+ <g id="shape18-40" v:mID="18" v:groupContext="shape" transform="translate(141.536,-286.954)">
+ <title>Sheet.18</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape19-42" v:mID="19" v:groupContext="shape" transform="translate(125.629,-266.044)">
+ <title>Sheet.19</title>
+ <path d="M0 329.94 C0 328.06 1.54 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.06 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.54 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st1"/>
+ </g>
+ <g id="shape20-44" v:mID="20" v:groupContext="shape" transform="translate(125.629,-266.044)">
+ <title>Sheet.20</title>
+ <path d="M0 329.94 C0 328.06 1.54 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.06 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.54 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st2"/>
+ </g>
+ <g id="shape21-46" v:mID="21" v:groupContext="shape" transform="translate(148.636,-267.636)">
+ <title>Sheet.21</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape22-50" v:mID="22" v:groupContext="shape" transform="translate(116.865,-225.183)">
+ <title>Sheet.22</title>
+ <path d="M0 330.74 C0 328.94 1.46 327.48 3.26 327.48 L87.15 327.48 C88.95 327.48 90.4 328.94 90.4 330.74 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.26 347.02 C1.46 347.02 0 345.56 0 343.76 L0 330.74 Z"
+ class="st1"/>
+ </g>
+ <g id="shape23-52" v:mID="23" v:groupContext="shape" transform="translate(116.865,-225.183)">
+ <title>Sheet.23</title>
+ <path d="M0 330.74 C0 328.94 1.46 327.48 3.26 327.48 L87.15 327.48 C88.95 327.48 90.4 328.94 90.4 330.74 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.26 347.02 C1.46 347.02 0 345.56 0 343.76 L0 330.74 Z"
+ class="st2"/>
+ </g>
+ <g id="shape24-54" v:mID="24" v:groupContext="shape" transform="translate(125.917,-226.322)">
+ <title>Sheet.24</title>
+ <desc>0x0103CDAB</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.3615" cy="339.824" width="86.73" height="14.3829"/>
+ <path d="M86.72 332.63 L0 332.63 L0 347.02 L86.72 347.02 L86.72 332.63" class="st3"/>
+ <text x="7.12" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0103CDAB</text> </g>
+ <g id="shape25-58" v:mID="25" v:groupContext="shape" transform="translate(141.536,-244.775)">
+ <title>Sheet.25</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape26-60" v:mID="26" v:groupContext="shape" transform="translate(138.595,-210.444)">
+ <title>Sheet.26</title>
+ <path d="M26.29 334.91 C26.29 338.26 25.84 340.96 25.29 340.96 L14.16 340.96 C13.6 340.96 13.15 343.67 13.15 347.02 C13.15
+ 343.67 12.7 340.96 12.14 340.96 L1.01 340.96 C0.46 340.96 0 338.26 0 334.91" class="st7"/>
+ </g>
+ <g id="shape27-63" v:mID="27" v:groupContext="shape" transform="translate(221.793,-307.625)">
+ <title>Sheet.27</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.64 326.64 C70.52 326.64 72.03 328.17 72.03 330.04 L72.03 343.63
+ C72.03 345.5 70.52 347.02 68.64 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st1"/>
+ </g>
+ <g id="shape28-65" v:mID="28" v:groupContext="shape" transform="translate(221.793,-307.625)">
+ <title>Sheet.28</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.64 326.64 C70.52 326.64 72.03 328.17 72.03 330.04 L72.03 343.63
+ C72.03 345.5 70.52 347.02 68.64 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st2"/>
+ </g>
+ <g id="shape29-67" v:mID="29" v:groupContext="shape" transform="translate(244.237,-309.155)">
+ <title>Sheet.29</title>
+ <desc>Key3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3</text> </g>
+ <g id="shape30-71" v:mID="30" v:groupContext="shape" transform="translate(237.701,-286.954)">
+ <title>Sheet.30</title>
+ <path d="M0 336.65 L9.84 336.65 L9.84 326.28 L29.53 326.28 L29.53 336.65 L39.38 336.65 L19.69 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape31-73" v:mID="31" v:groupContext="shape" transform="translate(221.793,-266.044)">
+ <title>Sheet.31</title>
+ <path d="M0 329.94 C-0 328.07 1.55 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.07 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st1"/>
+ </g>
+ <g id="shape32-75" v:mID="32" v:groupContext="shape" transform="translate(221.793,-266.044)">
+ <title>Sheet.32</title>
+ <path d="M0 329.94 C-0 328.07 1.55 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.07 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st2"/>
+ </g>
+ <g id="shape33-77" v:mID="33" v:groupContext="shape" transform="translate(244.84,-267.636)">
+ <title>Sheet.33</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape34-81" v:mID="34" v:groupContext="shape" transform="translate(213.029,-225.183)">
+ <title>Sheet.34</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.27 327.48 C89.07 327.48 90.52 328.95 90.52 330.75 L90.52 343.76
+ C90.52 345.56 89.07 347.02 87.27 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape35-83" v:mID="35" v:groupContext="shape" transform="translate(213.029,-225.183)">
+ <title>Sheet.35</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.27 327.48 C89.07 327.48 90.52 328.95 90.52 330.75 L90.52 343.76
+ C90.52 345.56 89.07 347.02 87.27 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape36-85" v:mID="36" v:groupContext="shape" transform="translate(222.002,-226.322)">
+ <title>Sheet.36</title>
+ <desc>0x0102BAAD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.4787" cy="339.824" width="86.96" height="14.3829"/>
+ <path d="M86.96 332.63 L0 332.63 L0 347.02 L86.96 347.02 L86.96 332.63" class="st3"/>
+ <text x="7.13" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102BAAD</text> </g>
+ <g id="shape37-89" v:mID="37" v:groupContext="shape" transform="translate(237.701,-244.775)">
+ <title>Sheet.37</title>
+ <path d="M0 336.71 L9.84 336.71 L9.84 326.4 L29.53 326.4 L29.53 336.71 L39.38 336.71 L19.69 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape38-91" v:mID="38" v:groupContext="shape" transform="translate(234.759,-210.444)">
+ <title>Sheet.38</title>
+ <path d="M26.41 334.91 C26.41 338.26 25.96 340.96 25.41 340.96 L14.22 340.96 C13.66 340.96 13.21 343.67 13.21 347.02
+ C13.21 343.67 12.76 340.96 12.2 340.96 L1.01 340.96 C0.46 340.96 0 338.26 0 334.91" class="st6"/>
+ </g>
+ <g id="shape39-94" v:mID="39" v:groupContext="shape" transform="translate(319.759,-307.625)">
+ <title>Sheet.39</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.52 326.64 C70.4 326.64 71.91 328.17 71.91 330.04 L71.91 343.63
+ C71.91 345.5 70.4 347.02 68.52 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st1"/>
+ </g>
+ <g id="shape40-96" v:mID="40" v:groupContext="shape" transform="translate(319.759,-307.625)">
+ <title>Sheet.40</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.52 326.64 C70.4 326.64 71.91 328.17 71.91 330.04 L71.91 343.63
+ C71.91 345.5 70.4 347.02 68.52 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st2"/>
+ </g>
+ <g id="shape41-98" v:mID="41" v:groupContext="shape" transform="translate(342.125,-309.155)">
+ <title>Sheet.41</title>
+ <desc>Key4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4</text> </g>
+ <g id="shape42-102" v:mID="42" v:groupContext="shape" transform="translate(335.666,-286.954)">
+ <title>Sheet.42</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape43-104" v:mID="43" v:groupContext="shape" transform="translate(319.759,-266.044)">
+ <title>Sheet.43</title>
+ <path d="M0 329.94 C0 328.07 1.55 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.07 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape44-106" v:mID="44" v:groupContext="shape" transform="translate(319.759,-266.044)">
+ <title>Sheet.44</title>
+ <path d="M0 329.94 C0 328.07 1.55 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.07 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape45-108" v:mID="45" v:groupContext="shape" transform="translate(342.728,-267.636)">
+ <title>Sheet.45</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape46-112" v:mID="46" v:groupContext="shape" transform="translate(310.995,-225.183)">
+ <title>Sheet.46</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.15 327.48 C88.95 327.48 90.4 328.95 90.4 330.75 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape47-114" v:mID="47" v:groupContext="shape" transform="translate(310.995,-225.183)">
+ <title>Sheet.47</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.15 327.48 C88.95 327.48 90.4 328.95 90.4 330.75 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape48-116" v:mID="48" v:groupContext="shape" transform="translate(321.689,-226.322)">
+ <title>Sheet.48</title>
+ <desc>0x0104BEEF</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="41.4183" cy="339.824" width="82.84" height="14.3829"/>
+ <path d="M82.84 332.63 L0 332.63 L0 347.02 L82.84 347.02 L82.84 332.63" class="st3"/>
+ <text x="6.87" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0104BEEF</text> </g>
+ <g id="shape49-120" v:mID="49" v:groupContext="shape" transform="translate(335.666,-244.775)">
+ <title>Sheet.49</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape50-122" v:mID="50" v:groupContext="shape" transform="translate(332.725,-210.444)">
+ <title>Sheet.50</title>
+ <path d="M26.29 334.91 C26.29 338.27 25.84 340.96 25.29 340.96 L14.17 340.96 C13.61 340.96 13.15 343.67 13.15 347.02
+ C13.15 343.67 12.7 340.96 12.14 340.96 L1.02 340.96 C0.47 340.96 0 338.27 0 334.91" class="st6"/>
+ </g>
+ <g id="shape51-125" v:mID="51" v:groupContext="shape" transform="translate(416.884,-307.625)">
+ <title>Sheet.51</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.52 326.64 C70.4 326.64 71.91 328.17 71.91 330.04 L71.91 343.63
+ C71.91 345.5 70.4 347.02 68.52 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st1"/>
+ </g>
+ <g id="shape52-127" v:mID="52" v:groupContext="shape" transform="translate(416.884,-307.625)">
+ <title>Sheet.52</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.52 326.64 C70.4 326.64 71.91 328.17 71.91 330.04 L71.91 343.63
+ C71.91 345.5 70.4 347.02 68.52 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st2"/>
+ </g>
+ <g id="shape53-129" v:mID="53" v:groupContext="shape" transform="translate(439.255,-309.155)">
+ <title>Sheet.53</title>
+ <desc>Key5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key5</text> </g>
+ <g id="shape54-133" v:mID="54" v:groupContext="shape" transform="translate(432.791,-286.954)">
+ <title>Sheet.54</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape55-135" v:mID="55" v:groupContext="shape" transform="translate(416.884,-266.044)">
+ <title>Sheet.55</title>
+ <path d="M0 329.94 C0 328.07 1.55 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.07 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape56-137" v:mID="56" v:groupContext="shape" transform="translate(416.884,-266.044)">
+ <title>Sheet.56</title>
+ <path d="M0 329.94 C0 328.07 1.55 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.07 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape57-139" v:mID="57" v:groupContext="shape" transform="translate(439.858,-267.636)">
+ <title>Sheet.57</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape58-143" v:mID="58" v:groupContext="shape" transform="translate(408.12,-225.183)">
+ <title>Sheet.58</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.15 327.48 C88.95 327.48 90.4 328.95 90.4 330.75 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape59-145" v:mID="59" v:groupContext="shape" transform="translate(408.12,-225.183)">
+ <title>Sheet.59</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.15 327.48 C88.95 327.48 90.4 328.95 90.4 330.75 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape60-147" v:mID="60" v:groupContext="shape" transform="translate(416.778,-226.322)">
+ <title>Sheet.60</title>
+ <desc>0x0103DABD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.7817" cy="339.824" width="87.57" height="14.3829"/>
+ <path d="M87.56 332.63 L0 332.63 L0 347.02 L87.56 347.02 L87.56 332.63" class="st3"/>
+ <text x="7.17" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0103DABD</text> </g>
+ <g id="shape61-151" v:mID="61" v:groupContext="shape" transform="translate(432.791,-244.775)">
+ <title>Sheet.61</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape62-153" v:mID="62" v:groupContext="shape" transform="translate(429.85,-210.444)">
+ <title>Sheet.62</title>
+ <path d="M26.29 334.91 C26.29 338.27 25.84 340.96 25.29 340.96 L14.17 340.96 C13.61 340.96 13.15 343.67 13.15 347.02
+ C13.15 343.67 12.7 340.96 12.14 340.96 L1.02 340.96 C0.47 340.96 0 338.27 0 334.91" class="st7"/>
+ </g>
+ <g id="shape63-156" v:mID="63" v:groupContext="shape" transform="translate(514.489,-307.625)">
+ <title>Sheet.63</title>
+ <path d="M0 330.06 C-0 328.17 1.53 326.64 3.42 326.64 L68.64 326.64 C70.53 326.64 72.03 328.17 72.03 330.06 L72.03 343.63
+ C72.03 345.52 70.53 347.02 68.64 347.02 L3.42 347.02 C1.53 347.02 0 345.52 0 343.63 L0 330.06 Z"
+ class="st1"/>
+ </g>
+ <g id="shape64-158" v:mID="64" v:groupContext="shape" transform="translate(514.489,-307.625)">
+ <title>Sheet.64</title>
+ <path d="M0 330.06 C-0 328.17 1.53 326.64 3.42 326.64 L68.64 326.64 C70.53 326.64 72.03 328.17 72.03 330.06 L72.03 343.63
+ C72.03 345.52 70.53 347.02 68.64 347.02 L3.42 347.02 C1.53 347.02 0 345.52 0 343.63 L0 330.06 Z"
+ class="st2"/>
+ </g>
+ <g id="shape65-160" v:mID="65" v:groupContext="shape" transform="translate(536.883,-309.19)">
+ <title>Sheet.65</title>
+ <desc>Key6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key6</text> </g>
+ <g id="shape66-164" v:mID="66" v:groupContext="shape" transform="translate(530.396,-287.074)">
+ <title>Sheet.66</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape67-166" v:mID="67" v:groupContext="shape" transform="translate(514.489,-266.044)">
+ <title>Sheet.67</title>
+ <path d="M0 329.94 C0 328.08 1.56 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.08 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.56 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st1"/>
+ </g>
+ <g id="shape68-168" v:mID="68" v:groupContext="shape" transform="translate(514.489,-266.044)">
+ <title>Sheet.68</title>
+ <path d="M0 329.94 C0 328.08 1.56 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.08 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.56 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st2"/>
+ </g>
+ <g id="shape69-170" v:mID="69" v:groupContext="shape" transform="translate(537.486,-267.671)">
+ <title>Sheet.69</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape70-174" v:mID="70" v:groupContext="shape" transform="translate(505.725,-225.183)">
+ <title>Sheet.70</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.16 327.48 C88.96 327.48 90.4 328.95 90.4 330.75 L90.4 343.78
+ C90.4 345.58 88.96 347.02 87.16 347.02 L3.27 347.02 C1.47 347.02 0 345.58 0 343.78 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape71-176" v:mID="71" v:groupContext="shape" transform="translate(505.725,-225.183)">
+ <title>Sheet.71</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.16 327.48 C88.96 327.48 90.4 328.95 90.4 330.75 L90.4 343.78
+ C90.4 345.58 88.96 347.02 87.16 347.02 L3.27 347.02 C1.47 347.02 0 345.58 0 343.78 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape72-178" v:mID="72" v:groupContext="shape" transform="translate(514.766,-226.356)">
+ <title>Sheet.72</title>
+ <desc>0x0102ADCB</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.3615" cy="339.824" width="86.73" height="14.3829"/>
+ <path d="M86.72 332.63 L0 332.63 L0 347.02 L86.72 347.02 L86.72 332.63" class="st3"/>
+ <text x="7.12" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102ADCB</text> </g>
+ <g id="shape73-182" v:mID="73" v:groupContext="shape" transform="translate(530.396,-244.775)">
+ <title>Sheet.73</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape74-184" v:mID="74" v:groupContext="shape" transform="translate(527.455,-210.564)">
+ <title>Sheet.74</title>
+ <path d="M26.29 335.03 C26.29 338.36 25.87 341.02 25.3 341.02 L14.17 341.02 C13.6 341.02 13.15 343.72 13.15 347.02 C13.15
+ 343.72 12.73 341.02 12.16 341.02 L1.02 341.02 C0.45 341.02 0 338.36 0 335.03" class="st6"/>
+ </g>
+ <g id="shape75-187" v:mID="75" v:groupContext="shape" transform="translate(610.653,-307.505)">
+ <title>Sheet.75</title>
+ <path d="M0 329.94 C0 328.08 1.56 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.08 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.56 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st1"/>
+ </g>
+ <g id="shape76-189" v:mID="76" v:groupContext="shape" transform="translate(610.653,-307.505)">
+ <title>Sheet.76</title>
+ <path d="M0 329.94 C0 328.08 1.56 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.08 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.56 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st2"/>
+ </g>
+ <g id="shape77-191" v:mID="77" v:groupContext="shape" transform="translate(633.086,-309.121)">
+ <title>Sheet.77</title>
+ <desc>Key7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7</text> </g>
+ <g id="shape78-195" v:mID="78" v:groupContext="shape" transform="translate(626.561,-286.954)">
+ <title>Sheet.78</title>
+ <path d="M0 336.65 L9.84 336.65 L9.84 326.28 L29.53 326.28 L29.53 336.65 L39.38 336.65 L19.69 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape79-197" v:mID="79" v:groupContext="shape" transform="translate(610.653,-266.044)">
+ <title>Sheet.79</title>
+ <path d="M0 330.06 C-0 328.17 1.53 326.64 3.42 326.64 L68.64 326.64 C70.53 326.64 72.03 328.17 72.03 330.06 L72.03 343.63
+ C72.03 345.52 70.53 347.02 68.64 347.02 L3.42 347.02 C1.53 347.02 0 345.52 0 343.63 L0 330.06 Z"
+ class="st1"/>
+ </g>
+ <g id="shape80-199" v:mID="80" v:groupContext="shape" transform="translate(610.653,-266.044)">
+ <title>Sheet.80</title>
+ <path d="M0 330.06 C-0 328.17 1.53 326.64 3.42 326.64 L68.64 326.64 C70.53 326.64 72.03 328.17 72.03 330.06 L72.03 343.63
+ C72.03 345.52 70.53 347.02 68.64 347.02 L3.42 347.02 C1.53 347.02 0 345.52 0 343.63 L0 330.06 Z"
+ class="st2"/>
+ </g>
+ <g id="shape81-201" v:mID="81" v:groupContext="shape" transform="translate(633.689,-267.602)">
+ <title>Sheet.81</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape82-205" v:mID="82" v:groupContext="shape" transform="translate(601.889,-225.183)">
+ <title>Sheet.82</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.28 327.48 C89.08 327.48 90.52 328.95 90.52 330.75 L90.52 343.78
+ C90.52 345.58 89.08 347.02 87.28 347.02 L3.27 347.02 C1.47 347.02 0 345.58 0 343.78 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape83-207" v:mID="83" v:groupContext="shape" transform="translate(601.889,-225.183)">
+ <title>Sheet.83</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.28 327.48 C89.08 327.48 90.52 328.95 90.52 330.75 L90.52 343.78
+ C90.52 345.58 89.08 347.02 87.28 347.02 L3.27 347.02 C1.47 347.02 0 345.58 0 343.78 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape84-209" v:mID="84" v:groupContext="shape" transform="translate(610.969,-226.287)">
+ <title>Sheet.84</title>
+ <desc>0x0104DBCA</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.3615" cy="339.824" width="86.73" height="14.3829"/>
+ <path d="M86.72 332.63 L0 332.63 L0 347.02 L86.72 347.02 L86.72 332.63" class="st3"/>
+ <text x="7.12" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0104DBCA</text> </g>
+ <g id="shape85-213" v:mID="85" v:groupContext="shape" transform="translate(626.561,-244.775)">
+ <title>Sheet.85</title>
+ <path d="M0 336.71 L9.84 336.71 L9.84 326.4 L29.53 326.4 L29.53 336.71 L39.38 336.71 L19.69 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape86-215" v:mID="86" v:groupContext="shape" transform="translate(623.619,-210.444)">
+ <title>Sheet.86</title>
+ <path d="M26.41 334.91 C26.41 338.27 25.96 340.96 25.42 340.96 L14.23 340.96 C13.69 340.96 13.21 343.69 13.21 347.02
+ C13.21 343.69 12.76 340.96 12.22 340.96 L1.02 340.96 C0.48 340.96 0 338.27 0 334.91" class="st8"/>
+ </g>
+ <g id="shape87-218" v:mID="87" v:groupContext="shape" transform="translate(242.323,-81.6288)">
+ <title>Sheet.87</title>
+ <path d="M0 281.23 L0 347.02 L41.18 347.02 L41.18 281.23 L0 281.23 L0 281.23 Z" class="st1"/>
+ </g>
+ <g id="shape88-220" v:mID="88" v:groupContext="shape" transform="translate(247.009,-81.6288)">
+ <title>Sheet.88</title>
+ <path d="M0 281.23 L41.18 281.23 L41.18 347.02 L0 347.02 L0 281.23" class="st9"/>
+ </g>
+ <g id="shape89-223" v:mID="89" v:groupContext="shape" transform="translate(245.254,-132.398)">
+ <title>Sheet.89</title>
+ <desc>0x0102</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.3211" cy="341.046" width="42.65" height="11.9384"/>
+ <path d="M42.64 335.08 L0 335.08 L0 347.02 L42.64 347.02 L42.64 335.08" class="st3"/>
+ <text x="4" y="344.03" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102</text> </g>
+ <g id="shape90-227" v:mID="90" v:groupContext="shape" transform="translate(245.015,-82.7016)">
+ <title>Sheet.90</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.79425" cy="341.046" width="9.59" height="11.9384"/>
+ <path d="M9.59 335.08 L0 335.08 L0 347.02 L9.59 347.02 L9.59 335.08" class="st3"/>
+ <text x="1.84" y="344.03" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape91-231" v:mID="91" v:groupContext="shape" transform="translate(336.326,-81.6288)">
+ <title>Sheet.91</title>
+ <path d="M0 281.23 L0 347.02 L41.18 347.02 L41.18 281.23 L0 281.23 L0 281.23 Z" class="st1"/>
+ </g>
+ <g id="shape92-233" v:mID="92" v:groupContext="shape" transform="translate(339.598,-81.6288)">
+ <title>Sheet.92</title>
+ <path d="M0 281.23 L41.18 281.23 L41.18 347.02 L0 347.02 L0 281.23" class="st9"/>
+ </g>
+ <g id="shape93-236" v:mID="93" v:groupContext="shape" transform="translate(339.264,-132.398)">
+ <title>Sheet.93</title>
+ <desc>0x0103</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.3211" cy="341.046" width="42.65" height="11.9384"/>
+ <path d="M42.64 335.08 L0 335.08 L0 347.02 L42.64 347.02 L42.64 335.08" class="st3"/>
+ <text x="4" y="344.03" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0103</text> </g>
+ <g id="shape94-240" v:mID="94" v:groupContext="shape" transform="translate(339.024,-82.7016)">
+ <title>Sheet.94</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.79425" cy="341.046" width="9.59" height="11.9384"/>
+ <path d="M9.59 335.08 L0 335.08 L0 347.02 L9.59 347.02 L9.59 335.08" class="st3"/>
+ <text x="1.84" y="344.03" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape95-244" v:mID="95" v:groupContext="shape" transform="translate(438.598,-81.5089)">
+ <title>Sheet.95</title>
+ <path d="M0 281.23 L0 347.02 L41.18 347.02 L41.18 281.23 L0 281.23 L0 281.23 Z" class="st1"/>
+ </g>
+ <g id="shape96-246" v:mID="96" v:groupContext="shape" transform="translate(438.598,-81.5089)">
+ <title>Sheet.96</title>
+ <path d="M0 281.23 L41.18 281.23 L41.18 347.02 L0 347.02 L0 281.23" class="st9"/>
+ </g>
+ <g id="shape97-249" v:mID="97" v:groupContext="shape" transform="translate(437.81,-132.27)">
+ <title>Sheet.97</title>
+ <desc>0x0104</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.3211" cy="341.046" width="42.65" height="11.9384"/>
+ <path d="M42.64 335.08 L0 335.08 L0 347.02 L42.64 347.02 L42.64 335.08" class="st3"/>
+ <text x="4" y="344.03" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0104</text> </g>
+ <g id="shape98-253" v:mID="98" v:groupContext="shape" transform="translate(437.57,-82.5735)">
+ <title>Sheet.98</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.79425" cy="341.046" width="9.59" height="11.9384"/>
+ <path d="M9.59 335.08 L0 335.08 L0 347.02 L9.59 347.02 L9.59 335.08" class="st3"/>
+ <text x="1.84" y="344.03" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape99-257" v:mID="99" v:groupContext="shape" transform="translate(53.5505,-147.924)">
+ <title>Sheet.99</title>
+ <path d="M0.59 283.52 L206.27 343.39 L205.7 345.34 L0 285.48 L0.59 283.52 L0.59 283.52 ZM205.85 341.14 L210.88 345.79
+ L204.14 347.02 L205.85 341.14 L205.85 341.14 Z" class="st12"/>
+ </g>
+ <g id="shape100-259" v:mID="100" v:groupContext="shape" transform="translate(151.516,-147.924)">
+ <title>Sheet.100</title>
+ <path d="M0.59 283.52 L202.41 343.41 L201.83 345.35 L0 285.48 L0.59 283.52 L0.59 283.52 ZM202.01 341.16 L207.01 345.83
+ L200.27 347.02 L202.01 341.16 L202.01 341.16 Z" class="st13"/>
+ </g>
+ <g id="shape101-261" v:mID="101" v:groupContext="shape" transform="translate(246.975,-147.37)">
+ <title>Sheet.101</title>
+ <path d="M2 283.72 L15.77 341.83 L13.79 342.3 L0 284.18 L2 283.72 L2 283.72 ZM17.53 340.36 L15.97 347.02 L11.57 341.77
+ L17.53 340.36 L17.53 340.36 Z" class="st12"/>
+ </g>
+ <g id="shape102-263" v:mID="102" v:groupContext="shape" transform="translate(262.972,-147.37)">
+ <title>Sheet.102</title>
+ <path d="M82.31 283.13 L3.45 343.12 L4.68 344.74 L83.54 284.76 L82.31 283.13 L82.31 283.13 ZM3.02 340.89 L0 347.02 L6.74
+ 345.74 L3.02 340.89 L3.02 340.89 Z" class="st12"/>
+ </g>
+ <g id="shape103-265" v:mID="103" v:groupContext="shape" transform="translate(358.537,-149.107)">
+ <title>Sheet.103</title>
+ <path d="M83.92 284.85 L3.53 343.2 L4.73 344.84 L85.12 286.5 L83.92 284.85 L83.92 284.85 ZM3.15 340.95 L0 347.02 L6.75
+ 345.89 L3.15 340.95 L3.15 340.95 Z" class="st13"/>
+ </g>
+ <g id="shape104-267" v:mID="104" v:groupContext="shape" transform="translate(264.413,-147.534)">
+ <title>Sheet.104</title>
+ <path d="M275.95 283 L4.77 343.27 L5.22 345.25 L276.37 285 L275.95 283 L275.95 283 ZM5.31 341.05 L0 345.37 L6.66 347.02
+ L5.31 341.05 L5.31 341.05 Z" class="st14"/>
+ </g>
+ <g id="shape105-269" v:mID="105" v:groupContext="shape" transform="translate(456.982,-148.103)">
+ <title>Sheet.105</title>
+ <path d="M179.48 283.72 L4.5 343.48 L5.16 345.43 L180.14 285.66 L179.48 283.72 L179.48 283.72 ZM4.8 341.23 L0 346.12
+ L6.81 347.02 L4.8 341.23 L4.8 341.23 Z" class="st15"/>
+ </g>
+ <g id="shape106-271" v:mID="106" v:groupContext="shape" transform="translate(335.628,-18)">
+ <title>Sheet.106</title>
+ <path d="M0 309.64 C0 305.52 2.99 302.16 6.65 302.16 L14.2 302.16 L8.01 284.85 L35.48 302.16 L78.47 302.16 C82.15 302.16
+ 85.12 305.52 85.12 309.64 L85.12 309.64 L85.12 320.85 L85.12 339.54 C85.12 343.68 82.15 347.02 78.47 347.02
+ L35.48 347.02 L14.2 347.02 L14.2 347.02 L6.65 347.02 C2.99 347.02 0 343.68 0 339.54 L0 320.85 L0 309.64
+ L0 309.64 Z" class="st5"/>
+ </g>
+ <g id="shape109-273" v:mID="109" v:groupContext="shape" transform="translate(157.564,-62.4234)">
+ <title>Sheet.109</title>
+ <path d="M16.21 347.02 C11.74 347.02 8.1 346.42 8.1 345.67 L8.1 303.49 C8.1 302.75 4.49 302.14 0 302.14 C4.49 302.14
+ 8.1 301.54 8.1 300.79 L8.1 258.61 C8.1 257.88 11.74 257.26 16.21 257.26" class="st7"/>
+ </g>
+ <g id="shape110-276" v:mID="110" v:groupContext="shape" transform="translate(113.844,-100.157)">
+ <title>Sheet.110</title>
+ <desc>Groups</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="20.2175" cy="341.046" width="40.44" height="11.9384"/>
+ <path d="M40.44 335.08 L0 335.08 L0 347.02 L40.44 347.02 L40.44 335.08" class="st3"/>
+ <text x="3.85" y="344.03" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Groups</text> </g>
+ <g id="shape111-280" v:mID="111" v:groupContext="shape" transform="translate(196.718,-76.2186)">
+ <title>Sheet.111</title>
+ <path d="M0 331.97 C0 330.32 2.27 328.96 5.04 328.96 L37.61 328.96 L60.43 284.85 L53.72 328.96 L59.43 328.96 C62.22 328.96
+ 64.47 330.32 64.47 331.97 L64.47 331.97 L64.47 336.48 L64.47 344.01 C64.47 345.67 62.22 347.02 59.43 347.02
+ L53.72 347.02 L37.61 347.02 L37.61 347.02 L5.04 347.02 C2.27 347.02 0 345.67 0 344.01 L0 336.48 L0 331.97
+ L0 331.97 Z" class="st5"/>
+ </g>
+ <g id="shape112-282" v:mID="112" v:groupContext="shape" transform="translate(196.65,-80.2991)">
+ <title>Sheet.112</title>
+ <desc>group id</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="27.7691" cy="339.824" width="55.54" height="14.3829"/>
+ <path d="M55.54 332.63 L0 332.63 L0 347.02 L55.54 347.02 L55.54 332.63" class="st3"/>
+ <text x="5.09" y="343.42" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>group id</text> </g>
+ <g id="shape114-286" v:mID="114" v:groupContext="shape" transform="translate(506.433,-128.007)">
+ <title>Sheet.114</title>
+ <desc>-</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="6.63728" cy="336.229" width="13.28" height="21.5726"/>
+ <path d="M13.27 325.44 L0 325.44 L0 347.02 L13.27 347.02 L13.27 325.44" class="st3"/>
+ <text x="3.06" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>-</text> </g>
+ <g id="shape115-290" v:mID="115" v:groupContext="shape" transform="translate(529.004,-128.007)">
+ <title>Sheet.115</title>
+ <desc>Keys separated into</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="97.1729" cy="336.229" width="194.35" height="21.5726"/>
+ <path d="M194.35 325.44 L0 325.44 L0 347.02 L194.35 347.02 L194.35 325.44" class="st3"/>
+ <text x="17.06" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Keys separated into </text> </g>
+ <g id="shape116-294" v:mID="116" v:groupContext="shape" transform="translate(529.004,-106.438)">
+ <title>Sheet.116</title>
+ <desc>groups based on</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="83.1587" cy="336.229" width="166.32" height="21.5726"/>
+ <path d="M166.32 325.44 L0 325.44 L0 347.02 L166.32 347.02 L166.32 325.44" class="st3"/>
+ <text x="15.23" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>groups based on </text> </g>
+ <g id="shape117-298" v:mID="117" v:groupContext="shape" transform="translate(529.004,-84.869)">
+ <title>Sheet.117</title>
+ <desc>some bits from hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="97.731" cy="336.229" width="195.47" height="21.5726"/>
+ <path d="M195.46 325.44 L0 325.44 L0 347.02 L195.46 347.02 L195.46 325.44" class="st3"/>
+ <text x="14.94" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>some bits from hash</text> </g>
+ <g id="shape118-302" v:mID="118" v:groupContext="shape" transform="translate(506.433,-63.2999)">
+ <title>Sheet.118</title>
+ <desc>-</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="6.63728" cy="336.229" width="13.28" height="21.5726"/>
+ <path d="M13.27 325.44 L0 325.44 L0 347.02 L13.27 347.02 L13.27 325.44" class="st3"/>
+ <text x="3.06" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>-</text> </g>
+ <g id="shape119-306" v:mID="119" v:groupContext="shape" transform="translate(529.004,-63.2999)">
+ <title>Sheet.119</title>
+ <desc>Groups contain a</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="84.2539" cy="336.229" width="168.51" height="21.5726"/>
+ <path d="M168.51 325.44 L0 325.44 L0 347.02 L168.51 347.02 L168.51 325.44" class="st3"/>
+ <text x="15.38" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Groups contain a </text> </g>
+ <g id="shape120-310" v:mID="120" v:groupContext="shape" transform="translate(529.004,-41.7308)">
+ <title>Sheet.120</title>
+ <desc>small number of</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="81.4635" cy="336.229" width="162.93" height="21.5726"/>
+ <path d="M162.93 325.44 L0 325.44 L0 347.02 L162.93 347.02 L162.93 325.44" class="st3"/>
+ <text x="15.01" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>small number of </text> </g>
+ <g id="shape121-314" v:mID="121" v:groupContext="shape" transform="translate(529.004,-20.1617)">
+ <title>Sheet.121</title>
+ <desc>keys (<28)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.4481" cy="336.229" width="100.9" height="21.5726"/>
+ <path d="M100.9 325.44 L0 325.44 L0 347.02 L100.9 347.02 L100.9 325.44" class="st3"/>
+ <text x="8.77" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>keys (<28)</text> </g>
+ <g id="shape122-318" v:mID="122" v:groupContext="shape" transform="translate(19.1996,-146.276)">
+ <title>Sheet.122</title>
+ <path d="M0 310.17 C-0 306.1 3.62 302.8 8.07 302.8 L14.46 302.8 L29.68 282.28 L36.14 302.8 L78.65 302.8 C83.11 302.8
+ 86.72 306.1 86.72 310.17 L86.72 310.17 L86.72 321.22 L86.72 339.65 C86.72 343.72 83.11 347.02 78.65 347.02
+ L36.14 347.02 L14.46 347.02 L14.46 347.02 L8.07 347.02 C3.62 347.02 0 343.72 0 339.65 L0 321.22 L0 310.17
+ L0 310.17 Z" class="st5"/>
+ </g>
+ <g id="shape123-320" v:mID="123" v:groupContext="shape" transform="translate(41.9777,-174.053)">
+ <title>Sheet.123</title>
+ <desc>Group</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="22.8289" cy="339.824" width="45.66" height="14.3829"/>
+ <path d="M45.66 332.63 L0 332.63 L0 347.02 L45.66 347.02 L45.66 332.63" class="st3"/>
+ <text x="5.9" y="343.42" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group </text> </g>
+ <g id="shape124-324" v:mID="124" v:groupContext="shape" transform="translate(34.4142,-159.674)">
+ <title>Sheet.124</title>
+ <desc>Identifier</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="31.5173" cy="339.824" width="63.04" height="14.3829"/>
+ <path d="M63.03 332.63 L0 332.63 L0 347.02 L63.03 347.02 L63.03 332.63" class="st3"/>
+ <text x="7.04" y="343.42" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Identifier </text> </g>
+ <g id="shape125-328" v:mID="125" v:groupContext="shape" transform="translate(28.7716,-145.295)">
+ <title>Sheet.125</title>
+ <desc>(simplified)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="36.2165" cy="339.824" width="72.44" height="14.3829"/>
+ <path d="M72.43 332.63 L0 332.63 L0 347.02 L72.43 347.02 L72.43 332.63" class="st3"/>
+ <text x="6.19" y="343.42" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(simplified)</text> </g>
+ <g id="shape127-332" v:mID="127" v:groupContext="shape" transform="translate(517.688,-71.2991)">
+ <title>Sheet.127</title>
+ <desc>Keys separated into groups based on some bits from hash. Grou...</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="112.5" cy="302.139" width="225" height="89.7513"/>
+ <g id="shadow127-333" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st19">
+ <rect x="0" y="257.264" width="225" height="89.7513" class="st20"/>
+ </g>
+ <rect x="0" y="257.264" width="225" height="89.7513" class="st21"/>
+ <text x="4" y="281.09" class="st22" v:langID="1033"><v:paragraph v:indentFirst="-18" v:indentLeft="18" v:bullet="1"/><v:tabList/><tspan
+ class="st23" v:isBullet="true">·</tspan> <tspan class="st24">Keys separated into groups based </tspan><tspan
+ x="22" dy="1.204em" class="st24">on some bits from hash</tspan><tspan class="st24">.<v:newlineChar/></tspan><tspan
+ x="4" dy="1.211em" class="st23" v:isBullet="true">·</tspan> <tspan class="st24">Groups contain a small number of </tspan><tspan
+ x="22" dy="1.204em" class="st24">keys </tspan><tspan class="st24">(</tspan><tspan class="st24"><</tspan><tspan
+ class="st24">28</tspan><tspan class="st24">)</tspan></text> </g>
+ <g id="shape129-349" v:mID="129" v:groupContext="shape" transform="translate(336.326,-26.2991)">
+ <title>Sheet.129</title>
+ <desc>Total # of keys in group so far</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="39.6784" cy="333.515" width="79.36" height="27"/>
+ <rect x="0" y="320.015" width="79.3567" height="27" class="st25"/>
+ <text x="4.5" y="329.92" class="st26" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Total # of keys <tspan
+ x="4.39" dy="1.2em" class="st23">in group so far</tspan></text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i8.svg b/doc/guides/prog_guide/img/efd_i8.svg
new file mode 100644
index 0000000..d0fd463
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i8.svg
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i9.svg Page-2 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="4.98372in" height="2.08442in"
+ viewBox="0 0 358.828 150.078" xml:space="preserve" color-interpolation-filters="sRGB" class="st8">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st4 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st5 {fill:#000000;font-family:Intel Clear;font-size:0.998566em}
+ .st6 {fill:#c00000;font-family:Intel Clear;font-size:0.828804em;font-weight:bold}
+ .st7 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st8 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="4" v:index="2" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-2</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape4-1" v:mID="4" v:groupContext="shape" transform="translate(206.306,-19.0195)">
+ <title>Sheet.4</title>
+ <path d="M0 38.04 L0 150.08 L133.5 150.08 L133.5 38.04 L0 38.04 L0 38.04 Z" class="st1"/>
+ </g>
+ <g id="shape5-3" v:mID="5" v:groupContext="shape" transform="translate(206.306,-19.0195)">
+ <title>Sheet.5</title>
+ <path d="M0 38.04 L133.5 38.04 L133.5 150.08 L0 150.08 L0 38.04" class="st2"/>
+ </g>
+ <g id="shape6-6" v:mID="6" v:groupContext="shape" transform="translate(215.55,-70.7853)">
+ <title>Sheet.6</title>
+ <path d="M0 121.92 C0 118.82 2.54 116.29 5.64 116.29 L110.69 116.29 C113.81 116.29 116.33 118.82 116.33 121.92 L116.33
+ 144.45 C116.33 147.56 113.81 150.08 110.69 150.08 L5.64 150.08 C2.54 150.08 0 147.56 0 144.45 L0 121.92
+ Z" class="st1"/>
+ </g>
+ <g id="shape7-8" v:mID="7" v:groupContext="shape" transform="translate(215.55,-70.7853)">
+ <title>Sheet.7</title>
+ <path d="M0 121.92 C0 118.82 2.54 116.29 5.64 116.29 L110.69 116.29 C113.81 116.29 116.33 118.82 116.33 121.92 L116.33
+ 144.45 C116.33 147.56 113.81 150.08 110.69 150.08 L5.64 150.08 C2.54 150.08 0 147.56 0 144.45 L0 121.92
+ Z" class="st3"/>
+ </g>
+ <g id="shape8-10" v:mID="8" v:groupContext="shape" transform="translate(242.756,-86.1914)">
+ <title>Sheet.8</title>
+ <desc>hash_index</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="36.9951" cy="142.887" width="74" height="14.3829"/>
+ <path d="M73.99 135.7 L0 135.7 L0 150.08 L73.99 150.08 L73.99 135.7" class="st4"/>
+ <text x="6.29" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash_index</text> </g>
+ <g id="shape9-14" v:mID="9" v:groupContext="shape" transform="translate(229.67,-71.812)">
+ <title>Sheet.9</title>
+ <desc>(integer, 16 bits)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="52.0635" cy="142.887" width="104.13" height="14.3829"/>
+ <path d="M104.13 135.7 L0 135.7 L0 150.08 L104.13 150.08 L104.13 135.7" class="st4"/>
+ <text x="8.25" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(integer, 16 bits)</text> </g>
+ <g id="shape10-18" v:mID="10" v:groupContext="shape" transform="translate(215.55,-27.1678)">
+ <title>Sheet.10</title>
+ <path d="M0 121.92 C0 118.82 2.54 116.29 5.64 116.29 L110.69 116.29 C113.81 116.29 116.33 118.82 116.33 121.92 L116.33
+ 144.45 C116.33 147.56 113.81 150.08 110.69 150.08 L5.64 150.08 C2.54 150.08 0 147.56 0 144.45 L0 121.92
+ Z" class="st1"/>
+ </g>
+ <g id="shape11-20" v:mID="11" v:groupContext="shape" transform="translate(215.55,-27.1678)">
+ <title>Sheet.11</title>
+ <path d="M0 121.92 C0 118.82 2.54 116.29 5.64 116.29 L110.69 116.29 C113.81 116.29 116.33 118.82 116.33 121.92 L116.33
+ 144.45 C116.33 147.56 113.81 150.08 110.69 150.08 L5.64 150.08 C2.54 150.08 0 147.56 0 144.45 L0 121.92
+ Z" class="st3"/>
+ </g>
+ <g id="shape12-22" v:mID="12" v:groupContext="shape" transform="translate(237.836,-42.6033)">
+ <title>Sheet.12</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="42.693" cy="142.887" width="85.39" height="14.3829"/>
+ <path d="M85.39 135.7 L0 135.7 L0 150.08 L85.39 150.08 L85.39 135.7" class="st4"/>
+ <text x="7.03" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape13-26" v:mID="13" v:groupContext="shape" transform="translate(251.643,-28.2239)">
+ <title>Sheet.13</title>
+ <desc>(16 bits)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="26.9562" cy="142.887" width="53.92" height="14.3829"/>
+ <path d="M53.91 135.7 L0 135.7 L0 150.08 L53.91 150.08 L53.91 135.7" class="st4"/>
+ <text x="4.98" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(16 bits)</text> </g>
+ <g id="shape14-30" v:mID="14" v:groupContext="shape" transform="translate(213.473,-114.303)">
+ <title>Sheet.14</title>
+ <desc>Group ID: 0x0102</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="47.976" cy="144.109" width="95.96" height="11.9384"/>
+ <path d="M95.95 138.14 L0 138.14 L0 150.08 L95.95 150.08 L95.95 138.14" class="st4"/>
+ <text x="7.47" y="147.09" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group ID: 0x0102</text> </g>
+ <g id="shape15-34" v:mID="15" v:groupContext="shape" transform="translate(19.0195,-99.4242)">
+ <title>Sheet.15</title>
+ <path d="M0 129.31 C0 127.02 1.87 125.15 4.16 125.15 L109.18 125.15 C111.47 125.15 113.33 127.02 113.33 129.31 L113.33
+ 145.93 C113.33 148.22 111.47 150.08 109.18 150.08 L4.16 150.08 C1.87 150.08 0 148.22 0 145.93 L0 129.31
+ Z" class="st1"/>
+ </g>
+ <g id="shape16-36" v:mID="16" v:groupContext="shape" transform="translate(19.0195,-99.4242)">
+ <title>Sheet.16</title>
+ <path d="M0 129.31 C0 127.02 1.87 125.15 4.16 125.15 L109.18 125.15 C111.47 125.15 113.33 127.02 113.33 129.31 L113.33
+ 145.93 C113.33 148.22 111.47 150.08 109.18 150.08 L4.16 150.08 C1.87 150.08 0 148.22 0 145.93 L0 129.31
+ Z" class="st3"/>
+ </g>
+ <g id="shape17-38" v:mID="17" v:groupContext="shape" transform="translate(33.9485,-103.285)">
+ <title>Sheet.17</title>
+ <desc>Key1: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="49.6862" cy="142.887" width="99.38" height="14.3829"/>
+ <path d="M99.37 135.7 L0 135.7 L0 150.08 L99.37 150.08 L99.37 135.7" class="st4"/>
+ <text x="7.94" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1: Value = 0</text> </g>
+ <g id="shape18-42" v:mID="18" v:groupContext="shape" transform="translate(19.0195,-74.6198)">
+ <title>Sheet.18</title>
+ <path d="M0 129.31 C0 127.02 1.87 125.15 4.16 125.15 L109.18 125.15 C111.47 125.15 113.33 127.02 113.33 129.31 L113.33
+ 145.93 C113.33 148.22 111.47 150.08 109.18 150.08 L4.16 150.08 C1.87 150.08 0 148.22 0 145.93 L0 129.31
+ Z" class="st1"/>
+ </g>
+ <g id="shape19-44" v:mID="19" v:groupContext="shape" transform="translate(19.0195,-74.6198)">
+ <title>Sheet.19</title>
+ <path d="M0 129.31 C0 127.02 1.87 125.15 4.16 125.15 L109.18 125.15 C111.47 125.15 113.33 127.02 113.33 129.31 L113.33
+ 145.93 C113.33 148.22 111.47 150.08 109.18 150.08 L4.16 150.08 C1.87 150.08 0 148.22 0 145.93 L0 129.31
+ Z" class="st3"/>
+ </g>
+ <g id="shape20-46" v:mID="20" v:groupContext="shape" transform="translate(33.9485,-78.4626)">
+ <title>Sheet.20</title>
+ <desc>Key3: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="49.6862" cy="142.887" width="99.38" height="14.3829"/>
+ <path d="M99.37 135.7 L0 135.7 L0 150.08 L99.37 150.08 L99.37 135.7" class="st4"/>
+ <text x="7.94" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3: Value = 1</text> </g>
+ <g id="shape21-50" v:mID="21" v:groupContext="shape" transform="translate(19.0195,-49.5757)">
+ <title>Sheet.21</title>
+ <path d="M0 129.21 C0 126.91 1.88 125.03 4.19 125.03 L109.15 125.03 C111.46 125.03 113.33 126.91 113.33 129.21 L113.33
+ 145.91 C113.33 148.21 111.46 150.08 109.15 150.08 L4.19 150.08 C1.88 150.08 0 148.21 0 145.91 L0 129.21
+ Z" class="st1"/>
+ </g>
+ <g id="shape22-52" v:mID="22" v:groupContext="shape" transform="translate(19.0195,-49.5757)">
+ <title>Sheet.22</title>
+ <path d="M0 129.21 C0 126.91 1.88 125.03 4.19 125.03 L109.15 125.03 C111.46 125.03 113.33 126.91 113.33 129.21 L113.33
+ 145.91 C113.33 148.21 111.46 150.08 109.15 150.08 L4.19 150.08 C1.88 150.08 0 148.21 0 145.91 L0 129.21
+ Z" class="st3"/>
+ </g>
+ <g id="shape23-54" v:mID="23" v:groupContext="shape" transform="translate(33.9485,-53.4903)">
+ <title>Sheet.23</title>
+ <desc>Key4: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="49.6862" cy="142.887" width="99.38" height="14.3829"/>
+ <path d="M99.37 135.7 L0 135.7 L0 150.08 L99.37 150.08 L99.37 135.7" class="st4"/>
+ <text x="7.94" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4: Value = 0</text> </g>
+ <g id="shape24-58" v:mID="24" v:groupContext="shape" transform="translate(19.0195,-25.0109)">
+ <title>Sheet.24</title>
+ <path d="M0 129.21 C0 126.91 1.88 125.03 4.19 125.03 L109.15 125.03 C111.46 125.03 113.33 126.91 113.33 129.21 L113.33
+ 145.91 C113.33 148.21 111.46 150.08 109.15 150.08 L4.19 150.08 C1.88 150.08 0 148.21 0 145.91 L0 129.21
+ Z" class="st1"/>
+ </g>
+ <g id="shape25-60" v:mID="25" v:groupContext="shape" transform="translate(19.0195,-25.0109)">
+ <title>Sheet.25</title>
+ <path d="M0 129.21 C0 126.91 1.88 125.03 4.19 125.03 L109.15 125.03 C111.46 125.03 113.33 126.91 113.33 129.21 L113.33
+ 145.91 C113.33 148.21 111.46 150.08 109.15 150.08 L4.19 150.08 C1.88 150.08 0 148.21 0 145.91 L0 129.21
+ Z" class="st3"/>
+ </g>
+ <g id="shape26-62" v:mID="26" v:groupContext="shape" transform="translate(33.9485,-28.927)">
+ <title>Sheet.26</title>
+ <desc>Key7: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="49.6862" cy="142.887" width="99.38" height="14.3829"/>
+ <path d="M99.37 135.7 L0 135.7 L0 150.08 L99.37 150.08 L99.37 135.7" class="st4"/>
+ <text x="7.94" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7: Value = 1</text> </g>
+ <g id="shape27-66" v:mID="27" v:groupContext="shape" transform="translate(141.536,-51.5529)">
+ <title>Sheet.27</title>
+ <path d="M0 115.39 L22.75 115.39 L22.75 103.82 L45.5 126.95 L22.75 150.08 L22.75 138.51 L0 138.51 L0 115.39 Z"
+ class="st7"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i9.svg b/doc/guides/prog_guide/img/efd_i9.svg
new file mode 100644
index 0000000..b2e385d
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i9.svg
@@ -0,0 +1,390 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i10.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="9.8125in" height="3.76365in"
+ viewBox="0 0 706.5 270.983" xml:space="preserve" color-interpolation-filters="sRGB" class="st9">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st4 {fill:#000000;font-family:Arial;font-size:0.998566em}
+ .st5 {fill:#000000;font-family:Arial;font-size:0.918686em}
+ .st6 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st7 {fill:#ffffff;font-family:Arial;font-size:0.998566em}
+ .st8 {fill:#ffffff;font-family:Arial;font-size:1.49785em}
+ .st9 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape68-1" v:mID="68" v:groupContext="shape" transform="translate(196.523,-158.978)">
+ <title>Sheet.68</title>
+ <path d="M0 250.22 C0 247.95 1.89 246.06 4.17 246.06 L317.25 246.06 C319.53 246.06 321.39 247.95 321.39 250.22 L321.39
+ 266.85 C321.39 269.13 319.53 270.98 317.25 270.98 L4.17 270.98 C1.89 270.98 0 269.13 0 266.85 L0 250.22
+ Z" class="st1"/>
+ </g>
+ <g id="shape69-3" v:mID="69" v:groupContext="shape" transform="translate(196.523,-158.978)">
+ <title>Sheet.69</title>
+ <path d="M0 250.22 C0 247.95 1.89 246.06 4.17 246.06 L317.25 246.06 C319.53 246.06 321.39 247.95 321.39 250.22 L321.39
+ 266.85 C321.39 269.13 319.53 270.98 317.25 270.98 L4.17 270.98 C1.89 270.98 0 269.13 0 266.85 L0 250.22
+ Z" class="st2"/>
+ </g>
+ <g id="shape70-5" v:mID="70" v:groupContext="shape" transform="translate(186.139,-162.437)">
+ <title>Sheet.70</title>
+ <desc>(hash(key, seed1) + hash_index *</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="106.671" cy="263.792" width="213.35" height="14.3829"/>
+ <path d="M213.34 256.6 L0 256.6 L0 270.98 L213.34 270.98 L213.34 256.6" class="st3"/>
+ <text x="17.24" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(hash(key, seed1) + hash_index * </text> </g>
+ <g id="shape71-9" v:mID="71" v:groupContext="shape" transform="translate(381.48,-162.845)">
+ <title>Sheet.71</title>
+ <desc>hash(key</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="27.4843" cy="264.367" width="54.97" height="13.2327"/>
+ <path d="M54.97 257.75 L0 257.75 L0 270.98 L54.97 270.98 L54.97 257.75" class="st3"/>
+ <text x="5.12" y="267.67" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash(key</text> </g>
+ <g id="shape72-13" v:mID="72" v:groupContext="shape" transform="translate(424.755,-162.437)">
+ <title>Sheet.72</title>
+ <desc>, seed2)) % 16</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="46.7254" cy="263.792" width="93.46" height="14.3829"/>
+ <path d="M93.45 256.6 L0 256.6 L0 270.98 L93.45 270.98 L93.45 256.6" class="st3"/>
+ <text x="7.76" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>, seed2)) % 16</text> </g>
+ <g id="shape73-17" v:mID="73" v:groupContext="shape" transform="translate(524.094,-148.373)">
+ <title>Sheet.73</title>
+ <path d="M0 236.29 L22.75 236.29 L22.75 224.73 L45.5 247.86 L22.75 270.98 L22.75 259.42 L0 259.42 L0 236.29 Z"
+ class="st6"/>
+ </g>
+ <g id="shape74-19" v:mID="74" v:groupContext="shape" transform="translate(574.148,-217.574)">
+ <title>Sheet.74</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st1"/>
+ </g>
+ <g id="shape75-21" v:mID="75" v:groupContext="shape" transform="translate(574.148,-217.574)">
+ <title>Sheet.75</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st2"/>
+ </g>
+ <g id="shape76-23" v:mID="76" v:groupContext="shape" transform="translate(584.296,-231.499)">
+ <title>Sheet.76</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="40.993" cy="263.792" width="81.99" height="14.3829"/>
+ <path d="M81.99 256.6 L0 256.6 L0 270.98 L81.99 270.98 L81.99 256.6" class="st3"/>
+ <text x="7.01" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape77-27" v:mID="77" v:groupContext="shape" transform="translate(655.369,-231.499)">
+ <title>Sheet.77</title>
+ <desc>bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1076" cy="263.792" width="22.22" height="14.3829"/>
+ <path d="M22.22 256.6 L0 256.6 L0 270.98 L22.22 270.98 L22.22 256.6" class="st3"/>
+ <text x="4.78" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit </text> </g>
+ <g id="shape78-31" v:mID="78" v:groupContext="shape" transform="translate(588.858,-217.12)">
+ <title>Sheet.78</title>
+ <desc>index for key1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="44.8113" cy="263.792" width="89.63" height="14.3829"/>
+ <path d="M89.62 256.6 L0 256.6 L0 270.98 L89.62 270.98 L89.62 256.6" class="st3"/>
+ <text x="7.51" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>index for key1</text> </g>
+ <g id="shape79-35" v:mID="79" v:groupContext="shape" transform="translate(573.548,-178.869)">
+ <title>Sheet.79</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st1"/>
+ </g>
+ <g id="shape80-37" v:mID="80" v:groupContext="shape" transform="translate(573.548,-178.869)">
+ <title>Sheet.80</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st2"/>
+ </g>
+ <g id="shape81-39" v:mID="81" v:groupContext="shape" transform="translate(584.296,-192.768)">
+ <title>Sheet.81</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="40.993" cy="263.792" width="81.99" height="14.3829"/>
+ <path d="M81.99 256.6 L0 256.6 L0 270.98 L81.99 270.98 L81.99 256.6" class="st3"/>
+ <text x="7.01" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape82-43" v:mID="82" v:groupContext="shape" transform="translate(655.369,-192.768)">
+ <title>Sheet.82</title>
+ <desc>bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1076" cy="263.792" width="22.22" height="14.3829"/>
+ <path d="M22.22 256.6 L0 256.6 L0 270.98 L22.22 270.98 L22.22 256.6" class="st3"/>
+ <text x="4.78" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit </text> </g>
+ <g id="shape83-47" v:mID="83" v:groupContext="shape" transform="translate(588.858,-178.388)">
+ <title>Sheet.83</title>
+ <desc>index for key3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="44.8113" cy="263.792" width="89.63" height="14.3829"/>
+ <path d="M89.62 256.6 L0 256.6 L0 270.98 L89.62 270.98 L89.62 256.6" class="st3"/>
+ <text x="7.51" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>index for key3</text> </g>
+ <g id="shape84-51" v:mID="84" v:groupContext="shape" transform="translate(574.148,-139.326)">
+ <title>Sheet.84</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st1"/>
+ </g>
+ <g id="shape85-53" v:mID="85" v:groupContext="shape" transform="translate(574.148,-139.326)">
+ <title>Sheet.85</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st2"/>
+ </g>
+ <g id="shape86-55" v:mID="86" v:groupContext="shape" transform="translate(584.296,-153.227)">
+ <title>Sheet.86</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="40.993" cy="263.792" width="81.99" height="14.3829"/>
+ <path d="M81.99 256.6 L0 256.6 L0 270.98 L81.99 270.98 L81.99 256.6" class="st3"/>
+ <text x="7.01" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape87-59" v:mID="87" v:groupContext="shape" transform="translate(655.369,-153.227)">
+ <title>Sheet.87</title>
+ <desc>bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1076" cy="263.792" width="22.22" height="14.3829"/>
+ <path d="M22.22 256.6 L0 256.6 L0 270.98 L22.22 270.98 L22.22 256.6" class="st3"/>
+ <text x="4.78" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit </text> </g>
+ <g id="shape88-63" v:mID="88" v:groupContext="shape" transform="translate(588.858,-138.848)">
+ <title>Sheet.88</title>
+ <desc>index for key4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="44.8113" cy="263.792" width="89.63" height="14.3829"/>
+ <path d="M89.62 256.6 L0 256.6 L0 270.98 L89.62 270.98 L89.62 256.6" class="st3"/>
+ <text x="7.51" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>index for key4</text> </g>
+ <g id="shape89-67" v:mID="89" v:groupContext="shape" transform="translate(574.148,-100.622)">
+ <title>Sheet.89</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st1"/>
+ </g>
+ <g id="shape90-69" v:mID="90" v:groupContext="shape" transform="translate(574.148,-100.622)">
+ <title>Sheet.90</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st2"/>
+ </g>
+ <g id="shape91-71" v:mID="91" v:groupContext="shape" transform="translate(584.296,-114.496)">
+ <title>Sheet.91</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="40.993" cy="263.792" width="81.99" height="14.3829"/>
+ <path d="M81.99 256.6 L0 256.6 L0 270.98 L81.99 270.98 L81.99 256.6" class="st3"/>
+ <text x="7.01" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape92-75" v:mID="92" v:groupContext="shape" transform="translate(655.369,-114.496)">
+ <title>Sheet.92</title>
+ <desc>bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1076" cy="263.792" width="22.22" height="14.3829"/>
+ <path d="M22.22 256.6 L0 256.6 L0 270.98 L22.22 270.98 L22.22 256.6" class="st3"/>
+ <text x="4.78" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit </text> </g>
+ <g id="shape93-79" v:mID="93" v:groupContext="shape" transform="translate(588.858,-100.117)">
+ <title>Sheet.93</title>
+ <desc>index for key7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="44.8113" cy="263.792" width="89.63" height="14.3829"/>
+ <path d="M89.62 256.6 L0 256.6 L0 270.98 L89.62 270.98 L89.62 256.6" class="st3"/>
+ <text x="7.51" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>index for key7</text> </g>
+ <g id="shape94-83" v:mID="94" v:groupContext="shape" transform="translate(205.227,-191.137)">
+ <title>Sheet.94</title>
+ <path d="M0 217.76 C0 213 3.87 209.14 8.64 209.14 L14.53 209.14 L14.53 209.14 L36.32 209.14 L78.52 209.14 C83.3 209.14
+ 87.16 213 87.16 217.76 L87.16 239.33 L87.16 239.33 L87.16 252.27 L87.16 252.27 C87.16 257.05 83.3 260.9
+ 78.52 260.9 L36.32 260.9 L18.46 270.98 L14.53 260.9 L8.64 260.9 C3.87 260.9 0 257.05 0 252.27 L0 239.33
+ L0 239.33 L0 217.76 Z" class="st6"/>
+ </g>
+ <g id="shape95-85" v:mID="95" v:groupContext="shape" transform="translate(214.98,-225.215)">
+ <title>Sheet.95</title>
+ <desc>CRC32 (32</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="38.2947" cy="263.792" width="76.59" height="14.3829"/>
+ <path d="M76.59 256.6 L0 256.6 L0 270.98 L76.59 270.98 L76.59 256.6" class="st3"/>
+ <text x="8.33" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>CRC32 (32 </text> </g>
+ <g id="shape96-89" v:mID="96" v:groupContext="shape" transform="translate(222.123,-210.835)">
+ <title>Sheet.96</title>
+ <desc>bit output)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="32.5584" cy="263.792" width="65.12" height="14.3829"/>
+ <path d="M65.12 256.6 L0 256.6 L0 270.98 L65.12 270.98 L65.12 256.6" class="st3"/>
+ <text x="5.91" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit output)</text> </g>
+ <g id="shape97-93" v:mID="97" v:groupContext="shape" transform="translate(305.473,-188.366)">
+ <title>Sheet.97</title>
+ <path d="M0 226.84 C0 223.28 2.9 220.39 6.47 220.39 L21.37 220.39 L21.37 220.39 L53.42 220.39 L121.77 220.39 C125.34
+ 220.39 128.22 223.28 128.22 226.84 L128.22 242.97 L128.22 242.97 L128.22 252.65 L128.22 252.65 C128.22 256.21
+ 125.34 259.09 121.77 259.09 L53.42 259.09 L38.73 270.98 L21.37 259.09 L6.47 259.09 C2.9 259.09 0 256.21
+ 0 252.65 L0 242.97 L0 242.97 L0 226.84 Z" class="st6"/>
+ </g>
+ <g id="shape98-95" v:mID="98" v:groupContext="shape" transform="translate(318.48,-217.733)">
+ <title>Sheet.98</title>
+ <desc>Goal: Find a valid</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="57.4478" cy="263.792" width="114.9" height="14.3829"/>
+ <path d="M114.9 256.6 L0 256.6 L0 270.98 L114.9 270.98 L114.9 256.6" class="st3"/>
+ <text x="10.82" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Goal: Find a valid </text> </g>
+ <g id="shape99-99" v:mID="99" v:groupContext="shape" transform="translate(339.077,-203.354)">
+ <title>Sheet.99</title>
+ <desc>hash_index</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="37.1611" cy="263.792" width="74.33" height="14.3829"/>
+ <path d="M74.32 256.6 L0 256.6 L0 270.98 L74.32 270.98 L74.32 256.6" class="st3"/>
+ <text x="6.51" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash_index</text> </g>
+ <g id="shape100-103" v:mID="100" v:groupContext="shape" transform="translate(438.135,-185.939)">
+ <title>Sheet.100</title>
+ <path d="M0 217.36 C0 213.8 2.91 210.89 6.48 210.89 L21.37 210.89 L21.37 210.89 L53.42 210.89 L121.77 210.89 C125.34
+ 210.89 128.22 213.8 128.22 217.36 L128.22 233.48 L128.22 233.48 L128.22 243.15 L128.22 243.15 C128.22 246.72
+ 125.34 249.59 121.77 249.59 L53.42 249.59 L54.75 270.98 L21.37 249.59 L6.48 249.59 C2.91 249.59 0 246.72
+ 0 243.15 L0 233.48 L0 233.48 L0 217.36 Z" class="st6"/>
+ </g>
+ <g id="shape101-105" v:mID="101" v:groupContext="shape" transform="translate(448.763,-224.802)">
+ <title>Sheet.101</title>
+ <desc>Lookup Table has</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.6085" cy="263.792" width="117.22" height="14.3829"/>
+ <path d="M117.22 256.6 L0 256.6 L0 270.98 L117.22 270.98 L117.22 256.6" class="st3"/>
+ <text x="10.98" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Lookup Table has </text> </g>
+ <g id="shape102-109" v:mID="102" v:groupContext="shape" transform="translate(484.549,-210.423)">
+ <title>Sheet.102</title>
+ <desc>16 bits</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="22.2166" cy="263.792" width="44.44" height="14.3829"/>
+ <path d="M44.43 256.6 L0 256.6 L0 270.98 L44.43 270.98 L44.43 256.6" class="st3"/>
+ <text x="4.56" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>16 bits</text> </g>
+ <g id="shape103-113" v:mID="103" v:groupContext="shape" transform="translate(369.583,-90.8555)">
+ <title>Sheet.103</title>
+ <path d="M0 227.76 C0 222.98 3.89 219.1 8.67 219.1 L14.53 219.1 L34.47 205.09 L36.32 219.1 L78.5 219.1 C83.29 219.1 87.16
+ 222.98 87.16 227.76 L87.16 227.76 L87.16 240.73 L87.16 262.34 C87.16 267.12 83.29 270.98 78.5 270.98 L36.32
+ 270.98 L14.53 270.98 L14.53 270.98 L8.67 270.98 C3.89 270.98 0 267.12 0 262.34 L0 240.73 L0 227.76 L0 227.76
+ Z" class="st6"/>
+ </g>
+ <g id="shape104-115" v:mID="104" v:groupContext="shape" transform="translate(383.264,-114.932)">
+ <title>Sheet.104</title>
+ <desc>CRC32 (32</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="38.2947" cy="263.792" width="76.59" height="14.3829"/>
+ <path d="M76.59 256.6 L0 256.6 L0 270.98 L76.59 270.98 L76.59 256.6" class="st3"/>
+ <text x="8.33" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>CRC32 (32 </text> </g>
+ <g id="shape105-119" v:mID="105" v:groupContext="shape" transform="translate(386.505,-100.553)">
+ <title>Sheet.105</title>
+ <desc>bit output)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="32.5584" cy="263.792" width="65.12" height="14.3829"/>
+ <path d="M65.12 256.6 L0 256.6 L0 270.98 L65.12 270.98 L65.12 256.6" class="st3"/>
+ <text x="5.91" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit output)</text> </g>
+ <g id="shape106-123" v:mID="106" v:groupContext="shape" transform="translate(313.397,-18)">
+ <title>Sheet.106</title>
+ <path d="M0 226.35 C0 221.43 4.02 217.42 8.94 217.42 L347.02 217.42 C351.97 217.42 355.96 221.43 355.96 226.35 L355.96
+ 262.06 C355.96 267 351.97 270.98 347.02 270.98 L8.94 270.98 C4.02 270.98 0 267 0 262.06 L0 226.35 Z"
+ class="st6"/>
+ </g>
+ <g id="shape107-125" v:mID="107" v:groupContext="shape" transform="translate(313.98,-41.963)">
+ <title>Sheet.107</title>
+ <desc>Goal is to find a hash_index that produces</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="177.75" cy="260.197" width="355.5" height="21.5726"/>
+ <path d="M355.5 249.41 L0 249.41 L0 270.98 L355.5 270.98 L355.5 249.41" class="st3"/>
+ <text x="9.88" y="265.59" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Goal is to find a hash_index that produces </text> </g>
+ <g id="shape108-129" v:mID="108" v:groupContext="shape" transform="translate(318.48,-20.3939)">
+ <title>Sheet.108</title>
+ <desc>a lookup_table with no contradictions</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="175.5" cy="260.197" width="351" height="21.5726"/>
+ <path d="M351 249.41 L0 249.41 L0 270.98 L351 270.98 L351 249.41" class="st3"/>
+ <text x="28.12" y="265.59" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>a lookup_table with no contradictions</text> </g>
+ <g id="shape109-133" v:mID="109" v:groupContext="shape" transform="translate(18,-196.244)">
+ <title>Sheet.109</title>
+ <path d="M0 250.22 C0 247.92 1.87 246.06 4.16 246.06 L109.18 246.06 C111.47 246.06 113.33 247.92 113.33 250.22 L113.33
+ 266.83 C113.33 269.13 111.47 270.98 109.18 270.98 L4.16 270.98 C1.87 270.98 0 269.13 0 266.83 L0 250.22
+ Z" class="st1"/>
+ </g>
+ <g id="shape110-135" v:mID="110" v:groupContext="shape" transform="translate(29.8201,-196.244)">
+ <title>Sheet.110</title>
+ <path d="M0 250.22 C-0 247.92 1.67 246.06 3.73 246.06 L97.79 246.06 C99.85 246.06 101.51 247.92 101.51 250.22 L101.51
+ 266.83 C101.51 269.13 99.85 270.98 97.79 270.98 L3.73 270.98 C1.67 270.98 0 269.13 0 266.83 L0 250.22 Z"
+ class="st2"/>
+ </g>
+ <g id="shape111-137" v:mID="111" v:groupContext="shape" transform="translate(32.5663,-199.746)">
+ <title>Sheet.111</title>
+ <desc>Key1: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.7562" cy="263.792" width="101.52" height="14.3829"/>
+ <path d="M101.51 256.6 L0 256.6 L0 270.98 L101.51 270.98 L101.51 256.6" class="st3"/>
+ <text x="8.29" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1: Value = 0</text> </g>
+ <g id="shape112-141" v:mID="112" v:groupContext="shape" transform="translate(18,-171.44)">
+ <title>Sheet.112</title>
+ <path d="M0 250.22 C0 247.92 1.87 246.06 4.16 246.06 L109.18 246.06 C111.47 246.06 113.33 247.92 113.33 250.22 L113.33
+ 266.83 C113.33 269.13 111.47 270.98 109.18 270.98 L4.16 270.98 C1.87 270.98 0 269.13 0 266.83 L0 250.22
+ Z" class="st1"/>
+ </g>
+ <g id="shape113-143" v:mID="113" v:groupContext="shape" transform="translate(29.8201,-171.44)">
+ <title>Sheet.113</title>
+ <path d="M0 250.22 C0 247.92 1.67 246.06 3.73 246.06 L97.79 246.06 C99.85 246.06 101.51 247.92 101.51 250.22 L101.51
+ 266.83 C101.51 269.13 99.85 270.98 97.79 270.98 L3.73 270.98 C1.67 270.98 0 269.13 0 266.83 L0 250.22 Z"
+ class="st2"/>
+ </g>
+ <g id="shape114-145" v:mID="114" v:groupContext="shape" transform="translate(32.5663,-174.923)">
+ <title>Sheet.114</title>
+ <desc>Key3: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.7562" cy="263.792" width="101.52" height="14.3829"/>
+ <path d="M101.51 256.6 L0 256.6 L0 270.98 L101.51 270.98 L101.51 256.6" class="st3"/>
+ <text x="8.29" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3: Value = 1</text> </g>
+ <g id="shape115-149" v:mID="115" v:groupContext="shape" transform="translate(18,-146.396)">
+ <title>Sheet.115</title>
+ <path d="M0 250.12 C0 247.81 1.88 245.94 4.19 245.94 L109.15 245.94 C111.46 245.94 113.33 247.81 113.33 250.12 L113.33
+ 266.81 C113.33 269.12 111.46 270.98 109.15 270.98 L4.19 270.98 C1.88 270.98 0 269.12 0 266.81 L0 250.12
+ Z" class="st1"/>
+ </g>
+ <g id="shape116-151" v:mID="116" v:groupContext="shape" transform="translate(29.8201,-146.396)">
+ <title>Sheet.116</title>
+ <path d="M0 250.12 C0 247.81 1.68 245.94 3.75 245.94 L97.77 245.94 C99.84 245.94 101.51 247.81 101.51 250.12 L101.51
+ 266.81 C101.51 269.12 99.84 270.98 97.77 270.98 L3.75 270.98 C1.68 270.98 0 269.12 0 266.81 L0 250.12 Z"
+ class="st2"/>
+ </g>
+ <g id="shape117-153" v:mID="117" v:groupContext="shape" transform="translate(32.5663,-149.951)">
+ <title>Sheet.117</title>
+ <desc>Key4: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.7562" cy="263.792" width="101.52" height="14.3829"/>
+ <path d="M101.51 256.6 L0 256.6 L0 270.98 L101.51 270.98 L101.51 256.6" class="st3"/>
+ <text x="8.29" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4: Value = 0</text> </g>
+ <g id="shape118-157" v:mID="118" v:groupContext="shape" transform="translate(18,-121.831)">
+ <title>Sheet.118</title>
+ <path d="M0 250.12 C0 247.81 1.88 245.94 4.19 245.94 L109.15 245.94 C111.46 245.94 113.33 247.81 113.33 250.12 L113.33
+ 266.81 C113.33 269.12 111.46 270.98 109.15 270.98 L4.19 270.98 C1.88 270.98 0 269.12 0 266.81 L0 250.12
+ Z" class="st1"/>
+ </g>
+ <g id="shape119-159" v:mID="119" v:groupContext="shape" transform="translate(29.8201,-121.831)">
+ <title>Sheet.119</title>
+ <path d="M0 250.12 C0 247.81 1.68 245.94 3.75 245.94 L97.77 245.94 C99.84 245.94 101.51 247.81 101.51 250.12 L101.51
+ 266.81 C101.51 269.12 99.84 270.98 97.77 270.98 L3.75 270.98 C1.68 270.98 0 269.12 0 266.81 L0 250.12 Z"
+ class="st2"/>
+ </g>
+ <g id="shape120-161" v:mID="120" v:groupContext="shape" transform="translate(32.5663,-125.388)">
+ <title>Sheet.120</title>
+ <desc>Key7: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.7562" cy="263.792" width="101.52" height="14.3829"/>
+ <path d="M101.51 256.6 L0 256.6 L0 270.98 L101.51 270.98 L101.51 256.6" class="st3"/>
+ <text x="8.29" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7: Value = 1</text> </g>
+ <g id="shape121-165" v:mID="121" v:groupContext="shape" transform="translate(140.517,-148.373)">
+ <title>Sheet.121</title>
+ <path d="M0 236.29 L22.75 236.29 L22.75 224.73 L45.5 247.86 L22.75 270.98 L22.75 259.42 L0 259.42 L0 236.29 Z"
+ class="st6"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/index.rst b/doc/guides/prog_guide/index.rst
index ed7f770..7f825cb 100644
--- a/doc/guides/prog_guide/index.rst
+++ b/doc/guides/prog_guide/index.rst
@@ -47,6 +47,7 @@ Programmer's Guide
link_bonding_poll_mode_drv_lib
timer_lib
hash_lib
+ efd_lib
lpm_lib
lpm6_lib
packet_distrib_lib
@@ -167,6 +168,28 @@ Programmer's Guide
:numref:`figure_figure39` :ref:`figure_figure39`
+:numref:`figure_efd1` :ref:`figure_efd1`
+
+:numref:`figure_efd2` :ref:`figure_efd2`
+
+:numref:`figure_efd3` :ref:`figure_efd3`
+
+:numref:`figure_efd4` :ref:`figure_efd4`
+
+:numref:`figure_efd5` :ref:`figure_efd5`
+
+:numref:`figure_efd6` :ref:`figure_efd6`
+
+:numref:`figure_efd7` :ref:`figure_efd7`
+
+:numref:`figure_efd8` :ref:`figure_efd8`
+
+:numref:`figure_efd9` :ref:`figure_efd9`
+
+:numref:`figure_efd10` :ref:`figure_efd10`
+
+:numref:`figure_efd11` :ref:`figure_efd11`
+
**Tables**
diff --git a/doc/guides/rel_notes/release_17_02.rst b/doc/guides/rel_notes/release_17_02.rst
index 5023038..4c94513 100644
--- a/doc/guides/rel_notes/release_17_02.rst
+++ b/doc/guides/rel_notes/release_17_02.rst
@@ -64,6 +64,9 @@ New Features
is much smaller than a hash-based flow table and therefore, it can better fit for
CPU cache, being able to scale to millions of flow keys.
+ See the :ref:`Elastic Flow Distributor Library <Efd_Library>` documentation in
+ the Programmers Guide document, for more information.
+
Resolved Issues
---------------
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v4 5/5] doc: add flow distributor guide
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 0/5] Elastic Flow Distributor Pablo de Lara
` (3 preceding siblings ...)
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 4/5] doc: add EFD library section in Programmers guide Pablo de Lara
@ 2017-01-15 12:04 ` Pablo de Lara
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 0/5] Elastic Flow Distributor Pablo de Lara
5 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-15 12:04 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Sameh Gobriel
Signed-off-by: Sameh Gobriel <sameh.gobriel@intel.com>
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 1 +
doc/guides/sample_app_ug/flow_distributor.rst | 494 ++++++++
doc/guides/sample_app_ug/img/flow_distributor.svg | 1254 +++++++++++++++++++++
doc/guides/sample_app_ug/index.rst | 1 +
4 files changed, 1750 insertions(+)
create mode 100644 doc/guides/sample_app_ug/flow_distributor.rst
create mode 100644 doc/guides/sample_app_ug/img/flow_distributor.svg
diff --git a/MAINTAINERS b/MAINTAINERS
index 66e9466..0d3b247 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -535,6 +535,7 @@ F: lib/librte_efd/
F: doc/guides/prog_guide/efd_lib.rst
F: app/test/test_efd*
F: examples/flow_distributor/
+F: doc/guides/sample_app_ug/flow_distributor.rst
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
diff --git a/doc/guides/sample_app_ug/flow_distributor.rst b/doc/guides/sample_app_ug/flow_distributor.rst
new file mode 100644
index 0000000..a8cd5f4
--- /dev/null
+++ b/doc/guides/sample_app_ug/flow_distributor.rst
@@ -0,0 +1,494 @@
+.. BSD LICENSE
+ Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of Intel Corporation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Flow Distributor Sample Application
+===================================
+
+This sample application demonstrates the use of EFD library as a flow-level
+load balancer, for more information about the EFD Library please refer to the
+DPDK programmer's guide.
+
+This sample application is a variant of the
+:ref:`client-server sample application <multi_process_app>`
+where a specific target node is specified for every and each flow
+(not in a round-robin fashion as the original load balancing sample application).
+
+Overview
+--------
+
+The architecture of the EFD flow-based load balancer sample application is
+presented in the following figure.
+
+.. _figure_efd_sample_app_overview:
+
+.. figure:: img/flow_distributor.*
+
+ Using EFD as a Flow-Level Load Balancer
+
+As shown in :numref:`_figure_efd_sample_app_overview`,
+the sample application consists of a front-end node (distributor)
+using the EFD library to create a load-balancing table for flows,
+for each flow a target backend worker node is specified. The EFD table does not
+store the flow key (unlike a regular hash table), and hence, it can
+individually load-balance millions of flows (number of targets * maximum number
+of flows fit in a flow table per target) while still fitting in CPU cache.
+
+It should be noted that although they are referred to as nodes, the frontend
+distributor and worker nodes are processes running on the same platform.
+
+Front-end Distributor
+~~~~~~~~~~~~~~~~~~~~~
+
+Upon initializing, the frontend distributor node (process) creates a flow
+distributor table (based on the EFD library) which is populated with flow
+information and its intended target node.
+
+The sample application assigns a specific target node_id (process) for each of
+the IP destination addresses as follows:
+
+.. code-block:: c
+
+ node_id = i % num_nodes; /* Target node id is generated */
+ ip_dst = rte_cpu_to_be_32(i); /* Specific ip destination address is
+ assigned to this target node */
+
+then the pair of <key,target> is inserted into the flow distribution table.
+
+The main loop of the the distributor node receives a burst of packets, then for
+each packet, a flow key (IP destination address) is extracted. The flow
+distributor table is looked up and the target node id is returned. Packets are
+then enqueued to the specified target node id.
+
+It should be noted that flow distributor table is not a membership test table.
+I.e. if the key has already been inserted the target node id will be correct,
+but for new keys the flow distributor table will return a value (which can be
+valid).
+
+Backend Worker Nodes
+~~~~~~~~~~~~~~~~~~~~
+
+Upon initializing, the worker node (process) creates a flow table (a regular
+hash table that stores the key default size 1M flows) which is populated with
+only the flow information that is serviced at this node. This flow key is
+essential to point out new keys that have not been inserted before.
+
+The worker node's main loop is simply receiving packets then doing a hash table
+lookup. If a match occurs then statistics are updated for flows serviced by
+this node. If no match is found in the local hash table then this indicates
+that this is a new flow, which is dropped.
+
+
+Compiling the Application
+-------------------------
+
+The sequence of steps used to build the application is:
+
+#. Export the required environment variables:
+
+ .. code-block:: console
+
+ export RTE_SDK=/path/to/rte_sdk
+ export RTE_TARGET=x86_64-native-linuxapp-gcc
+
+#. Build the application executable file:
+
+ .. code-block:: console
+
+ cd ${RTE_SDK}/examples/flow_distributor/
+ make
+
+ For more details on how to build the DPDK libraries and sample
+ applications,
+ please refer to the *DPDK Getting Started Guide.*
+
+
+Running the Application
+-----------------------
+
+The application has two binaries to be run: the front-end distributor
+and the back-end node.
+
+The frontend distributor (distributor) has the following command line options::
+
+ ./distributor [EAL options] -- -p PORTMASK -n NUM_NODES -f NUM_FLOWS
+
+Where,
+
+* ``-p PORTMASK:`` Hexadecimal bitmask of ports to configure
+* ``-n NUM_NODES:`` Number of back-end nodes that will be used
+* ``-f NUM_FLOWS:`` Number of flows to be added in the EFD table (1 million, by default)
+
+The back-end node (node) has the following command line options::
+
+ ./node [EAL options] -- -n NODE_ID
+
+Where,
+
+* ``-n NODE_ID:`` Node ID, which cannot be equal or higher than NUM_MODES
+
+
+First, the distributor app must be launched, with the number of nodes that will be run.
+Once it has been started, the node instances can be run, with different NODE_ID.
+These instances have to be run as secondary processes, with ``--proc-type=secondary``
+in the EAL options, which will attach to the primary process memory, and therefore,
+they can access the queues created by the primary process to distribute packets.
+
+To successfully run the application, the command line used to start the
+application has to be in sync with the traffic flows configured on the traffic
+generator side.
+
+For examples of application command lines and traffic generator flows, please
+refer to the DPDK Test Report. For more details on how to set up and run the
+sample applications provided with DPDK package, please refer to the
+:ref:`DPDK Getting Started Guide for Linux <linux_gsg>` and
+:ref:`DPDK Getting Started Guide for FreeBSD <freebsd_gsg>`.
+
+
+Explanation
+-----------
+
+As described in previous sections, there are two processes in this example.
+
+The first process, the front-end distributor, creates and populates the EFD table,
+which is used to distribute packets to nodes, which the number of flows
+specified in the command line (1 million, by default).
+
+
+.. code-block:: c
+
+ static void
+ create_flow_distributor_table(void)
+ {
+ uint8_t socket_id = rte_socket_id();
+
+ /* create table */
+ efd_table = rte_efd_create("flow table", num_flows * 2, sizeof(uint32_t),
+ 1 << socket_id, socket_id);
+
+ if (efd_table == NULL)
+ rte_exit(EXIT_FAILURE, "Problem creating the flow table\n");
+ }
+
+ static void
+ populate_flow_distributor_table(void)
+ {
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint8_t socket_id = rte_socket_id();
+ uint64_t node_id;
+
+ /* Add flows in table */
+ for (i = 0; i < num_flows; i++) {
+ node_id = i % num_nodes;
+
+ ip_dst = rte_cpu_to_be_32(i);
+ ret = rte_efd_update(efd_table, socket_id,
+ (void *)&ip_dst, (efd_value_t)node_id);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u in "
+ "flow distributor table\n", i);
+ }
+
+ printf("EFD table: Adding 0x%x keys\n", num_flows);
+ }
+
+After initialization, packets are received from the enabled ports, and the IPv4
+address from the packets is used as a key to look up in the EFD table,
+which tells the node where the packet has to be distributed.
+
+.. code-block:: c
+
+ static void
+ process_packets(uint32_t port_num __rte_unused, struct rte_mbuf *pkts[],
+ uint16_t rx_count, unsigned int socket_id)
+ {
+ uint16_t i;
+ uint8_t node;
+ efd_value_t data[EFD_BURST_MAX];
+ const void *key_ptrs[EFD_BURST_MAX];
+
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[EFD_BURST_MAX];
+
+ for (i = 0; i < rx_count; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(pkts[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = (void *)&ipv4_dst_ip[i];
+ }
+
+ rte_efd_lookup_bulk(efd_table, socket_id, rx_count,
+ (const void **) key_ptrs, data);
+ for (i = 0; i < rx_count; i++) {
+ node = (uint8_t) ((uintptr_t)data[i]);
+
+ if (node >= num_nodes) {
+ /*
+ * Node is out of range, which means that
+ * flow has not been inserted
+ */
+ flow_dist_stats.drop++;
+ rte_pktmbuf_free(pkts[i]);
+ } else {
+ flow_dist_stats.distributed++;
+ enqueue_rx_packet(node, pkts[i]);
+ }
+ }
+
+ for (i = 0; i < num_nodes; i++)
+ flush_rx_queue(i);
+ }
+
+The burst of packets received is enqueued in temporary buffers (per node),
+and enqueued in the shared ring between the distributor and the node.
+After this, a new burst of packets is received and this process is
+repeated infinitely.
+
+.. code-block:: c
+
+ static void
+ flush_rx_queue(uint16_t node)
+ {
+ uint16_t j;
+ struct node *cl;
+
+ if (cl_rx_buf[node].count == 0)
+ return;
+
+ cl = &nodes[node];
+ if (rte_ring_enqueue_bulk(cl->rx_q, (void **)cl_rx_buf[node].buffer,
+ cl_rx_buf[node].count) != 0){
+ for (j = 0; j < cl_rx_buf[node].count; j++)
+ rte_pktmbuf_free(cl_rx_buf[node].buffer[j]);
+ cl->stats.rx_drop += cl_rx_buf[node].count;
+ } else
+ cl->stats.rx += cl_rx_buf[node].count;
+
+ cl_rx_buf[node].count = 0;
+ }
+
+The second process, the back-end node, receives the packets from the shared
+ring with the distributor and send them out, if they belong to the node.
+
+At initialization, it attaches to the distributor process memory, to have
+access to the shared ring, parameters and statistics.
+
+.. code-block:: c
+
+ rx_ring = rte_ring_lookup(get_rx_queue_name(node_id));
+ if (rx_ring == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get RX ring - "
+ "is distributor process running?\n");
+
+ mp = rte_mempool_lookup(PKTMBUF_POOL_NAME);
+ if (mp == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get mempool for mbufs\n");
+
+ mz = rte_memzone_lookup(MZ_SHARED_INFO);
+ if (mz == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get port info structure\n");
+ info = mz->addr;
+ tx_stats = &(info->tx_stats[node_id]);
+ filter_stats = &(info->filter_stats[node_id]);
+
+Then, the hash table that contains the flows that will be handled
+by the node is created and populated.
+
+.. code-block:: c
+
+ static struct rte_hash *
+ create_hash_table(const struct shared_info *info)
+ {
+ uint32_t num_flows_node = info->num_flows / info->num_nodes;
+ char name[RTE_HASH_NAMESIZE];
+ struct rte_hash *h;
+
+ /* create table */
+ struct rte_hash_parameters hash_params = {
+ .entries = num_flows_node * 2, /* table load = 50% */
+ .key_len = sizeof(uint32_t), /* Store IPv4 dest IP address */
+ .socket_id = rte_socket_id(),
+ .hash_func_init_val = 0,
+ };
+
+ snprintf(name, sizeof(name), "hash_table_%d", node_id);
+ hash_params.name = name;
+ h = rte_hash_create(&hash_params);
+
+ if (h == NULL)
+ rte_exit(EXIT_FAILURE,
+ "Problem creating the hash table for node %d\n",
+ node_id);
+ return h;
+ }
+
+ static void
+ populate_hash_table(const struct rte_hash *h, const struct shared_info *info)
+ {
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint32_t num_flows_node = 0;
+ uint64_t target_node;
+
+ /* Add flows in table */
+ for (i = 0; i < info->num_flows; i++) {
+ target_node = i % info->num_nodes;
+ if (target_node != node_id)
+ continue;
+
+ ip_dst = rte_cpu_to_be_32(i);
+
+ ret = rte_hash_add_key(h, (void *) &ip_dst);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u "
+ "in hash table\n", i);
+ else
+ num_flows_node++;
+
+ }
+
+ printf("Hash table: Adding 0x%x keys\n", num_flows_node);
+ }
+
+After initialization, packets are dequeued from the shared ring
+(from the distributor) and, like in the distributor process,
+the IPv4 address from the packets is used as a key to look up in the hash table.
+If there is a hit, packet is stored in a buffer, to be eventually transmitted
+in one of the enabled ports. If key is not there, packet is dropped, since the
+flow is not handled by the node.
+
+.. code-block:: c
+
+ static inline void
+ handle_packets(struct rte_hash *h, struct rte_mbuf **bufs, uint16_t num_packets)
+ {
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[PKT_READ_SIZE];
+ const void *key_ptrs[PKT_READ_SIZE];
+ unsigned int i;
+ int32_t positions[PKT_READ_SIZE] = {0};
+
+ for (i = 0; i < num_packets; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(bufs[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = &ipv4_dst_ip[i];
+ }
+ /* Check if packets belongs to any flows handled by this node */
+ rte_hash_lookup_bulk(h, key_ptrs, num_packets, positions);
+
+ for (i = 0; i < num_packets; i++) {
+ if (likely(positions[i] >= 0)) {
+ filter_stats->passed++;
+ transmit_packet(bufs[i]);
+ } else {
+ filter_stats->drop++;
+ /* Drop packet, as flow is not handled by this node */
+ rte_pktmbuf_free(bufs[i]);
+ }
+ }
+ }
+
+Finally, note that both processes updates statistics, such as transmitted, received
+and dropped packets, which are shown and refreshed by the distributor app.
+
+.. code-block:: c
+
+ static void
+ do_stats_display(void)
+ {
+ unsigned int i, j;
+ const char clr[] = {27, '[', '2', 'J', '\0'};
+ const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
+ uint64_t port_tx[RTE_MAX_ETHPORTS], port_tx_drop[RTE_MAX_ETHPORTS];
+ uint64_t node_tx[MAX_NODES], node_tx_drop[MAX_NODES];
+
+ /* to get TX stats, we need to do some summing calculations */
+ memset(port_tx, 0, sizeof(port_tx));
+ memset(port_tx_drop, 0, sizeof(port_tx_drop));
+ memset(node_tx, 0, sizeof(node_tx));
+ memset(node_tx_drop, 0, sizeof(node_tx_drop));
+
+ for (i = 0; i < num_nodes; i++) {
+ const struct tx_stats *tx = &info->tx_stats[i];
+
+ for (j = 0; j < info->num_ports; j++) {
+ const uint64_t tx_val = tx->tx[info->id[j]];
+ const uint64_t drop_val = tx->tx_drop[info->id[j]];
+
+ port_tx[j] += tx_val;
+ port_tx_drop[j] += drop_val;
+ node_tx[i] += tx_val;
+ node_tx_drop[i] += drop_val;
+ }
+ }
+
+ /* Clear screen and move to top left */
+ printf("%s%s", clr, topLeft);
+
+ printf("PORTS\n");
+ printf("-----\n");
+ for (i = 0; i < info->num_ports; i++)
+ printf("Port %u: '%s'\t", (unsigned int)info->id[i],
+ get_printable_mac_addr(info->id[i]));
+ printf("\n\n");
+ for (i = 0; i < info->num_ports; i++) {
+ printf("Port %u - rx: %9"PRIu64"\t"
+ "tx: %9"PRIu64"\n",
+ (unsigned int)info->id[i], info->rx_stats.rx[i],
+ port_tx[i]);
+ }
+
+ printf("\nFLOW DISTRIBUTOR\n");
+ printf("-----\n");
+ printf("distributed: %9"PRIu64", drop: %9"PRIu64"\n",
+ flow_dist_stats.distributed, flow_dist_stats.drop);
+
+ printf("\nNODES\n");
+ printf("-------\n");
+ for (i = 0; i < num_nodes; i++) {
+ const unsigned long long rx = nodes[i].stats.rx;
+ const unsigned long long rx_drop = nodes[i].stats.rx_drop;
+ const struct filter_stats *filter = &info->filter_stats[i];
+
+ printf("Node %2u - rx: %9llu, rx_drop: %9llu\n"
+ " tx: %9"PRIu64", tx_drop: %9"PRIu64"\n"
+ " filter_passed: %9"PRIu64", "
+ "filter_drop: %9"PRIu64"\n",
+ i, rx, rx_drop, node_tx[i], node_tx_drop[i],
+ filter->passed, filter->drop);
+ }
+
+ printf("\n");
+ }
diff --git a/doc/guides/sample_app_ug/img/flow_distributor.svg b/doc/guides/sample_app_ug/img/flow_distributor.svg
new file mode 100644
index 0000000..9aee30b
--- /dev/null
+++ b/doc/guides/sample_app_ug/img/flow_distributor.svg
@@ -0,0 +1,1254 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i6.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="8.2496in" height="5.89673in"
+ viewBox="0 0 593.971 424.565" xml:space="preserve" color-interpolation-filters="sRGB" class="st27">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st5 {fill:#feffff;font-family:Calibri;font-size:0.75em}
+ .st6 {fill:none;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st7 {fill:none;stroke:#2e75b5;stroke-width:2.25}
+ .st8 {fill:#305497;stroke:#2e75b5;stroke-width:1}
+ .st9 {fill:#feffff;font-family:Calibri;font-size:0.833336em;font-weight:bold}
+ .st10 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2)}
+ .st11 {fill:#5b9bd5}
+ .st12 {stroke:#c7c8c8;stroke-width:0.25}
+ .st13 {fill:#acccea;stroke:#c7c8c8;stroke-width:0.25}
+ .st14 {fill:#feffff;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st15 {fill:#ed7d31;stroke:#c7c8c8;stroke-width:0.25}
+ .st16 {fill:#deebf6;stroke:#c7c8c8;stroke-width:0.25}
+ .st17 {marker-end:url(#mrkr5-212);stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st18 {fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st19 {fill:none;stroke:#2e75b5;stroke-width:1}
+ .st20 {fill:#5b9bd5;font-family:Calibri;font-size:1.00001em}
+ .st21 {fill:none;stroke:none;stroke-width:0.25}
+ .st22 {font-size:1em}
+ .st23 {fill:#ffffff}
+ .st24 {stroke:#5b9bd5;stroke-dasharray:1.5,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st25 {marker-end:url(#mrkr5-444);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st26 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st27 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-212" class="st18" v:arrowType="5" v:arrowSize="2" v:setback="5.8" refX="-5.8" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ <marker id="mrkr5-444" class="st26" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(319.501,-335.688)">
+ <title>Rectangle.58</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow3-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape4-7" v:mID="4" v:groupContext="shape" transform="translate(353.251,-335.688)">
+ <title>Rectangle.59</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow4-8" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape5-13" v:mID="5" v:groupContext="shape" transform="translate(400.501,-335.688)">
+ <title>Rectangle.60</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow5-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape6-19" v:mID="6" v:groupContext="shape" transform="translate(434.251,-335.688)">
+ <title>Rectangle.61</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow6-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape7-25" v:mID="7" v:groupContext="shape" transform="translate(481.501,-335.688)">
+ <title>Rectangle.62</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow7-26" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape8-30" v:mID="8" v:groupContext="shape" transform="translate(515.251,-335.688)">
+ <title>Rectangle.63</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow8-31" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape9-35" v:mID="9" v:groupContext="shape" transform="translate(319.501,-313.188)">
+ <title>Rectangle.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow9-36" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape10-40" v:mID="10" v:groupContext="shape" transform="translate(353.251,-313.188)">
+ <title>Rectangle.65</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow10-41" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape11-45" v:mID="11" v:groupContext="shape" transform="translate(400.501,-313.188)">
+ <title>Rectangle.66</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow11-46" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape12-50" v:mID="12" v:groupContext="shape" transform="translate(434.251,-313.188)">
+ <title>Rectangle.67</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow12-51" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape13-55" v:mID="13" v:groupContext="shape" transform="translate(481.501,-313.188)">
+ <title>Rectangle.68</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow13-56" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape14-60" v:mID="14" v:groupContext="shape" transform="translate(515.251,-313.188)">
+ <title>Rectangle.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow14-61" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape15-65" v:mID="15" v:groupContext="shape" transform="translate(319.501,-277.188)">
+ <title>Rectangle.70</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow15-66" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape16-71" v:mID="16" v:groupContext="shape" transform="translate(353.251,-277.188)">
+ <title>Rectangle.71</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow16-72" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape17-77" v:mID="17" v:groupContext="shape" transform="translate(400.501,-277.188)">
+ <title>Rectangle.72</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow17-78" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape18-83" v:mID="18" v:groupContext="shape" transform="translate(434.251,-277.188)">
+ <title>Rectangle.73</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow18-84" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape19-89" v:mID="19" v:groupContext="shape" transform="translate(481.501,-277.188)">
+ <title>Rectangle.74</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow19-90" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape20-95" v:mID="20" v:groupContext="shape" transform="translate(515.251,-277.188)">
+ <title>Rectangle.75</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow20-96" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape21-101" v:mID="21" v:groupContext="shape" transform="translate(319.501,-240.687)">
+ <title>Rectangle.76</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow21-102" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape22-106" v:mID="22" v:groupContext="shape" transform="translate(353.251,-240.687)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow22-107" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape23-111" v:mID="23" v:groupContext="shape" transform="translate(400.501,-240.687)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow23-112" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape24-116" v:mID="24" v:groupContext="shape" transform="translate(434.251,-240.687)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow24-117" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape25-121" v:mID="25" v:groupContext="shape" transform="translate(481.501,-240.687)">
+ <title>Rectangle.80</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow25-122" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape26-127" v:mID="26" v:groupContext="shape" transform="translate(515.251,-240.687)">
+ <title>Rectangle.81</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow26-128" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape27-133" v:mID="27" v:groupContext="shape" transform="translate(317.251,-231.687)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow27-134" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st6"/>
+ </g>
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st7"/>
+ </g>
+ <g id="shape28-138" v:mID="28" v:groupContext="shape" transform="translate(328.501,-362.688)">
+ <title>Sheet.28</title>
+ <desc>Local Table for N Specific Flows Serviced at Node 1</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="110.423" cy="418.94" width="220.85" height="11.25"/>
+ <rect x="0" y="413.315" width="220.846" height="11.25" class="st8"/>
+ <text x="5.77" y="421.94" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Local Table for N Specific Flows Serviced at Node 1</text> </g>
+ <g id="group34-141" transform="translate(66.0294,-165.569)" v:mID="34" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Device)"/>
+ <v:cp v:nameU="SubShapeType" v:lbl="SubShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Load balancer)"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Load balancer</title>
+ <g id="shape35-142" v:mID="35" v:groupContext="shape" transform="translate(0,-7.33146)">
+ <title>Sheet.35</title>
+ <g id="shadow35-143" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23 Z" class="st10"/>
+ <path d="M0 377.86 L72 377.86" class="st6"/>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23" class="st6"/>
+ </g>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23 Z" class="st11"/>
+ <path d="M0 377.86 L72 377.86" class="st12"/>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23" class="st12"/>
+ </g>
+ <g id="shape36-152" v:mID="36" v:groupContext="shape" transform="translate(8.03054,-12.9324)">
+ <title>Sheet.36</title>
+ <g id="shadow36-153" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M45.34 421.81 L41.2 422.66 L44.12 424.56 L49.68 423.16 L48.75 417.51 L45.8 415.59 L46.69 419.68 L36.97
+ 413.34 L35.6 415.45 L45.34 421.81 ZM50.83 405.36 L39.2 405.36 L39.2 407.88 L50.8 407.88 L47.82 410.83
+ L51.34 410.83 L55.21 406.61 L51.32 402.39 L47.83 402.39 L50.83 405.36 ZM46.49 392.01 L36.75 398.37
+ L38.13 400.48 L47.84 394.14 L46.96 398.23 L49.91 396.31 L50.84 390.66 L45.28 389.26 L42.36 391.16
+ L46.49 392.01 ZM27.71 397.16 C22.66 397.16 18.58 401.25 18.58 406.29 C18.58 411.33 22.66 415.42
+ 27.71 415.42 C32.75 415.42 36.84 411.33 36.84 406.29 C36.84 401.25 32.75 397.16 27.71 397.16 ZM27.71
+ 400.04 C31.15 400.04 33.96 402.84 33.96 406.29 C33.96 409.74 31.15 412.54 27.71 412.54 C24.26 412.54
+ 21.46 409.74 21.46 406.29 C21.46 402.84 24.26 400.04 27.71 400.04 ZM11.64 405.04 L0 405.04 L0 407.56
+ L11.6 407.56 L8.62 410.51 L12.14 410.51 L16.01 406.29 L12.12 402.07 L8.64 402.07 L11.64 405.04 Z"
+ class="st2"/>
+ </g>
+ <path d="M45.34 421.81 L41.2 422.66 L44.12 424.56 L49.68 423.16 L48.75 417.51 L45.8 415.59 L46.69 419.68 L36.97 413.34
+ L35.6 415.45 L45.34 421.81 ZM50.83 405.36 L39.2 405.36 L39.2 407.88 L50.8 407.88 L47.82 410.83 L51.34
+ 410.83 L55.21 406.61 L51.32 402.39 L47.83 402.39 L50.83 405.36 ZM46.49 392.01 L36.75 398.37 L38.13 400.48
+ L47.84 394.14 L46.96 398.23 L49.91 396.31 L50.84 390.66 L45.28 389.26 L42.36 391.16 L46.49 392.01 ZM27.71
+ 397.16 C22.66 397.16 18.58 401.25 18.58 406.29 C18.58 411.33 22.66 415.42 27.71 415.42 C32.75 415.42
+ 36.84 411.33 36.84 406.29 C36.84 401.25 32.75 397.16 27.71 397.16 ZM27.71 400.04 C31.15 400.04 33.96
+ 402.84 33.96 406.29 C33.96 409.74 31.15 412.54 27.71 412.54 C24.26 412.54 21.46 409.74 21.46 406.29
+ C21.46 402.84 24.26 400.04 27.71 400.04 ZM11.64 405.04 L0 405.04 L0 407.56 L11.6 407.56 L8.62 410.51
+ L12.14 410.51 L16.01 406.29 L12.12 402.07 L8.64 402.07 L11.64 405.04 Z" class="st13"/>
+ </g>
+ </g>
+ <g id="shape37-157" v:mID="37" v:groupContext="shape" transform="translate(21.0294,-45.4375)">
+ <title>Rectangle</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow37-158" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="336.433" width="135" height="88.1315" class="st2"/>
+ </g>
+ <rect x="0" y="336.433" width="135" height="88.1315" class="st3"/>
+ </g>
+ <g id="shape38-162" v:mID="38" v:groupContext="shape" transform="translate(34.693,-126.438)">
+ <title>Sheet.38</title>
+ <desc>EFD Table</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="49.3364" cy="415.565" width="98.68" height="18"/>
+ <rect x="0" y="406.565" width="98.6728" height="18" class="st8"/>
+ <text x="24.87" y="419.17" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>EFD Table</text> </g>
+ <g id="shape39-165" v:mID="39" v:groupContext="shape" transform="translate(30.0294,-99.4375)">
+ <title>Rectangle.39</title>
+ <desc>Group_id</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="26.9182" cy="415.565" width="53.84" height="18"/>
+ <g id="shadow39-166" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st15"/>
+ <text x="7.87" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group_id</text> </g>
+ <g id="shape40-171" v:mID="40" v:groupContext="shape" transform="translate(93.193,-99.4375)">
+ <title>Rectangle.40</title>
+ <desc>Hash index</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="26.9182" cy="415.565" width="53.84" height="18"/>
+ <g id="shadow40-172" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st15"/>
+ <text x="4.64" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Hash index</text> </g>
+ <g id="shape41-177" v:mID="41" v:groupContext="shape" transform="translate(30.193,-82.4275)">
+ <title>Rectangle.41</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow41-178" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape42-182" v:mID="42" v:groupContext="shape" transform="translate(30.193,-66.8125)">
+ <title>Rectangle.42</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow42-183" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape43-187" v:mID="43" v:groupContext="shape" transform="translate(30.1112,-52.1875)">
+ <title>Rectangle.43</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow43-188" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape44-192" v:mID="44" v:groupContext="shape" transform="translate(93.0294,-81.4375)">
+ <title>Rectangle.44</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow44-193" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape45-197" v:mID="45" v:groupContext="shape" transform="translate(93.193,-66.8125)">
+ <title>Rectangle.45</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow45-198" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape46-202" v:mID="46" v:groupContext="shape" transform="translate(93.193,-52.1875)">
+ <title>Rectangle.46</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow46-203" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape47-207" v:mID="47" v:groupContext="shape" transform="translate(374.924,544.022) rotate(135)">
+ <title>Sheet.47</title>
+ <path d="M-0 417.75 A40.674 18.0151 -156.2 0 0 40.24 422.15 L40.49 421.89" class="st17"/>
+ </g>
+ <g id="shape48-213" v:mID="48" v:groupContext="shape" transform="translate(21.0294,-19)">
+ <title>Sheet.48</title>
+ <desc>Supports X*N Flows</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="415.565" width="135" height="18"/>
+ <rect x="0" y="406.565" width="135" height="18" class="st19"/>
+ <text x="19.05" y="419.17" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Supports X*N Flows</text> </g>
+ <g id="shape49-216" v:mID="49" v:groupContext="shape" transform="translate(48.0294,-229.938)">
+ <title>Sheet.49</title>
+ <desc>Frontend Server or Load Balancer</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="400.94" width="108" height="47.25"/>
+ <rect x="0" y="377.315" width="108" height="47.25" class="st21"/>
+ <text x="14.56" y="397.34" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Frontend Server<v:newlineChar/><tspan
+ x="13.16" dy="1.2em" class="st22">or Load Balancer </tspan> </text> </g>
+ <g id="group51-220" transform="translate(223.876,-310.938)" v:mID="51" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server</title>
+ <g id="shape52-221" v:mID="52" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.52</title>
+ <g id="shadow52-222" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape53-226" v:mID="53" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.53</title>
+ <g id="shadow53-227" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape54-231" v:mID="54" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.54</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow54-232" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape59-239" v:mID="59" v:groupContext="shape" transform="translate(277.876,-373.938)">
+ <title>Sheet.59</title>
+ <path d="M-0 424.56 A111.108 53.2538 42.31 0 1 93.83 421.21 L94.14 421.41" class="st17"/>
+ </g>
+ <g id="shape60-244" v:mID="60" v:groupContext="shape" transform="translate(205.876,-283.938)">
+ <title>Sheet.60</title>
+ <desc>Backend Server 1</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.93" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server 1</text> </g>
+ <g id="group61-247" transform="translate(223.876,-207.438)" v:mID="61" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server.61</title>
+ <g id="shape62-248" v:mID="62" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.62</title>
+ <g id="shadow62-249" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape63-253" v:mID="63" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.63</title>
+ <g id="shadow63-254" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape64-258" v:mID="64" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow64-259" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape65-266" v:mID="65" v:groupContext="shape" transform="translate(205.876,-180.437)">
+ <title>Sheet.65</title>
+ <desc>Backend Server 2</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.93" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server 2</text> </g>
+ <g id="group66-269" transform="translate(219.029,-58.9375)" v:mID="66" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server.66</title>
+ <g id="shape67-270" v:mID="67" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.67</title>
+ <g id="shadow67-271" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape68-275" v:mID="68" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.68</title>
+ <g id="shadow68-276" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape69-280" v:mID="69" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow69-281" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape70-288" v:mID="70" v:groupContext="shape" transform="translate(201.029,-26.056)">
+ <title>Sheet.70</title>
+ <desc>Backend Server X</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.86" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server X</text> </g>
+ <g id="shape71-291" v:mID="71" v:groupContext="shape" transform="translate(684.44,239.627) rotate(90)">
+ <title>Sheet.71</title>
+ <path d="M0 424.56 L45 424.56" class="st24"/>
+ </g>
+ <g id="shape72-294" v:mID="72" v:groupContext="shape" transform="translate(6.85967,-22.443) rotate(-38.1076)">
+ <title>Sheet.72</title>
+ <path d="M-0 424.56 A96.1331 44.4001 55.03 0 1 68.24 420.56 L68.51 420.79" class="st17"/>
+ </g>
+ <g id="shape73-299" v:mID="73" v:groupContext="shape" transform="translate(328.501,-135.937)">
+ <title>Rectangle.73</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow73-300" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape74-305" v:mID="74" v:groupContext="shape" transform="translate(362.251,-135.937)">
+ <title>Rectangle.74</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow74-306" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape75-311" v:mID="75" v:groupContext="shape" transform="translate(409.501,-135.937)">
+ <title>Rectangle.75</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow75-312" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape76-317" v:mID="76" v:groupContext="shape" transform="translate(443.251,-135.937)">
+ <title>Rectangle.76</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow76-318" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape77-323" v:mID="77" v:groupContext="shape" transform="translate(490.501,-135.937)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow77-324" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape78-328" v:mID="78" v:groupContext="shape" transform="translate(524.251,-135.937)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow78-329" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape79-333" v:mID="79" v:groupContext="shape" transform="translate(328.501,-113.437)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow79-334" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape80-338" v:mID="80" v:groupContext="shape" transform="translate(362.251,-113.437)">
+ <title>Rectangle.80</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow80-339" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape81-343" v:mID="81" v:groupContext="shape" transform="translate(409.501,-113.437)">
+ <title>Rectangle.81</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow81-344" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape82-348" v:mID="82" v:groupContext="shape" transform="translate(443.251,-113.437)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow82-349" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape83-353" v:mID="83" v:groupContext="shape" transform="translate(490.501,-113.437)">
+ <title>Rectangle.83</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow83-354" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape84-358" v:mID="84" v:groupContext="shape" transform="translate(524.251,-113.437)">
+ <title>Rectangle.84</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow84-359" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape85-363" v:mID="85" v:groupContext="shape" transform="translate(328.501,-77.4375)">
+ <title>Rectangle.85</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow85-364" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape86-369" v:mID="86" v:groupContext="shape" transform="translate(362.251,-77.4375)">
+ <title>Rectangle.86</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow86-370" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape87-375" v:mID="87" v:groupContext="shape" transform="translate(409.501,-77.4375)">
+ <title>Rectangle.87</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow87-376" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape88-381" v:mID="88" v:groupContext="shape" transform="translate(443.251,-77.4375)">
+ <title>Rectangle.88</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow88-382" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape89-387" v:mID="89" v:groupContext="shape" transform="translate(490.501,-77.4375)">
+ <title>Rectangle.89</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow89-388" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape90-393" v:mID="90" v:groupContext="shape" transform="translate(524.251,-77.4375)">
+ <title>Rectangle.90</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow90-394" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape91-399" v:mID="91" v:groupContext="shape" transform="translate(328.501,-40.9375)">
+ <title>Rectangle.91</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow91-400" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape92-404" v:mID="92" v:groupContext="shape" transform="translate(362.251,-40.9375)">
+ <title>Rectangle.92</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow92-405" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape93-409" v:mID="93" v:groupContext="shape" transform="translate(409.501,-40.9375)">
+ <title>Rectangle.93</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow93-410" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape94-414" v:mID="94" v:groupContext="shape" transform="translate(443.251,-40.9375)">
+ <title>Rectangle.94</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow94-415" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape95-419" v:mID="95" v:groupContext="shape" transform="translate(490.501,-40.9375)">
+ <title>Rectangle.95</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow95-420" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape96-425" v:mID="96" v:groupContext="shape" transform="translate(524.251,-40.9375)">
+ <title>Rectangle.96</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow96-426" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape97-431" v:mID="97" v:groupContext="shape" transform="translate(326.251,-31.9375)">
+ <title>Rectangle.97</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow97-432" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st6"/>
+ </g>
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st7"/>
+ </g>
+ <g id="shape98-436" v:mID="98" v:groupContext="shape" transform="translate(337.501,-162.938)">
+ <title>Sheet.98</title>
+ <desc>Local Table for N Specific Flows Serviced at Node X</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="110.423" cy="418.94" width="220.85" height="11.25"/>
+ <rect x="0" y="413.315" width="220.846" height="11.25" class="st8"/>
+ <text x="5.55" y="421.94" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Local Table for N Specific Flows Serviced at Node X</text> </g>
+ <g id="shape99-439" v:mID="99" v:groupContext="shape" transform="translate(-204.342,-29.4449) rotate(-53.7462)">
+ <title>Sheet.99</title>
+ <path d="M0 424.56 L160.37 424.56" class="st25"/>
+ </g>
+ <g id="shape100-445" v:mID="100" v:groupContext="shape" transform="translate(-37.6568,-164.882) rotate(-24.444)">
+ <title>Sheet.100</title>
+ <path d="M0 424.56 L101.71 424.56" class="st25"/>
+ </g>
+ <g id="shape101-450" v:mID="101" v:groupContext="shape" transform="translate(464.049,-50.8578) rotate(50.099)">
+ <title>Sheet.101</title>
+ <path d="M0 424.56 L139.8 424.56" class="st25"/>
+ </g>
+ <g id="shape102-455" v:mID="102" v:groupContext="shape" transform="translate(372.376,-207.438)">
+ <title>Sheet.102</title>
+ <desc>Supports N Flows</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="415.565" width="135" height="18"/>
+ <rect x="0" y="406.565" width="135" height="18" class="st19"/>
+ <text x="25.15" y="419.17" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Supports N Flows</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/sample_app_ug/index.rst b/doc/guides/sample_app_ug/index.rst
index 775e2f7..260f6a5 100644
--- a/doc/guides/sample_app_ug/index.rst
+++ b/doc/guides/sample_app_ug/index.rst
@@ -57,6 +57,7 @@ Sample Applications User Guides
l3_forward_virtual
link_status_intr
load_balancer
+ flow_distributor
multi_process
qos_metering
qos_scheduler
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [dpdk-dev] [PATCH v4 4/5] doc: add EFD library section in Programmers guide
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 4/5] doc: add EFD library section in Programmers guide Pablo de Lara
@ 2017-01-16 4:15 ` Jerin Jacob
2017-01-16 15:33 ` De Lara Guarch, Pablo
0 siblings, 1 reply; 63+ messages in thread
From: Jerin Jacob @ 2017-01-16 4:15 UTC (permalink / raw)
To: Pablo de Lara; +Cc: dev, Sameh Gobriel
On Sun, Jan 15, 2017 at 12:04:34PM +0000, Pablo de Lara wrote:
> Signed-off-by: Sameh Gobriel <sameh.gobriel@intel.com>
> Acked-by: Christian Maciocco <christian.maciocco@intel.com>
Looks like trailing white space error on git am. Please check it
➜ [dpdk] $ pwclient git-am 19363
Applying patch #19363 using 'git am'
Description: [dpdk-dev,v4,4/5] doc: add EFD library section in
Programmers guide
Applying: doc: add EFD library section in Programmers guide
/export/upstream/dpdk/.git/rebase-apply/patch:342: trailing whitespace.
``rte_efd_delete()`` can be used. The function returns zero upon success
warning: 1 line adds whitespace errors.
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [dpdk-dev] [PATCH v4 1/5] efd: new Elastic Flow Distributor library
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 1/5] efd: new Elastic Flow Distributor library Pablo de Lara
@ 2017-01-16 4:25 ` Jerin Jacob
2017-01-16 15:34 ` De Lara Guarch, Pablo
0 siblings, 1 reply; 63+ messages in thread
From: Jerin Jacob @ 2017-01-16 4:25 UTC (permalink / raw)
To: Pablo de Lara; +Cc: dev, Byron Marohn, Saikrishna Edupuganti
On Sun, Jan 15, 2017 at 12:04:31PM +0000, Pablo de Lara wrote:
> Elastic Flow Distributor (EFD) is a distributor library that uses
> perfect hashing to determine a target/value for a given incoming flow key.
> It has the following advantages:
>
> - First, because it uses perfect hashing, it does not store
> the key itself and hence lookup performance is not dependent
> on the key size.
>
> - Second, the target/value can be any arbitrary value hence
> the system designer and/or operator can better optimize service rates
> and inter-cluster network traffic locating.
>
> - Third, since the storage requirement is much smaller than a hash-based
> flow table (i.e. better fit for CPU cache), EFD can scale to
> millions of flow keys.
> Finally, with current optimized library implementation performance
> is fully scalable with number of CPU cores.
>
> Signed-off-by: Byron Marohn <byron.marohn@intel.com>
> Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
> Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
> Acked-by: Christian Maciocco <christian.maciocco@intel.com>
> ---
> +#if (RTE_EFD_VALUE_NUM_BITS == 8 || RTE_EFD_VALUE_NUM_BITS == 16 || \
> + RTE_EFD_VALUE_NUM_BITS == 24 || RTE_EFD_VALUE_NUM_BITS == 32)
> +#define EFD_LOAD_SI128(val) _mm_load_si128(val)
> +#else
> +#define EFD_LOAD_SI128(val) _mm_lddqu_si128(val)
> +#endif
> +
> +static inline efd_value_t
> +efd_lookup_internal(const struct efd_online_group_entry * const group,
> + const uint32_t hash_val_a, const uint32_t hash_val_b,
> + enum rte_efd_compare_function cmp_fn)
> +{
> + efd_value_t value = 0;
> + uint32_t i;
> +
> + switch (cmp_fn) {
> +#ifdef RTE_MACHINE_CPUFLAG_AVX2
> + case RTE_HASH_COMPARE_AVX2:
> +
> + i = 0;
> + __m256i vhash_val_a = _mm256_set1_epi32(hash_val_a);
> + __m256i vhash_val_b = _mm256_set1_epi32(hash_val_b);
> +
Could you please abstract and move SIMD specific code to another file like other
libraries(example: lib_acl) to enable smooth integration with neon and altivec
SIMD implementations in future.
> + for (; i < RTE_EFD_VALUE_NUM_BITS; i += 8) {
> + __m256i vhash_idx =
> + _mm256_cvtepu16_epi32(EFD_LOAD_SI128(
> + (__m128i const *) &group->hash_idx[i]));
> + __m256i vlookup_table = _mm256_cvtepu16_epi32(
> + EFD_LOAD_SI128((__m128i const *)
> + &group->lookup_table[i]));
> + __m256i vhash = _mm256_add_epi32(vhash_val_a,
> + _mm256_mullo_epi32(vhash_idx, vhash_val_b));
> + __m256i vbucket_idx = _mm256_srli_epi32(vhash,
> + EFD_LOOKUPTBL_SHIFT);
> + __m256i vresult = _mm256_srlv_epi32(vlookup_table,
> + vbucket_idx);
> +
> + value |= (_mm256_movemask_ps(
> + (__m256) _mm256_slli_epi32(vresult, 31))
> + & ((1 << (RTE_EFD_VALUE_NUM_BITS - i)) - 1)) << i;
> + }
> + break;
> +#endif
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v5 0/5] Elastic Flow Distributor
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 0/5] Elastic Flow Distributor Pablo de Lara
` (4 preceding siblings ...)
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 5/5] doc: add flow distributor guide Pablo de Lara
@ 2017-01-16 9:43 ` Pablo de Lara
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 1/5] efd: new Elastic Flow Distributor library Pablo de Lara
` (6 more replies)
5 siblings, 7 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-16 9:43 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara
EFD is a distributor library that uses perfect hashing to determine a
target/value for a given incoming flow key. It has the following advantages:
first, because it uses perfect hashing it does not store the key itself and
hence lookup performance is not dependent on the key size. Second, the
target/value can be any arbitrary value hence the system designer and/or
operator can better optimize service rates and inter-cluster network traffic
locating. Third, since the storage requirement is much smaller than a
hash-based flow table (i.e. better fit for CPU cache), EFD can scale to millions
of flow keys. Finally, with current optimized library implementation performance
is fully scalable with number of CPU cores.
The basic idea of EFD is when a given key is to be inserted, a family of hash
functions is searched until the correct hash function that maps the input key to
the correct value is found. However, rather than explicitly storing all keys and
their associated values, EFD stores only indices of hash functions that map keys
to values, and thereby consumes much less space than conventional flow-based
tables. The lookup operation is very simple, similar to computational-based
scheme, given an input key the lookup operation is reduced to hashing that key
with the correct hash function.
Intuitively, finding a hash function that maps each of a large number (millions)
of input keys to the correct output value is effectively impossible, as a result
EFD, breaks the problem into smaller pieces (divide and conquer). EFD divides
the entire input key set into many small groups. Each group consists of
approximately 20-28 keys (a configurable parameter for the library), then, for
each small group, a brute force search to find a hash function that produces the
correct outputs for each key in the group.
It should be mentioned that since in the online lookup table for EFD doesn’t
store the key itself, the size of the EFD table is independent of the key size
and hence EFD lookup performance which is almost constant irrespective of the
length of the key which is a highly desirable feature especially for longer
keys.
Library code is included in the patch, plus an sample application that shows
how the library can be used.
RFC for this library was already sent:
http://dpdk.org/ml/archives/dev/2016-October/049238.html
Changes in v5:
- Removed trailing whitespace in doc
Changes in v4:
- Added References section
Changes in v3:
- Fixed SVG files
- Fixed copyright dates
- Reformatted parts of documentation
Changes in v2:
- Added documentation for library and sample app
- Fixed checkpatch errors/warnings
- Added functional and performance tests
- Made key size variable at runtime
- Made code multi-architecture compatible at runtime
Pablo de Lara (5):
efd: new Elastic Flow Distributor library
app/test: add EFD functional and perf tests
examples/flow_distributor: sample app to demonstrate EFD usage
doc: add EFD library section in Programmers guide
doc: add flow distributor guide
MAINTAINERS | 9 +
app/test/Makefile | 5 +-
app/test/test_efd.c | 494 ++++++++
app/test/test_efd_perf.c | 407 +++++++
config/common_base | 5 +
doc/api/doxy-api-index.md | 3 +-
doc/api/doxy-api.conf | 1 +
doc/api/examples.dox | 4 +
doc/guides/prog_guide/efd_lib.rst | 440 +++++++
doc/guides/prog_guide/img/efd_i1.svg | 130 ++
doc/guides/prog_guide/img/efd_i10.svg | 384 ++++++
doc/guides/prog_guide/img/efd_i11.svg | 319 +++++
doc/guides/prog_guide/img/efd_i12.svg | 1008 +++++++++++++++
doc/guides/prog_guide/img/efd_i2.svg | 280 +++++
doc/guides/prog_guide/img/efd_i3.svg | 634 ++++++++++
doc/guides/prog_guide/img/efd_i4.svg | 203 +++
doc/guides/prog_guide/img/efd_i5.svg | 183 +++
doc/guides/prog_guide/img/efd_i6.svg | 1254 +++++++++++++++++++
doc/guides/prog_guide/img/efd_i7.svg | 790 ++++++++++++
doc/guides/prog_guide/img/efd_i8.svg | 182 +++
doc/guides/prog_guide/img/efd_i9.svg | 390 ++++++
doc/guides/prog_guide/index.rst | 23 +
doc/guides/rel_notes/release_17_02.rst | 15 +
doc/guides/sample_app_ug/flow_distributor.rst | 494 ++++++++
doc/guides/sample_app_ug/img/flow_distributor.svg | 1254 +++++++++++++++++++
doc/guides/sample_app_ug/index.rst | 1 +
examples/Makefile | 1 +
examples/flow_distributor/Makefile | 44 +
examples/flow_distributor/distributor/Makefile | 57 +
examples/flow_distributor/distributor/args.c | 200 +++
examples/flow_distributor/distributor/args.h | 39 +
examples/flow_distributor/distributor/init.c | 371 ++++++
examples/flow_distributor/distributor/init.h | 76 ++
examples/flow_distributor/distributor/main.c | 362 ++++++
examples/flow_distributor/node/Makefile | 48 +
examples/flow_distributor/node/node.c | 417 +++++++
examples/flow_distributor/shared/common.h | 99 ++
lib/Makefile | 3 +-
lib/librte_eal/common/include/rte_log.h | 3 +-
lib/librte_efd/Makefile | 56 +
lib/librte_efd/rte_efd.c | 1354 +++++++++++++++++++++
lib/librte_efd/rte_efd.h | 294 +++++
lib/librte_efd/rte_efd_version.map | 12 +
mk/rte.app.mk | 3 +-
44 files changed, 12346 insertions(+), 5 deletions(-)
create mode 100644 app/test/test_efd.c
create mode 100644 app/test/test_efd_perf.c
create mode 100644 doc/guides/prog_guide/efd_lib.rst
create mode 100644 doc/guides/prog_guide/img/efd_i1.svg
create mode 100644 doc/guides/prog_guide/img/efd_i10.svg
create mode 100644 doc/guides/prog_guide/img/efd_i11.svg
create mode 100644 doc/guides/prog_guide/img/efd_i12.svg
create mode 100644 doc/guides/prog_guide/img/efd_i2.svg
create mode 100644 doc/guides/prog_guide/img/efd_i3.svg
create mode 100644 doc/guides/prog_guide/img/efd_i4.svg
create mode 100644 doc/guides/prog_guide/img/efd_i5.svg
create mode 100644 doc/guides/prog_guide/img/efd_i6.svg
create mode 100644 doc/guides/prog_guide/img/efd_i7.svg
create mode 100644 doc/guides/prog_guide/img/efd_i8.svg
create mode 100644 doc/guides/prog_guide/img/efd_i9.svg
create mode 100644 doc/guides/sample_app_ug/flow_distributor.rst
create mode 100644 doc/guides/sample_app_ug/img/flow_distributor.svg
create mode 100644 examples/flow_distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/args.c
create mode 100644 examples/flow_distributor/distributor/args.h
create mode 100644 examples/flow_distributor/distributor/init.c
create mode 100644 examples/flow_distributor/distributor/init.h
create mode 100644 examples/flow_distributor/distributor/main.c
create mode 100644 examples/flow_distributor/node/Makefile
create mode 100644 examples/flow_distributor/node/node.c
create mode 100644 examples/flow_distributor/shared/common.h
create mode 100644 lib/librte_efd/Makefile
create mode 100644 lib/librte_efd/rte_efd.c
create mode 100644 lib/librte_efd/rte_efd.h
create mode 100644 lib/librte_efd/rte_efd_version.map
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v5 1/5] efd: new Elastic Flow Distributor library
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 0/5] Elastic Flow Distributor Pablo de Lara
@ 2017-01-16 9:43 ` Pablo de Lara
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 2/5] app/test: add EFD functional and perf tests Pablo de Lara
` (5 subsequent siblings)
6 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-16 9:43 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Byron Marohn, Saikrishna Edupuganti
Elastic Flow Distributor (EFD) is a distributor library that uses
perfect hashing to determine a target/value for a given incoming flow key.
It has the following advantages:
- First, because it uses perfect hashing, it does not store
the key itself and hence lookup performance is not dependent
on the key size.
- Second, the target/value can be any arbitrary value hence
the system designer and/or operator can better optimize service rates
and inter-cluster network traffic locating.
- Third, since the storage requirement is much smaller than a hash-based
flow table (i.e. better fit for CPU cache), EFD can scale to
millions of flow keys.
Finally, with current optimized library implementation performance
is fully scalable with number of CPU cores.
Signed-off-by: Byron Marohn <byron.marohn@intel.com>
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 5 +
config/common_base | 5 +
doc/api/doxy-api-index.md | 3 +-
doc/api/doxy-api.conf | 1 +
doc/guides/rel_notes/release_17_02.rst | 12 +
lib/Makefile | 3 +-
lib/librte_eal/common/include/rte_log.h | 3 +-
lib/librte_efd/Makefile | 56 ++
lib/librte_efd/rte_efd.c | 1354 +++++++++++++++++++++++++++++++
lib/librte_efd/rte_efd.h | 294 +++++++
lib/librte_efd/rte_efd_version.map | 12 +
mk/rte.app.mk | 3 +-
12 files changed, 1747 insertions(+), 4 deletions(-)
create mode 100644 lib/librte_efd/Makefile
create mode 100644 lib/librte_efd/rte_efd.c
create mode 100644 lib/librte_efd/rte_efd.h
create mode 100644 lib/librte_efd/rte_efd_version.map
diff --git a/MAINTAINERS b/MAINTAINERS
index 9645c9b..9c60d67 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -528,6 +528,11 @@ F: app/test/test_acl.*
F: examples/l3fwd-acl/
F: doc/guides/sample_app_ug/l3_forward_access_ctrl.rst
+EFD
+M: Byron Marohn <byron.marohn@intel.com>
+M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
+F: lib/librte_efd/
+
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
M: Pablo de Lara <pablo.de.lara.guarch@intel.com>
diff --git a/config/common_base b/config/common_base
index 8e9dcfa..869d8fb 100644
--- a/config/common_base
+++ b/config/common_base
@@ -467,6 +467,11 @@ CONFIG_RTE_LIBRTE_HASH=y
CONFIG_RTE_LIBRTE_HASH_DEBUG=n
#
+# Compile librte_efd
+#
+CONFIG_RTE_LIBRTE_EFD=y
+
+#
# Compile librte_jobstats
#
CONFIG_RTE_LIBRTE_JOBSTATS=y
diff --git a/doc/api/doxy-api-index.md b/doc/api/doxy-api-index.md
index 72d59b2..0d34e2f 100644
--- a/doc/api/doxy-api-index.md
+++ b/doc/api/doxy-api-index.md
@@ -90,7 +90,8 @@ There are many libraries, so their headers may be grouped by topics:
[frag/reass] (@ref rte_ip_frag.h),
[LPM IPv4 route] (@ref rte_lpm.h),
[LPM IPv6 route] (@ref rte_lpm6.h),
- [ACL] (@ref rte_acl.h)
+ [ACL] (@ref rte_acl.h),
+ [EFD] (@ref rte_efd.h)
- **QoS**:
[metering] (@ref rte_meter.h),
diff --git a/doc/api/doxy-api.conf b/doc/api/doxy-api.conf
index b340fcf..6892315 100644
--- a/doc/api/doxy-api.conf
+++ b/doc/api/doxy-api.conf
@@ -40,6 +40,7 @@ INPUT = doc/api/doxy-api-index.md \
lib/librte_compat \
lib/librte_cryptodev \
lib/librte_distributor \
+ lib/librte_efd \
lib/librte_ether \
lib/librte_hash \
lib/librte_ip_frag \
diff --git a/doc/guides/rel_notes/release_17_02.rst b/doc/guides/rel_notes/release_17_02.rst
index 180af82..5023038 100644
--- a/doc/guides/rel_notes/release_17_02.rst
+++ b/doc/guides/rel_notes/release_17_02.rst
@@ -53,6 +53,18 @@ New Features
information.
+* **Added Elastic Flow Distributor library (rte_efd).**
+
+ This new library uses perfect hashing to determine a target/value for a
+ given incoming flow key.
+
+ It does not store the key itself for lookup operations, and therefore,
+ lookup performance is not dependent on the key size. Also, the target/value
+ can be any arbitrary value (8 bits by default). Finally, the storage requirement
+ is much smaller than a hash-based flow table and therefore, it can better fit for
+ CPU cache, being able to scale to millions of flow keys.
+
+
Resolved Issues
---------------
diff --git a/lib/Makefile b/lib/Makefile
index 990f23a..4178325 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -1,6 +1,6 @@
# BSD LICENSE
#
-# Copyright(c) 2010-2015 Intel Corporation. All rights reserved.
+# Copyright(c) 2010-2017 Intel Corporation. All rights reserved.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -43,6 +43,7 @@ DIRS-$(CONFIG_RTE_LIBRTE_ETHER) += librte_ether
DIRS-$(CONFIG_RTE_LIBRTE_CRYPTODEV) += librte_cryptodev
DIRS-$(CONFIG_RTE_LIBRTE_VHOST) += librte_vhost
DIRS-$(CONFIG_RTE_LIBRTE_HASH) += librte_hash
+DIRS-$(CONFIG_RTE_LIBRTE_EFD) += librte_efd
DIRS-$(CONFIG_RTE_LIBRTE_LPM) += librte_lpm
DIRS-$(CONFIG_RTE_LIBRTE_ACL) += librte_acl
DIRS-$(CONFIG_RTE_LIBRTE_NET) += librte_net
diff --git a/lib/librte_eal/common/include/rte_log.h b/lib/librte_eal/common/include/rte_log.h
index 671e274..954b96c 100644
--- a/lib/librte_eal/common/include/rte_log.h
+++ b/lib/librte_eal/common/include/rte_log.h
@@ -1,7 +1,7 @@
/*-
* BSD LICENSE
*
- * Copyright(c) 2010-2014 Intel Corporation. All rights reserved.
+ * Copyright(c) 2010-2017 Intel Corporation. All rights reserved.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -79,6 +79,7 @@ extern struct rte_logs rte_logs;
#define RTE_LOGTYPE_PIPELINE 0x00008000 /**< Log related to pipeline. */
#define RTE_LOGTYPE_MBUF 0x00010000 /**< Log related to mbuf. */
#define RTE_LOGTYPE_CRYPTODEV 0x00020000 /**< Log related to cryptodev. */
+#define RTE_LOGTYPE_EFD 0x00040000 /**< Log related to EFD. */
/* these log types can be used in an application */
#define RTE_LOGTYPE_USER1 0x01000000 /**< User-defined log type 1. */
diff --git a/lib/librte_efd/Makefile b/lib/librte_efd/Makefile
new file mode 100644
index 0000000..58d34af
--- /dev/null
+++ b/lib/librte_efd/Makefile
@@ -0,0 +1,56 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+# library name
+LIB = librte_efd.a
+
+CFLAGS += -O3
+CFLAGS += $(WERROR_FLAGS) -I$(SRCDIR)
+
+EXPORT_MAP := rte_efd_version.map
+
+LIBABIVER := 1
+
+# all source are stored in SRCS-y
+SRCS-$(CONFIG_RTE_LIBRTE_EFD) := rte_efd.c
+
+# install this header file
+SYMLINK-$(CONFIG_RTE_LIBRTE_EFD)-include := rte_efd.h
+
+# this lib depends upon:
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_mbuf
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_mempool
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_eal
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_ether
+
+include $(RTE_SDK)/mk/rte.lib.mk
diff --git a/lib/librte_efd/rte_efd.c b/lib/librte_efd/rte_efd.c
new file mode 100644
index 0000000..917e076
--- /dev/null
+++ b/lib/librte_efd/rte_efd.c
@@ -0,0 +1,1354 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <immintrin.h>
+#include <math.h>
+#include <sys/queue.h>
+
+#include <rte_log.h>
+#include <rte_eal_memconfig.h>
+#include <rte_errno.h>
+#include <rte_malloc.h>
+#include <rte_memzone.h>
+#include <rte_prefetch.h>
+#include <rte_branch_prediction.h>
+#include <rte_memcpy.h>
+#include <rte_ring.h>
+#include <rte_jhash.h>
+#include <rte_hash_crc.h>
+
+#include "rte_efd.h"
+
+#define EFD_KEY(key_idx, table) (table->keys + ((key_idx) * table->key_len))
+/** Hash function used to determine chunk_id and bin_id for a group */
+#define EFD_HASH(key, table) \
+ (uint32_t)(rte_jhash(key, table->key_len, 0xbc9f1d34))
+/** Hash function used as constant component of perfect hash search */
+#define EFD_HASHFUNCA(key, table) \
+ (uint32_t)(rte_hash_crc(key, table->key_len, 0xbc9f1d35))
+/** Hash function used as multiplicative component of perfect hash search */
+#define EFD_HASHFUNCB(key, table) \
+ (uint32_t)(rte_hash_crc(key, table->key_len, 0xbc9f1d36))
+
+/*************************************************************************
+ * Fixed constants
+ *************************************************************************/
+
+/* These parameters are fixed by the efd_bin_to_group balancing table */
+#define EFD_CHUNK_NUM_GROUPS (64)
+#define EFD_CHUNK_NUM_BINS (256)
+#define EFD_CHUNK_NUM_BIN_TO_GROUP_SETS \
+ (EFD_CHUNK_NUM_BINS / EFD_CHUNK_NUM_GROUPS)
+
+/*
+ * Target number of rules that each chunk is created to handle.
+ * Used when initially allocating the table
+ */
+#define EFD_TARGET_CHUNK_NUM_RULES \
+ (EFD_CHUNK_NUM_GROUPS * EFD_TARGET_GROUP_NUM_RULES)
+/*
+ * Max number of rules that each chunk is created to handle.
+ * Used when initially allocating the table
+ */
+#define EFD_TARGET_CHUNK_MAX_NUM_RULES \
+ (EFD_CHUNK_NUM_GROUPS * EFD_MAX_GROUP_NUM_RULES)
+
+/** This is fixed based on the bin_to_group permutation array */
+#define EFD_MAX_GROUP_NUM_BINS (16)
+
+/**
+ * The end of the chunks array needs some extra padding to ensure
+ * that vectorization over-reads on the last online chunk stay within
+allocated memory
+ */
+#define EFD_NUM_CHUNK_PADDING_BYTES (256)
+
+#define EFD_LOOKUPTBL_SHIFT (32 - 4)
+typedef uint16_t efd_lookuptbl_t;
+typedef uint16_t efd_hashfunc_t;
+
+/* All different signature compare functions */
+enum rte_efd_compare_function {
+ RTE_HASH_COMPARE_SCALAR = 0,
+ RTE_HASH_COMPARE_AVX2,
+ RTE_HASH_COMPARE_NUM
+};
+
+TAILQ_HEAD(rte_efd_list, rte_tailq_entry);
+
+static struct rte_tailq_elem rte_efd_tailq = {
+ .name = "RTE_EFD",
+};
+EAL_REGISTER_TAILQ(rte_efd_tailq);
+
+/** Internal permutation array used to shuffle bins into pseudorandom groups */
+const uint32_t efd_bin_to_group[EFD_CHUNK_NUM_BIN_TO_GROUP_SETS][EFD_CHUNK_NUM_BINS] = {
+ {
+ 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3,
+ 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
+ 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11,
+ 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15,
+ 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19,
+ 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23,
+ 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27,
+ 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31,
+ 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35,
+ 36, 36, 36, 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39,
+ 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, 42, 43, 43, 43, 43,
+ 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47,
+ 48, 48, 48, 48, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51,
+ 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, 54, 55, 55, 55, 55,
+ 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59,
+ 60, 60, 60, 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63
+ },
+ {
+ 34, 33, 48, 59, 0, 21, 36, 18, 9, 49, 54, 38, 51, 23, 31, 5,
+ 44, 23, 37, 52, 11, 4, 58, 20, 38, 40, 38, 22, 26, 28, 42, 6,
+ 46, 16, 31, 28, 46, 14, 60, 0, 35, 53, 16, 58, 16, 29, 39, 7,
+ 1, 54, 15, 11, 48, 3, 62, 9, 58, 5, 30, 43, 17, 7, 36, 34,
+ 6, 36, 2, 14, 10, 1, 47, 47, 20, 45, 62, 56, 34, 25, 39, 18,
+ 51, 41, 61, 25, 56, 40, 41, 37, 52, 35, 30, 57, 11, 42, 37, 27,
+ 54, 19, 26, 13, 48, 31, 46, 15, 12, 10, 16, 20, 43, 17, 12, 55,
+ 45, 18, 8, 41, 7, 31, 42, 63, 12, 14, 21, 57, 24, 40, 5, 41,
+ 13, 44, 23, 59, 25, 57, 52, 50, 62, 1, 2, 49, 32, 57, 26, 43,
+ 56, 60, 55, 5, 49, 6, 3, 50, 46, 39, 27, 33, 17, 4, 53, 13,
+ 2, 19, 36, 51, 63, 0, 22, 33, 59, 28, 29, 23, 45, 33, 53, 27,
+ 22, 21, 40, 56, 4, 18, 44, 47, 28, 17, 4, 50, 21, 62, 8, 39,
+ 0, 8, 15, 24, 29, 24, 9, 11, 48, 61, 35, 55, 43, 1, 54, 42,
+ 53, 60, 22, 3, 32, 52, 25, 8, 15, 60, 7, 55, 27, 63, 19, 10,
+ 63, 24, 61, 19, 12, 38, 6, 29, 13, 37, 10, 3, 45, 32, 32, 30,
+ 49, 61, 44, 14, 20, 58, 35, 30, 2, 26, 34, 51, 9, 59, 47, 50
+ },
+ {
+ 32, 35, 32, 34, 55, 5, 6, 23, 49, 11, 6, 23, 52, 37, 29, 54,
+ 55, 40, 63, 50, 29, 52, 61, 25, 12, 56, 39, 38, 29, 11, 46, 1,
+ 40, 11, 19, 56, 7, 28, 51, 16, 15, 48, 21, 51, 60, 31, 14, 22,
+ 41, 47, 59, 56, 53, 28, 58, 26, 43, 27, 41, 33, 24, 52, 44, 38,
+ 13, 59, 48, 51, 60, 15, 3, 30, 15, 0, 10, 62, 44, 14, 28, 51,
+ 38, 2, 41, 26, 25, 49, 10, 12, 55, 57, 27, 35, 19, 33, 0, 30,
+ 5, 36, 47, 53, 5, 53, 20, 43, 34, 37, 52, 41, 21, 63, 59, 9,
+ 24, 1, 45, 24, 39, 44, 45, 16, 9, 17, 7, 50, 57, 22, 18, 28,
+ 25, 45, 2, 40, 58, 15, 17, 3, 1, 27, 61, 39, 19, 0, 19, 21,
+ 57, 62, 54, 60, 54, 40, 48, 33, 36, 37, 4, 42, 1, 43, 58, 8,
+ 13, 42, 10, 56, 35, 22, 48, 61, 63, 10, 49, 9, 24, 9, 25, 57,
+ 33, 18, 13, 31, 42, 36, 36, 55, 30, 37, 53, 34, 59, 4, 4, 23,
+ 8, 16, 58, 14, 30, 11, 12, 63, 49, 62, 2, 39, 47, 22, 2, 60,
+ 18, 8, 46, 31, 6, 20, 32, 29, 46, 42, 20, 31, 32, 61, 34, 4,
+ 47, 26, 20, 43, 26, 21, 7, 3, 16, 35, 18, 44, 27, 62, 13, 23,
+ 6, 50, 12, 8, 45, 17, 3, 46, 50, 7, 14, 5, 17, 54, 38, 0
+ },
+ {
+ 29, 56, 5, 7, 54, 48, 23, 37, 35, 44, 52, 40, 33, 49, 60, 0,
+ 59, 51, 28, 12, 41, 26, 2, 23, 34, 5, 59, 40, 3, 19, 6, 26,
+ 35, 53, 45, 49, 29, 57, 28, 62, 58, 59, 19, 53, 59, 62, 6, 54,
+ 13, 15, 48, 50, 45, 21, 41, 12, 34, 40, 24, 56, 19, 21, 35, 18,
+ 55, 45, 9, 61, 47, 61, 19, 15, 16, 39, 17, 31, 3, 51, 21, 50,
+ 17, 25, 25, 11, 44, 16, 18, 28, 14, 2, 37, 61, 58, 27, 62, 4,
+ 14, 17, 1, 9, 46, 28, 37, 0, 53, 43, 57, 7, 57, 46, 21, 41,
+ 39, 14, 52, 60, 44, 53, 49, 60, 49, 63, 13, 11, 29, 1, 55, 47,
+ 55, 12, 60, 43, 54, 37, 13, 6, 42, 10, 36, 13, 9, 8, 34, 51,
+ 31, 32, 12, 7, 57, 2, 26, 14, 3, 30, 63, 3, 32, 1, 5, 11,
+ 27, 24, 26, 44, 31, 23, 56, 38, 62, 0, 40, 30, 6, 23, 38, 2,
+ 47, 5, 15, 27, 16, 10, 31, 25, 22, 63, 30, 25, 20, 33, 32, 50,
+ 29, 43, 55, 10, 50, 45, 56, 20, 4, 7, 27, 46, 11, 16, 22, 52,
+ 35, 20, 41, 54, 46, 33, 42, 18, 63, 8, 22, 58, 36, 4, 51, 42,
+ 38, 32, 38, 22, 17, 0, 47, 8, 48, 8, 48, 1, 61, 36, 33, 20,
+ 24, 39, 39, 18, 30, 36, 9, 43, 42, 24, 10, 58, 4, 15, 34, 52
+ },
+};
+
+/*************************************************************************
+ * Offline region structures
+ *************************************************************************/
+
+/** Online group containing number of rules, values, keys and their bins
+ * for EFD_MAX_GROUP_NUM_RULES rules.
+ */
+struct efd_offline_group_rules {
+ uint32_t num_rules;
+ /**< Sum of the number of rules in all bins assigned to this group. */
+
+ uint32_t key_idx[EFD_MAX_GROUP_NUM_RULES];
+ /**< Array with all keys of the group. */
+ efd_value_t value[EFD_MAX_GROUP_NUM_RULES];
+ /**< Array with all values of the keys of the group. */
+
+ uint8_t bin_id[EFD_MAX_GROUP_NUM_RULES];
+ /**< Stores the bin for each correspending key to
+ * avoid having to recompute it
+ */
+};
+
+/** Offline chunk record, containing EFD_TARGET_CHUNK_NUM_RULES rules.
+ * Those rules are split into EFD_CHUNK_NUM_GROUPS groups per chunk.
+ */
+struct efd_offline_chunk_rules {
+ uint16_t num_rules;
+ /**< Number of rules in the entire chunk;
+ * used to detect unbalanced groups
+ */
+
+ struct efd_offline_group_rules group_rules[EFD_CHUNK_NUM_GROUPS];
+ /**< Array of all groups in the chunk. */
+};
+
+/*************************************************************************
+ * Online region structures
+ *************************************************************************/
+
+/** Online group containing values for EFD_MAX_GROUP_NUM_RULES rules. */
+struct efd_online_group_entry {
+ efd_hashfunc_t hash_idx[RTE_EFD_VALUE_NUM_BITS];
+ efd_lookuptbl_t lookup_table[RTE_EFD_VALUE_NUM_BITS];
+} __attribute__((__packed__));
+
+/**
+ * A single chunk record, containing EFD_TARGET_CHUNK_NUM_RULES rules.
+ * Those rules are split into EFD_CHUNK_NUM_GROUPS groups per chunk.
+ */
+struct efd_online_chunk {
+ uint8_t bin_choice_list[(EFD_CHUNK_NUM_BINS * 2 + 7) / 8];
+ /**< This is a packed indirection index into the 'groups' array.
+ * Each byte contains four two-bit values which index into
+ * the efd_bin_to_group array.
+ * The efd_bin_to_group array returns the index into the groups array
+ */
+
+ struct efd_online_group_entry groups[EFD_CHUNK_NUM_GROUPS];
+ /**< Array of all the groups in the chunk. */
+} __attribute__((__packed__));
+
+/**
+ * EFD table structure
+ */
+struct rte_efd_table {
+ char name[RTE_EFD_NAMESIZE]; /**< Name of the efd table. */
+
+ uint32_t key_len; /**< Length of the key stored offline */
+
+ uint32_t max_num_rules;
+ /**< Static maximum number of entries the table was constructed to hold. */
+
+ uint32_t num_rules;
+ /**< Number of entries currently in the table . */
+
+ uint32_t num_chunks;
+ /**< Number of chunks in the table needed to support num_rules. */
+
+ uint32_t num_chunks_shift;
+ /**< Bits to shift to get chunk id, instead of dividing by num_chunk. */
+
+ enum rte_efd_compare_function cmp_fn;
+ /**< Indicates which compare function to use. */
+
+ struct efd_online_chunk *chunks[RTE_MAX_NUMA_NODES];
+ /**< Dynamic array of size num_chunks of chunk records. */
+
+ struct efd_offline_chunk_rules *offline_chunks;
+ /**< Dynamic array of size num_chunks of key-value pairs. */
+
+ struct rte_ring *free_slots;
+ /**< Ring that stores all indexes of the free slots in the key table */
+
+ uint8_t *keys; /**< Dynamic array of size max_num_rules of keys */
+};
+
+/**
+ * Computes the chunk ID for a given key hash
+ *
+ * @param table
+ * EFD table to reference
+ * @param hashed_key
+ * 32-bit key hash returned by EFD_HASH
+ *
+ * @return
+ * chunk ID containing this key hash
+ */
+static inline uint32_t
+efd_get_chunk_id(const struct rte_efd_table * const table,
+ const uint32_t hashed_key)
+{
+ return hashed_key & (table->num_chunks - 1);
+}
+
+/**
+ * Computes the bin ID for a given key hash
+ *
+ * @param table
+ * EFD table to reference
+ * @param hashed_key
+ * 32-bit key hash returned by EFD_HASH
+ *
+ * @return bin ID containing this key hash
+ */
+static inline uint32_t
+efd_get_bin_id(const struct rte_efd_table * const table,
+ const uint32_t hashed_key)
+{
+ return (hashed_key >> table->num_chunks_shift) & (EFD_CHUNK_NUM_BINS - 1);
+}
+
+/**
+ * Looks up the current permutation choice for a particular bin in the online table
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to look up existing values (ideally caller's socket id)
+ * @param chunk_id
+ * Chunk ID of bin to look up
+ * @param bin_id
+ * Bin ID to look up
+ *
+ * @return
+ * Currently active permutation choice in the online table
+ */
+static inline uint8_t
+efd_get_choice(const struct rte_efd_table * const table,
+ const unsigned int socket_id, const uint32_t chunk_id,
+ const uint32_t bin_id)
+{
+ struct efd_online_chunk *chunk = &table->chunks[socket_id][chunk_id];
+
+ /*
+ * Grab the chunk (byte) that contains the choices
+ * for four neighboring bins.
+ */
+ uint8_t choice_chunk =
+ chunk->bin_choice_list[bin_id / EFD_CHUNK_NUM_BIN_TO_GROUP_SETS];
+
+ /*
+ * Compute the offset into the chunk that contains
+ * the group_id lookup position
+ */
+ int offset = (bin_id & 0x3) * 2;
+
+ /* Extract from the byte just the desired lookup position */
+ return (uint8_t) ((choice_chunk >> offset) & 0x3);
+}
+
+/**
+ * Compute the chunk_id and bin_id for a given key
+ *
+ * @param table
+ * EFD table to reference
+ * @param key
+ * Key to hash and find location of
+ * @param chunk_id
+ * Computed chunk ID
+ * @param bin_id
+ * Computed bin ID
+ *
+ */
+static inline void
+efd_compute_ids(const struct rte_efd_table * const table,
+ const void *key, uint32_t * const chunk_id, uint32_t * const bin_id)
+{
+ /* Compute the position of the entry in the hash table */
+ uint32_t h = EFD_HASH(key, table);
+
+ /* Compute the chunk_id where that entry can be found */
+ *chunk_id = efd_get_chunk_id(table, h);
+
+ /*
+ * Compute the bin within that chunk where the entry
+ * can be found (0 - 255)
+ */
+ *bin_id = efd_get_bin_id(table, h);
+}
+
+/**
+ * Search for a hash function for a group that satisfies all group results
+ */
+static inline int
+efd_search_hash(struct rte_efd_table * const table,
+ const struct efd_offline_group_rules * const off_group,
+ struct efd_online_group_entry * const on_group)
+{
+ efd_hashfunc_t hash_idx;
+ efd_hashfunc_t start_hash_idx[RTE_EFD_VALUE_NUM_BITS];
+ efd_lookuptbl_t start_lookup_table[RTE_EFD_VALUE_NUM_BITS];
+
+ uint32_t i, j, rule_id;
+ uint32_t hash_val_a[EFD_MAX_GROUP_NUM_RULES];
+ uint32_t hash_val_b[EFD_MAX_GROUP_NUM_RULES];
+ uint32_t hash_val[EFD_MAX_GROUP_NUM_RULES];
+
+
+ rte_prefetch0(off_group->value);
+
+ /*
+ * Prepopulate the hash_val tables by running the two hash functions
+ * for each provided rule
+ */
+ for (i = 0; i < off_group->num_rules; i++) {
+ void *key_stored = EFD_KEY(off_group->key_idx[i], table);
+ hash_val_b[i] = EFD_HASHFUNCB(key_stored, table);
+ hash_val_a[i] = EFD_HASHFUNCA(key_stored, table);
+ }
+
+ for (i = 0; i < RTE_EFD_VALUE_NUM_BITS; i++) {
+ hash_idx = on_group->hash_idx[i];
+ start_hash_idx[i] = hash_idx;
+ start_lookup_table[i] = on_group->lookup_table[i];
+
+ do {
+ efd_lookuptbl_t lookup_table = 0;
+ efd_lookuptbl_t lookup_table_complement = 0;
+
+ for (rule_id = 0; rule_id < off_group->num_rules; rule_id++)
+ hash_val[rule_id] = hash_val_a[rule_id] + (hash_idx *
+ hash_val_b[rule_id]);
+
+ /*
+ * The goal here is to find a hash function for this
+ * particular bit entry that meets the following criteria:
+ * The most significant bits of the hash result define a
+ * shift into the lookup table where the bit will be stored
+ */
+
+ /* Iterate over each provided rule */
+ for (rule_id = 0; rule_id < off_group->num_rules;
+ rule_id++) {
+ /*
+ * Use the few most significant bits (number based on
+ * EFD_LOOKUPTBL_SIZE) to see what position the
+ * expected bit should be set in the lookup_table
+ */
+ uint32_t bucket_idx = hash_val[rule_id] >>
+ EFD_LOOKUPTBL_SHIFT;
+
+ /*
+ * Get the current bit of interest.
+ * This only find an appropriate hash function
+ * for one bit at a time of the rule
+ */
+ efd_lookuptbl_t expected =
+ (off_group->value[rule_id] >> i) & 0x1;
+
+ /*
+ * Add the expected bit (if set) to a map
+ * (lookup_table). Also set its complement
+ * in lookup_table_complement
+ */
+ lookup_table |= expected << bucket_idx;
+ lookup_table_complement |= (1 - expected)
+ << bucket_idx;
+
+ /*
+ * If ever the hash function of two different
+ * elements result in different values at the
+ * same location in the lookup_table,
+ * the current hash_idx is not valid.
+ */
+ if (lookup_table & lookup_table_complement)
+ break;
+ }
+
+ /*
+ * Check if the previous loop completed without
+ * breaking early
+ */
+ if (rule_id == off_group->num_rules) {
+ /*
+ * Current hash function worked, store it
+ * for the current group
+ */
+ on_group->hash_idx[i] = hash_idx;
+ on_group->lookup_table[i] = lookup_table;
+
+ /*
+ * Make sure that the hash function has changed
+ * from the starting value
+ */
+ hash_idx = start_hash_idx[i] + 1;
+ break;
+ }
+ hash_idx++;
+
+ } while (hash_idx != start_hash_idx[i]);
+
+ /* Failed to find perfect hash for this group */
+ if (hash_idx == start_hash_idx[i]) {
+ /*
+ * Restore previous hash_idx and lookup_table
+ * for all value bits
+ */
+ for (j = 0; j < i; j++) {
+ on_group->hash_idx[j] = start_hash_idx[j];
+ on_group->lookup_table[j] = start_lookup_table[j];
+ }
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+struct rte_efd_table *
+rte_efd_create(const char *name, uint32_t max_num_rules, uint32_t key_len,
+ uint8_t online_cpu_socket_bitmask, uint8_t offline_cpu_socket)
+{
+ struct rte_efd_table *table = NULL;
+ uint8_t *key_array = NULL;
+ uint32_t num_chunks, num_chunks_shift;
+ uint8_t socket_id;
+ struct rte_efd_list *efd_list = NULL;
+ struct rte_tailq_entry *te;
+ uint64_t offline_table_size;
+ char ring_name[RTE_RING_NAMESIZE];
+ struct rte_ring *r = NULL;
+ unsigned int i;
+
+ efd_list = RTE_TAILQ_CAST(rte_efd_tailq.head, rte_efd_list);
+
+ if (online_cpu_socket_bitmask == 0) {
+ RTE_LOG(ERR, EFD, "At least one CPU socket must be enabled "
+ "in the bitmask\n");
+ return NULL;
+ }
+
+ if (max_num_rules == 0) {
+ RTE_LOG(ERR, EFD, "Max num rules must be higher than 0\n");
+ return NULL;
+ }
+
+ /*
+ * Compute the minimum number of chunks (smallest power of 2)
+ * that can hold all of the rules
+ */
+ if (max_num_rules % EFD_TARGET_CHUNK_NUM_RULES == 0)
+ num_chunks = rte_align32pow2(max_num_rules /
+ EFD_TARGET_CHUNK_NUM_RULES);
+ else
+ num_chunks = rte_align32pow2((max_num_rules /
+ EFD_TARGET_CHUNK_NUM_RULES) + 1);
+
+ num_chunks_shift = log2(num_chunks);
+
+ rte_rwlock_write_lock(RTE_EAL_TAILQ_RWLOCK);
+
+ /*
+ * Guarantee there's no existing: this is normally already checked
+ * by ring creation above
+ */
+ TAILQ_FOREACH(te, efd_list, next)
+ {
+ table = (struct rte_efd_table *) te->data;
+ if (strncmp(name, table->name, RTE_EFD_NAMESIZE) == 0)
+ break;
+ }
+
+ table = NULL;
+ if (te != NULL) {
+ rte_errno = EEXIST;
+ te = NULL;
+ goto error_unlock_exit;
+ }
+
+ te = rte_zmalloc("EFD_TAILQ_ENTRY", sizeof(*te), 0);
+ if (te == NULL) {
+ RTE_LOG(ERR, EFD, "tailq entry allocation failed\n");
+ goto error_unlock_exit;
+ }
+
+ /* Create a new EFD table management structure */
+ table = (struct rte_efd_table *) rte_zmalloc_socket(NULL,
+ sizeof(struct rte_efd_table),
+ RTE_CACHE_LINE_SIZE,
+ offline_cpu_socket);
+ if (table == NULL) {
+ RTE_LOG(ERR, EFD, "Allocating EFD table management structure"
+ " on socket %u failed\n",
+ offline_cpu_socket);
+ goto error_unlock_exit;
+ }
+
+
+ RTE_LOG(DEBUG, EFD, "Allocated EFD table management structure "
+ "on socket %u\n", offline_cpu_socket);
+
+ table->max_num_rules = num_chunks * EFD_TARGET_CHUNK_MAX_NUM_RULES;
+ table->num_rules = 0;
+ table->num_chunks = num_chunks;
+ table->num_chunks_shift = num_chunks_shift;
+ table->key_len = key_len;
+
+ /* key_array */
+ key_array = (uint8_t *) rte_zmalloc_socket(NULL,
+ table->max_num_rules * table->key_len,
+ RTE_CACHE_LINE_SIZE,
+ offline_cpu_socket);
+ if (key_array == NULL) {
+ RTE_LOG(ERR, EFD, "Allocating key array"
+ " on socket %u failed\n",
+ offline_cpu_socket);
+ goto error_unlock_exit;
+ }
+ table->keys = key_array;
+ snprintf(table->name, sizeof(table->name), "%s", name);
+
+ RTE_LOG(DEBUG, EFD, "Creating an EFD table with %u chunks,"
+ " which potentially supports %u entries\n",
+ num_chunks, table->max_num_rules);
+
+ /* Make sure all the allocatable table pointers are NULL initially */
+ for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++)
+ table->chunks[socket_id] = NULL;
+ table->offline_chunks = NULL;
+
+ /*
+ * Allocate one online table per socket specified
+ * in the user-supplied bitmask
+ */
+ uint64_t online_table_size = num_chunks * sizeof(struct efd_online_chunk) +
+ EFD_NUM_CHUNK_PADDING_BYTES;
+
+ for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++) {
+ if ((online_cpu_socket_bitmask >> socket_id) & 0x01) {
+ /*
+ * Allocate all of the EFD table chunks (the online portion)
+ * as a continuous block
+ */
+ table->chunks[socket_id] =
+ (struct efd_online_chunk *) rte_zmalloc_socket(
+ NULL,
+ online_table_size,
+ RTE_CACHE_LINE_SIZE,
+ socket_id);
+ if (table->chunks[socket_id] == NULL) {
+ RTE_LOG(ERR, EFD,
+ "Allocating EFD online table on "
+ "socket %u failed\n",
+ socket_id);
+ goto error_unlock_exit;
+ }
+ RTE_LOG(DEBUG, EFD,
+ "Allocated EFD online table of size "
+ "%"PRIu64" bytes (%.2f MB) on socket %u\n",
+ online_table_size,
+ (float) online_table_size /
+ (1024.0F * 1024.0F),
+ socket_id);
+ }
+ }
+
+#if defined(RTE_ARCH_X86)
+ if (RTE_EFD_VALUE_NUM_BITS > 3 && rte_cpu_get_flag_enabled(RTE_CPUFLAG_AVX2))
+ table->cmp_fn = RTE_HASH_COMPARE_AVX2;
+ else
+#endif
+ table->cmp_fn = RTE_HASH_COMPARE_SCALAR;
+
+ /*
+ * Allocate the EFD table offline portion (with the actual rules
+ * mapping keys to values) as a continuous block.
+ * This could be several gigabytes of memory.
+ */
+ offline_table_size = num_chunks * sizeof(struct efd_offline_chunk_rules);
+ table->offline_chunks =
+ (struct efd_offline_chunk_rules *) rte_zmalloc_socket(NULL,
+ offline_table_size,
+ RTE_CACHE_LINE_SIZE,
+ offline_cpu_socket);
+ if (table->offline_chunks == NULL) {
+ RTE_LOG(ERR, EFD, "Allocating EFD offline table on socket %u "
+ "failed\n", offline_cpu_socket);
+ goto error_unlock_exit;
+ }
+
+ RTE_LOG(DEBUG, EFD,
+ "Allocated EFD offline table of size %"PRIu64" bytes "
+ " (%.2f MB) on socket %u\n", offline_table_size,
+ (float) offline_table_size / (1024.0F * 1024.0F),
+ offline_cpu_socket);
+
+ te->data = (void *) table;
+ TAILQ_INSERT_TAIL(efd_list, te, next);
+ rte_rwlock_write_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+ snprintf(ring_name, sizeof(ring_name), "HT_%s", table->name);
+ /* Create ring (Dummy slot index is not enqueued) */
+ r = rte_ring_create(ring_name, rte_align32pow2(table->max_num_rules),
+ offline_cpu_socket, 0);
+ if (r == NULL) {
+ RTE_LOG(ERR, EFD, "memory allocation failed\n");
+ goto error_unlock_exit;
+ }
+
+ /* Populate free slots ring. Entry zero is reserved for key misses. */
+ for (i = 0; i < table->max_num_rules; i++)
+ rte_ring_sp_enqueue(r, (void *) ((uintptr_t) i));
+
+ table->free_slots = r;
+ return table;
+
+error_unlock_exit:
+ rte_rwlock_write_unlock(RTE_EAL_TAILQ_RWLOCK);
+ rte_efd_free(table);
+
+ return NULL;
+}
+
+struct rte_efd_table *
+rte_efd_find_existing(const char *name)
+{
+ struct rte_efd_table *table = NULL;
+ struct rte_tailq_entry *te;
+ struct rte_efd_list *efd_list;
+
+ efd_list = RTE_TAILQ_CAST(rte_efd_tailq.head, rte_efd_list);
+
+ rte_rwlock_read_lock(RTE_EAL_TAILQ_RWLOCK);
+
+ TAILQ_FOREACH(te, efd_list, next)
+ {
+ table = (struct rte_efd_table *) te->data;
+ if (strncmp(name, table->name, RTE_EFD_NAMESIZE) == 0)
+ break;
+ }
+ rte_rwlock_read_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+ if (te == NULL) {
+ rte_errno = ENOENT;
+ return NULL;
+ }
+ return table;
+}
+
+void
+rte_efd_free(struct rte_efd_table *table)
+{
+ uint8_t socket_id;
+
+ if (table == NULL)
+ return;
+
+ for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++)
+ rte_free(table->chunks[socket_id]);
+
+ rte_ring_free(table->free_slots);
+ rte_free(table->offline_chunks);
+ rte_free(table->keys);
+ rte_free(table);
+}
+
+/**
+ * Applies a previously computed table entry to the specified table for all
+ * socket-local copies of the online table.
+ * Intended to apply an update for only a single change
+ * to a key/value pair at a time
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing values (ideally caller's socket id)
+ * @param chunk_id
+ * Chunk index to update
+ * @param group_id
+ * Group index to update
+ * @param bin_id
+ * Bin within the group that this update affects
+ * @param new_bin_choice
+ * Newly chosen permutation which this bin should use - only lower 2 bits
+ * @param new_group_entry
+ * Previously computed updated chunk/group entry
+ */
+static inline void
+efd_apply_update(struct rte_efd_table * const table, const unsigned int socket_id,
+ const uint32_t chunk_id, const uint32_t group_id,
+ const uint32_t bin_id, const uint8_t new_bin_choice,
+ const struct efd_online_group_entry * const new_group_entry)
+{
+ int i;
+ struct efd_online_chunk *chunk = &table->chunks[socket_id][chunk_id];
+ uint8_t bin_index = bin_id / EFD_CHUNK_NUM_BIN_TO_GROUP_SETS;
+
+ /*
+ * Grab the current byte that contains the choices
+ * for four neighboring bins
+ */
+ uint8_t choice_chunk =
+ chunk->bin_choice_list[bin_index];
+
+
+ /* Compute the offset into the chunk that needs to be updated */
+ int offset = (bin_id & 0x3) * 2;
+
+ /* Zero the two bits of interest and set them to new_bin_choice */
+ choice_chunk = (choice_chunk & (~(0x03 << offset)))
+ | ((new_bin_choice & 0x03) << offset);
+
+ /* Update the online table with the new data across all sockets */
+ for (i = 0; i < RTE_MAX_NUMA_NODES; i++) {
+ if (table->chunks[i] != NULL) {
+ memcpy(&(table->chunks[i][chunk_id].groups[group_id]),
+ new_group_entry,
+ sizeof(struct efd_online_group_entry));
+ table->chunks[i][chunk_id].bin_choice_list[bin_index] =
+ choice_chunk;
+ }
+ }
+}
+
+/*
+ * Move the bin from prev group to the new group
+ */
+static inline void
+move_groups(uint32_t bin_id, uint8_t bin_size,
+ struct efd_offline_group_rules *new_group,
+ struct efd_offline_group_rules * const current_group)
+{
+
+ uint8_t empty_idx = 0;
+ unsigned int i;
+
+ if (new_group == current_group)
+ return;
+
+ for (i = 0; i < current_group->num_rules; i++) {
+ /*
+ * Move keys that belong to the same bin
+ * to the new group
+ */
+ if (current_group->bin_id[i] == bin_id) {
+ new_group->key_idx[new_group->num_rules] =
+ current_group->key_idx[i];
+ new_group->value[new_group->num_rules] =
+ current_group->value[i];
+ new_group->bin_id[new_group->num_rules] =
+ current_group->bin_id[i];
+ new_group->num_rules++;
+ } else {
+ if (i != empty_idx) {
+ /*
+ * Need to move this key towards
+ * the top of the array
+ */
+ current_group->key_idx[empty_idx] =
+ current_group->key_idx[i];
+ current_group->value[empty_idx] =
+ current_group->value[i];
+ current_group->bin_id[empty_idx] =
+ current_group->bin_id[i];
+ }
+ empty_idx++;
+ }
+
+ }
+ current_group->num_rules -= bin_size;
+}
+
+/*
+ * Revert group/s to their previous state before
+ * trying to insert/add a new key
+ */
+static inline void
+revert_groups(struct efd_offline_group_rules *previous_group,
+ struct efd_offline_group_rules *current_group, uint8_t bin_size)
+{
+ unsigned int i;
+
+ if (current_group == previous_group)
+ return;
+
+ /* Move keys back to previous group */
+ for (i = current_group->num_rules - bin_size;
+ i < current_group->num_rules; i++) {
+ previous_group->key_idx[previous_group->num_rules] =
+ current_group->key_idx[i];
+ previous_group->value[previous_group->num_rules] =
+ current_group->value[i];
+ previous_group->bin_id[previous_group->num_rules] =
+ current_group->bin_id[i];
+ previous_group->num_rules++;
+ }
+
+ /*
+ * Decrease number of rules after the move
+ * in the new group
+ */
+ current_group->num_rules -= bin_size;
+}
+
+/**
+ * Computes an updated table entry where the supplied key points to a new host.
+ * If no entry exists, one is inserted.
+ *
+ * This function does NOT modify the online table(s)
+ * This function DOES modify the offline table
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing values (ideally caller's socket id)
+ * @param key
+ * Key to insert
+ * @param value
+ * Value to associate with key
+ * @param chunk_id
+ * Chunk ID of the chunk that was modified
+ * @param group_id
+ * Group ID of the group that was modified
+ * @param bin_id
+ * Bin ID that was modified
+ * @param new_bin_choice
+ * Newly chosen permutation which this bin will use
+ * @param entry
+ * Newly computed online entry to apply later with efd_apply_update
+ *
+ * @return
+ * RTE_EFD_UPDATE_WARN_GROUP_FULL
+ * Operation is insert, and the last available space in the
+ * key's group was just used. Future inserts may fail as groups fill up.
+ * This operation was still successful, and entry contains a valid update
+ * RTE_EFD_UPDATE_FAILED
+ * Either the EFD failed to find a suitable perfect hash or the group was full
+ * This is a fatal error, and the table is now in an indeterminite state
+ * RTE_EFD_UPDATE_NO_CHANGE
+ * Operation resulted in no change to the table (same value already exists)
+ * 0
+ * Insert or update was successful, and the new efd_online_group_entry
+ * is stored in *entry
+ *
+ * @warning
+ * Note that entry will be UNCHANGED if the update has no effect, and thus any
+ * subsequent use of the entry content will likely be invalid
+ */
+static inline int
+efd_compute_update(struct rte_efd_table * const table,
+ const unsigned int socket_id, const void *key,
+ const efd_value_t value, uint32_t * const chunk_id,
+ uint32_t * const group_id, uint32_t * const bin_id,
+ uint8_t * const new_bin_choice,
+ struct efd_online_group_entry * const entry)
+{
+ unsigned int i;
+ int ret;
+ uint32_t new_idx;
+ void *new_k, *slot_id = NULL;
+ int status = EXIT_SUCCESS;
+ unsigned int found = 0;
+
+ efd_compute_ids(table, key, chunk_id, bin_id);
+
+ struct efd_offline_chunk_rules * const chunk =
+ &table->offline_chunks[*chunk_id];
+ struct efd_offline_group_rules *new_group;
+
+ uint8_t current_choice = efd_get_choice(table, socket_id,
+ *chunk_id, *bin_id);
+ uint32_t current_group_id = efd_bin_to_group[current_choice][*bin_id];
+ struct efd_offline_group_rules * const current_group =
+ &chunk->group_rules[current_group_id];
+ uint8_t bin_size = 0;
+ uint8_t key_changed_index = 0;
+ efd_value_t key_changed_previous_value = 0;
+ uint32_t key_idx_previous = 0;
+
+ /* Scan the current group and see if the key is already present */
+ for (i = 0; i < current_group->num_rules; i++) {
+ if (current_group->bin_id[i] == *bin_id)
+ bin_size++;
+ else
+ continue;
+
+ void *key_stored = EFD_KEY(current_group->key_idx[i], table);
+ if (found == 0 && unlikely(memcmp(key_stored, key,
+ table->key_len) == 0)) {
+ /* Key is already present */
+
+ /*
+ * If previous value is same as new value,
+ * no additional work is required
+ */
+ if (current_group->value[i] == value)
+ return RTE_EFD_UPDATE_NO_CHANGE;
+
+ key_idx_previous = current_group->key_idx[i];
+ key_changed_previous_value = current_group->value[i];
+ key_changed_index = i;
+ current_group->value[i] = value;
+ found = 1;
+ }
+ }
+
+ if (found == 0) {
+ /* Key does not exist. Insert the rule into the bin/group */
+ if (unlikely(current_group->num_rules >= EFD_MAX_GROUP_NUM_RULES)) {
+ RTE_LOG(ERR, EFD,
+ "Fatal: No room remaining for insert into "
+ "chunk %u group %u bin %u\n",
+ *chunk_id,
+ current_group_id, *bin_id);
+ return RTE_EFD_UPDATE_FAILED;
+ }
+
+ if (unlikely(current_group->num_rules ==
+ (EFD_MAX_GROUP_NUM_RULES - 1))) {
+ RTE_LOG(INFO, EFD, "Warn: Insert into last "
+ "available slot in chunk %u "
+ "group %u bin %u\n", *chunk_id,
+ current_group_id, *bin_id);
+ status = RTE_EFD_UPDATE_WARN_GROUP_FULL;
+ }
+
+ if (rte_ring_sc_dequeue(table->free_slots, &slot_id) != 0)
+ return RTE_EFD_UPDATE_FAILED;
+
+ new_k = RTE_PTR_ADD(table->keys, (uintptr_t) slot_id *
+ table->key_len);
+ rte_prefetch0(new_k);
+ new_idx = (uint32_t) ((uintptr_t) slot_id);
+
+ rte_memcpy(EFD_KEY(new_idx, table), key, table->key_len);
+ current_group->key_idx[current_group->num_rules] = new_idx;
+ current_group->value[current_group->num_rules] = value;
+ current_group->bin_id[current_group->num_rules] = *bin_id;
+ current_group->num_rules++;
+ table->num_rules++;
+ bin_size++;
+ } else {
+ uint32_t last = current_group->num_rules - 1;
+ /* Swap the key with the last key inserted*/
+ current_group->key_idx[key_changed_index] =
+ current_group->key_idx[last];
+ current_group->value[key_changed_index] =
+ current_group->value[last];
+ current_group->bin_id[key_changed_index] =
+ current_group->bin_id[last];
+
+ /*
+ * Key to be updated will always be available
+ * at the end of the group
+ */
+ current_group->key_idx[last] = key_idx_previous;
+ current_group->value[last] = value;
+ current_group->bin_id[last] = *bin_id;
+ }
+
+ *new_bin_choice = current_choice;
+ *group_id = current_group_id;
+ new_group = current_group;
+
+ /* Group need to be rebalanced when it starts to get loaded */
+ if (current_group->num_rules > EFD_MIN_BALANCED_NUM_RULES) {
+
+ /*
+ * Subtract the number of entries in the bin from
+ * the original group
+ */
+ current_group->num_rules -= bin_size;
+
+ /*
+ * Figure out which of the available groups that this bin
+ * can map to is the smallest (using the current group
+ * as baseline)
+ */
+ uint8_t smallest_choice = current_choice;
+ uint8_t smallest_size = current_group->num_rules;
+ uint32_t smallest_group_id = current_group_id;
+ unsigned char choice;
+
+ for (choice = 0; choice < EFD_CHUNK_NUM_BIN_TO_GROUP_SETS;
+ choice++) {
+ uint32_t test_group_id =
+ efd_bin_to_group[choice][*bin_id];
+ uint32_t num_rules =
+ chunk->group_rules[test_group_id].num_rules;
+ if (num_rules < smallest_size) {
+ smallest_choice = choice;
+ smallest_size = num_rules;
+ smallest_group_id = test_group_id;
+ }
+ }
+
+ *new_bin_choice = smallest_choice;
+ *group_id = smallest_group_id;
+ new_group = &chunk->group_rules[smallest_group_id];
+ current_group->num_rules += bin_size;
+
+ }
+
+ uint8_t choice = 0;
+ for (;;) {
+ if (current_group != new_group &&
+ new_group->num_rules + bin_size >
+ EFD_MAX_GROUP_NUM_RULES) {
+ RTE_LOG(DEBUG, EFD,
+ "Unable to move_groups to dest group "
+ "containing %u entries."
+ "bin_size:%u choice:%02x\n",
+ new_group->num_rules, bin_size,
+ choice - 1);
+ goto next_choice;
+ }
+ move_groups(*bin_id, bin_size, new_group, current_group);
+ /*
+ * Recompute the hash function for the modified group,
+ * and return it to the caller
+ */
+ ret = efd_search_hash(table, new_group, entry);
+
+ if (!ret)
+ return status;
+
+ RTE_LOG(DEBUG, EFD,
+ "Failed to find perfect hash for group "
+ "containing %u entries. bin_size:%u choice:%02x\n",
+ new_group->num_rules, bin_size, choice - 1);
+ /* Restore groups modified to their previous state */
+ revert_groups(current_group, new_group, bin_size);
+
+next_choice:
+ if (choice == EFD_CHUNK_NUM_BIN_TO_GROUP_SETS)
+ break;
+ *new_bin_choice = choice;
+ *group_id = efd_bin_to_group[choice][*bin_id];
+ new_group = &chunk->group_rules[*group_id];
+ choice++;
+ }
+
+ if (!found) {
+ current_group->num_rules--;
+ table->num_rules--;
+ } else
+ current_group->value[current_group->num_rules - 1] =
+ key_changed_previous_value;
+ return RTE_EFD_UPDATE_FAILED;
+}
+
+int
+rte_efd_update(struct rte_efd_table * const table, const unsigned int socket_id,
+ const void *key, const efd_value_t value)
+{
+ uint32_t chunk_id = 0, group_id = 0, bin_id = 0;
+ uint8_t new_bin_choice = 0;
+ struct efd_online_group_entry entry;
+
+ int status = efd_compute_update(table, socket_id, key, value,
+ &chunk_id, &group_id, &bin_id,
+ &new_bin_choice, &entry);
+
+ if (status == RTE_EFD_UPDATE_NO_CHANGE)
+ return EXIT_SUCCESS;
+
+ if (status == RTE_EFD_UPDATE_FAILED)
+ return status;
+
+ efd_apply_update(table, socket_id, chunk_id, group_id, bin_id,
+ new_bin_choice, &entry);
+ return status;
+}
+
+int
+rte_efd_delete(struct rte_efd_table * const table, const unsigned int socket_id,
+ const void *key, efd_value_t * const prev_value)
+{
+ unsigned int i;
+ uint32_t chunk_id, bin_id;
+ uint8_t not_found = 1;
+
+ efd_compute_ids(table, key, &chunk_id, &bin_id);
+
+ struct efd_offline_chunk_rules * const chunk =
+ &table->offline_chunks[chunk_id];
+
+ uint8_t current_choice = efd_get_choice(table, socket_id,
+ chunk_id, bin_id);
+ uint32_t current_group_id = efd_bin_to_group[current_choice][bin_id];
+ struct efd_offline_group_rules * const current_group =
+ &chunk->group_rules[current_group_id];
+
+ /*
+ * Search the current group for the specified key.
+ * If it exists, remove it and re-pack the other values
+ */
+ for (i = 0; i < current_group->num_rules; i++) {
+ if (not_found) {
+ /* Found key that needs to be removed */
+ if (memcmp(EFD_KEY(current_group->key_idx[i], table),
+ key, table->key_len) == 0) {
+ /* Store previous value if requested by caller */
+ if (prev_value != NULL)
+ *prev_value = current_group->value[i];
+
+ not_found = 0;
+ rte_ring_sp_enqueue(table->free_slots,
+ (void *)((uintptr_t)current_group->key_idx[i]));
+ }
+ } else {
+ /*
+ * If the desired key has been found,
+ * need to shift other values up one
+ */
+
+ /* Need to shift this entry back up one index */
+ current_group->key_idx[i - 1] = current_group->key_idx[i];
+ current_group->value[i - 1] = current_group->value[i];
+ current_group->bin_id[i - 1] = current_group->bin_id[i];
+ }
+ }
+
+ if (not_found == 0) {
+ table->num_rules--;
+ current_group->num_rules--;
+ }
+
+ return not_found;
+}
+
+
+#if (RTE_EFD_VALUE_NUM_BITS == 8 || RTE_EFD_VALUE_NUM_BITS == 16 || \
+ RTE_EFD_VALUE_NUM_BITS == 24 || RTE_EFD_VALUE_NUM_BITS == 32)
+#define EFD_LOAD_SI128(val) _mm_load_si128(val)
+#else
+#define EFD_LOAD_SI128(val) _mm_lddqu_si128(val)
+#endif
+
+static inline efd_value_t
+efd_lookup_internal(const struct efd_online_group_entry * const group,
+ const uint32_t hash_val_a, const uint32_t hash_val_b,
+ enum rte_efd_compare_function cmp_fn)
+{
+ efd_value_t value = 0;
+ uint32_t i;
+
+ switch (cmp_fn) {
+#ifdef RTE_MACHINE_CPUFLAG_AVX2
+ case RTE_HASH_COMPARE_AVX2:
+
+ i = 0;
+ __m256i vhash_val_a = _mm256_set1_epi32(hash_val_a);
+ __m256i vhash_val_b = _mm256_set1_epi32(hash_val_b);
+
+ for (; i < RTE_EFD_VALUE_NUM_BITS; i += 8) {
+ __m256i vhash_idx =
+ _mm256_cvtepu16_epi32(EFD_LOAD_SI128(
+ (__m128i const *) &group->hash_idx[i]));
+ __m256i vlookup_table = _mm256_cvtepu16_epi32(
+ EFD_LOAD_SI128((__m128i const *)
+ &group->lookup_table[i]));
+ __m256i vhash = _mm256_add_epi32(vhash_val_a,
+ _mm256_mullo_epi32(vhash_idx, vhash_val_b));
+ __m256i vbucket_idx = _mm256_srli_epi32(vhash,
+ EFD_LOOKUPTBL_SHIFT);
+ __m256i vresult = _mm256_srlv_epi32(vlookup_table,
+ vbucket_idx);
+
+ value |= (_mm256_movemask_ps(
+ (__m256) _mm256_slli_epi32(vresult, 31))
+ & ((1 << (RTE_EFD_VALUE_NUM_BITS - i)) - 1)) << i;
+ }
+ break;
+#endif
+ default:
+
+ i = 0;
+ for (; i < RTE_EFD_VALUE_NUM_BITS; i++) {
+ value <<= 1;
+
+ uint32_t h = hash_val_a + (hash_val_b *
+ group->hash_idx[RTE_EFD_VALUE_NUM_BITS - i - 1]);
+ uint16_t bucket_idx = h >> EFD_LOOKUPTBL_SHIFT;
+
+ value |= (group->lookup_table[
+ RTE_EFD_VALUE_NUM_BITS - i - 1] >>
+ bucket_idx) & 0x1;
+ }
+ }
+ return value;
+}
+
+efd_value_t
+rte_efd_lookup(const struct rte_efd_table * const table,
+ const unsigned int socket_id, const void *key)
+{
+ uint32_t chunk_id, group_id, bin_id;
+ uint8_t bin_choice;
+ const struct efd_online_group_entry *group;
+ const struct efd_online_chunk * const chunks = table->chunks[socket_id];
+
+ /* Determine the chunk and group location for the given key */
+ efd_compute_ids(table, key, &chunk_id, &bin_id);
+ bin_choice = efd_get_choice(table, socket_id, chunk_id, bin_id);
+ group_id = efd_bin_to_group[bin_choice][bin_id];
+ group = &chunks[chunk_id].groups[group_id];
+
+ return efd_lookup_internal(group,
+ EFD_HASHFUNCA(key, table),
+ EFD_HASHFUNCB(key, table),
+ table->cmp_fn);
+}
+
+void rte_efd_lookup_bulk(const struct rte_efd_table * const table,
+ const unsigned int socket_id, const int num_keys,
+ const void **key_list, efd_value_t * const value_list)
+{
+ int i;
+ uint32_t chunk_id_list[RTE_EFD_BURST_MAX];
+ uint32_t bin_id_list[RTE_EFD_BURST_MAX];
+ uint8_t bin_choice_list[RTE_EFD_BURST_MAX];
+ uint32_t group_id_list[RTE_EFD_BURST_MAX];
+ struct efd_online_group_entry *group;
+
+ struct efd_online_chunk *chunks = table->chunks[socket_id];
+
+ for (i = 0; i < num_keys; i++) {
+ efd_compute_ids(table, key_list[i], &chunk_id_list[i],
+ &bin_id_list[i]);
+ rte_prefetch0(&chunks[chunk_id_list[i]].bin_choice_list);
+ }
+
+ for (i = 0; i < num_keys; i++) {
+ bin_choice_list[i] = efd_get_choice(table, socket_id,
+ chunk_id_list[i], bin_id_list[i]);
+ group_id_list[i] =
+ efd_bin_to_group[bin_choice_list[i]][bin_id_list[i]];
+ group = &chunks[chunk_id_list[i]].groups[group_id_list[i]];
+ rte_prefetch0(group);
+ }
+
+ for (i = 0; i < num_keys; i++) {
+ group = &chunks[chunk_id_list[i]].groups[group_id_list[i]];
+ value_list[i] = efd_lookup_internal(group,
+ EFD_HASHFUNCA(key_list[i], table),
+ EFD_HASHFUNCB(key_list[i], table),
+ table->cmp_fn);
+ }
+}
diff --git a/lib/librte_efd/rte_efd.h b/lib/librte_efd/rte_efd.h
new file mode 100644
index 0000000..a728096
--- /dev/null
+++ b/lib/librte_efd/rte_efd.h
@@ -0,0 +1,294 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _RTE_EFD_H_
+#define _RTE_EFD_H_
+
+/**
+ * @file
+ *
+ * RTE EFD Table
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*************************************************************************
+ * User selectable constants
+ *************************************************************************/
+
+/*
+ * If possible, best lookup performance will be achieved by ensuring that
+ * the entire table fits in the L3 cache.
+ *
+ * Some formulas for calculating various sizes are listed below:
+ *
+ * # of chunks =
+ * 2 ^ (ceiling(log2((requested # of rules) /
+ * (EFD_CHUNK_NUM_GROUPS * EFD_TARGET_GROUP_NUM_RULES))))
+ *
+ * Target # of rules = (# of chunks) * EFD_CHUNK_NUM_GROUPS *
+ * EFD_TARGET_GROUP_NUM_RULES
+ *
+ * Group Size (in bytes) = 4 (per value bit)
+ *
+ * Table size (in bytes) = RTE_EFD_VALUE_NUM_BITS * (# of chunks) *
+ * EFD_CHUNK_NUM_GROUPS * (group size)
+ */
+
+/**
+ * !!! This parameter should be adjusted for your application !!!
+ *
+ * This parameter adjusts the number of bits of value that can be
+ * stored in the table.
+ * For example, setting the number of bits to 3 will allow storing 8 values
+ * in the table (between 0 and 7).
+ *
+ * This number directly affects the performance of both lookups and insertion.
+ * In general, performance decreases as more bits are stored in the table.
+ *
+ * This number is directly proportional to the size of the online region
+ * used for lookups.
+ *
+ * Note that due to the way the CPU operates on memory, best lookup performance
+ * will be achieved when RTE_EFD_VALUE_NUM_BITS is a multiple of 8.
+ * These values align the hash indexes on 16-byte boundaries.
+ * The greatest performance drop is moving from 8->9 bits, 16->17 bits, etc.
+ *
+ * This value must be between 1 and 32
+ */
+#ifndef RTE_EFD_VALUE_NUM_BITS
+#define RTE_EFD_VALUE_NUM_BITS (8)
+#endif
+
+/*
+ * EFD_TARGET_GROUP_NUM_RULES:
+ * Adjusts how many groups/chunks are allocated at table creation time
+ * to support the requested number of rules. Higher values pack entries
+ * more tightly in memory, resulting in a smaller memory footprint
+ * for the online table.
+ * This comes at the cost of lower insert/update performance.
+ *
+ * EFD_MAX_GROUP_NUM_RULES:
+ * This adjusts the amount of offline memory allocated to store key/value
+ * pairs for the table. The recommended numbers are upper-bounds for
+ * this parameter
+ * - any higher and it becomes very unlikely that a perfect hash function
+ * can be found for that group size. This value should be at
+ * least 40% larger than EFD_TARGET_GROUP_NUM_RULES
+ *
+ * Recommended values for various lookuptable and hashfunc sizes are:
+ *
+ * HASH_FUNC_SIZE = 16, LOOKUPTBL_SIZE = 16:
+ * EFD_TARGET_GROUP_NUM_RULES = 22
+ * EFD_MAX_GROUP_NUM_RULES = 28
+ */
+#define EFD_TARGET_GROUP_NUM_RULES (22)
+#define EFD_MAX_GROUP_NUM_RULES (28LU)
+
+#define EFD_MIN_BALANCED_NUM_RULES 5
+
+/**
+ * Maximum number of keys that can be looked up in one call to efd_lookup_bulk
+ */
+#ifndef RTE_EFD_BURST_MAX
+#define RTE_EFD_BURST_MAX (32)
+#endif
+
+/** Maximum number of characters in efd name.*/
+#define RTE_EFD_NAMESIZE 32
+
+#if (RTE_EFD_VALUE_NUM_BITS > 0 && RTE_EFD_VALUE_NUM_BITS <= 8)
+typedef uint8_t efd_value_t;
+#elif (RTE_EFD_VALUE_NUM_BITS > 8 && RTE_EFD_VALUE_NUM_BITS <= 16)
+typedef uint16_t efd_value_t;
+#elif (RTE_EFD_VALUE_NUM_BITS > 16 && RTE_EFD_VALUE_NUM_BITS <= 32)
+typedef uint32_t efd_value_t;
+#else
+#error("RTE_EFD_VALUE_NUM_BITS must be in the range [1:32]")
+#endif
+
+/**
+ * Creates an EFD table with a single offline region and multiple per-socket
+ * internally-managed copies of the online table used for lookups
+ *
+ * @param name
+ * EFD table name
+ * @param max_num_rules
+ * Minimum number of rules the table should be sized to hold.
+ * Will be rounded up to the next smallest valid table size
+ * @param online_cpu_socket_bitmask
+ * Bitmask specifying which sockets should get a copy of the online table.
+ * LSB = socket 0, etc.
+ * @param offline_cpu_socket
+ * Identifies the socket where the offline table will be allocated
+ * (and most efficiently accessed in the case of updates/insertions)
+ *
+ * @return
+ * EFD table, or NULL if table allocation failed or the bitmask is invalid
+ */
+struct rte_efd_table *
+rte_efd_create(const char *name, uint32_t max_num_rules, uint32_t key_len,
+ uint8_t online_cpu_socket_bitmask, uint8_t offline_cpu_socket);
+
+/**
+ * Releases the resources from an EFD table
+ *
+ * @param table
+ * Table to free
+ */
+void
+rte_efd_free(struct rte_efd_table *table);
+
+/**
+ * Find an existing EFD table object and return a pointer to it.
+ *
+ * @param name
+ * Name of the EFD table as passed to rte_efd_create()
+ * @return
+ * Pointer to EFD table or NULL if object not found
+ * with rte_errno set appropriately. Possible rte_errno values include:
+ * - ENOENT - value not available for return
+ */
+struct rte_efd_table*
+rte_efd_find_existing(const char *name);
+
+#define RTE_EFD_UPDATE_WARN_GROUP_FULL (1)
+#define RTE_EFD_UPDATE_NO_CHANGE (2)
+#define RTE_EFD_UPDATE_FAILED (3)
+
+/**
+ * Computes an updated table entry for the supplied key/value pair.
+ * The update is then immediately applied to the provided table and
+ * all socket-local copies of the chunks are updated.
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param key
+ * EFD table key to modify
+ * @param value
+ * Value to associate with the key
+ *
+ * @return
+ * RTE_EFD_UPDATE_WARN_GROUP_FULL
+ * Operation is insert, and the last available space in the
+ * key's group was just used
+ * Future inserts may fail as groups fill up
+ * This operation was still successful, and entry contains a valid update
+ * RTE_EFD_UPDATE_FAILED
+ * Either the EFD failed to find a suitable perfect hash or the group was full
+ * This is a fatal error, and the table is now in an indeterminite state
+ * RTE_EFD_UPDATE_NO_CHANGE
+ * Operation resulted in no change to the table (same value already exists)
+ * 0 - success
+ */
+int
+rte_efd_update(struct rte_efd_table *table, unsigned int socket_id,
+ const void *key, efd_value_t value);
+
+/**
+ * Removes any value currently associated with the specified key from the table
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param key
+ * EFD table key to delete
+ * @param prev_value
+ * If not NULL, will store the previous value here before deleting it
+ *
+ * @return
+ * 0 - successfully found and deleted the key
+ * nonzero otherwise
+ */
+int
+rte_efd_delete(struct rte_efd_table *table, unsigned int socket_id,
+ const void *key, efd_value_t *prev_value);
+
+/**
+ * Looks up the value associated with a key
+ *
+ * NOTE: Lookups will *always* succeed - this is a property of
+ * using a perfect hash table.
+ * If the specified key was never inserted, a pseudorandom answer will be returned.
+ * There is no way to know based on the lookup if the key was ever inserted
+ * originally, so this must be tracked elsewhere.
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param key
+ * EFD table key to look up
+ *
+ * @return
+ * Value associated with the key, or random junk if they key was never inserted
+ */
+efd_value_t
+rte_efd_lookup(const struct rte_efd_table *table, unsigned int socket_id,
+ const void *key);
+
+/**
+ * Looks up the value associated with several keys.
+ *
+ * NOTE: Lookups will *always* succeed - this is a property of
+ * using a perfect hash table.
+ * If the specified key was never inserted, a pseudorandom answer will be returned.
+ * There is no way to know based on the lookup if the key was ever inserted
+ * originally, so this must be tracked elsewhere.
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param num_keys
+ * Number of keys in the key_list array, must be less than RTE_EFD_BURST_MAX
+ * @param key_list
+ * Array of num_keys pointers which point to keys to look up
+ * @param value_list
+ * Array of size num_keys where lookup values will be stored
+ */
+void
+rte_efd_lookup_bulk(const struct rte_efd_table *table, unsigned int socket_id,
+ int num_keys, const void **key_list,
+ efd_value_t *value_list);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RTE_EFD_H_ */
diff --git a/lib/librte_efd/rte_efd_version.map b/lib/librte_efd/rte_efd_version.map
new file mode 100644
index 0000000..91810b3
--- /dev/null
+++ b/lib/librte_efd/rte_efd_version.map
@@ -0,0 +1,12 @@
+DPDK_17.02 {
+ global:
+
+ rte_efd_create;
+ rte_efd_delete;
+ rte_efd_free;
+ rte_efd_lookup;
+ rte_efd_lookup_bulk;
+ rte_efd_update;
+
+ local: *;
+};
diff --git a/mk/rte.app.mk b/mk/rte.app.mk
index f75f0e2..ed1e68a 100644
--- a/mk/rte.app.mk
+++ b/mk/rte.app.mk
@@ -1,6 +1,6 @@
# BSD LICENSE
#
-# Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+# Copyright(c) 2010-2017 Intel Corporation. All rights reserved.
# Copyright(c) 2014-2015 6WIND S.A.
# All rights reserved.
#
@@ -86,6 +86,7 @@ _LDLIBS-y += --whole-archive
_LDLIBS-$(CONFIG_RTE_LIBRTE_TIMER) += -lrte_timer
_LDLIBS-$(CONFIG_RTE_LIBRTE_HASH) += -lrte_hash
+_LDLIBS-$(CONFIG_RTE_LIBRTE_EFD) += -lrte_efd
_LDLIBS-$(CONFIG_RTE_LIBRTE_VHOST) += -lrte_vhost
_LDLIBS-$(CONFIG_RTE_LIBRTE_KVARGS) += -lrte_kvargs
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v5 2/5] app/test: add EFD functional and perf tests
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 0/5] Elastic Flow Distributor Pablo de Lara
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 1/5] efd: new Elastic Flow Distributor library Pablo de Lara
@ 2017-01-16 9:43 ` Pablo de Lara
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 3/5] examples/flow_distributor: sample app to demonstrate EFD usage Pablo de Lara
` (4 subsequent siblings)
6 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-16 9:43 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Byron Marohn, Karla Saur, Saikrishna Edupuganti
Signed-off-by: Byron Marohn <byron.marohn@intel.com>
Signed-off-by: Karla Saur <karla.saur@intel.com>
Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 1 +
app/test/Makefile | 5 +-
app/test/test_efd.c | 494 +++++++++++++++++++++++++++++++++++++++++++++++
app/test/test_efd_perf.c | 407 ++++++++++++++++++++++++++++++++++++++
4 files changed, 906 insertions(+), 1 deletion(-)
create mode 100644 app/test/test_efd.c
create mode 100644 app/test/test_efd_perf.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 9c60d67..d812962 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -532,6 +532,7 @@ EFD
M: Byron Marohn <byron.marohn@intel.com>
M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
F: lib/librte_efd/
+F: app/test/test_efd*
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
diff --git a/app/test/Makefile b/app/test/Makefile
index 5be023a..9de301f 100644
--- a/app/test/Makefile
+++ b/app/test/Makefile
@@ -1,6 +1,6 @@
# BSD LICENSE
#
-# Copyright(c) 2010-2015 Intel Corporation. All rights reserved.
+# Copyright(c) 2010-2017 Intel Corporation. All rights reserved.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -123,6 +123,9 @@ SRCS-y += test_logs.c
SRCS-y += test_memcpy.c
SRCS-y += test_memcpy_perf.c
+SRCS-$(CONFIG_RTE_LIBRTE_EFD) += test_efd.c
+SRCS-$(CONFIG_RTE_LIBRTE_EFD) += test_efd_perf.c
+
SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_hash.c
SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_thash.c
SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_hash_perf.c
diff --git a/app/test/test_efd.c b/app/test/test_efd.c
new file mode 100644
index 0000000..d5c3bd9
--- /dev/null
+++ b/app/test/test_efd.c
@@ -0,0 +1,494 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rte_memcpy.h>
+#include <rte_malloc.h>
+#include <rte_efd.h>
+#include <rte_byteorder.h>
+#include <rte_random.h>
+#include <rte_debug.h>
+#include <rte_ip.h>
+
+#include "test.h"
+
+#define EFD_TEST_KEY_LEN 8
+#define TABLE_SIZE (1 << 21)
+#define ITERATIONS 3
+static unsigned int test_socket_id;
+
+/* 5-tuple key type */
+struct flow_key {
+ uint32_t ip_src;
+ uint32_t ip_dst;
+ uint16_t port_src;
+ uint16_t port_dst;
+ uint8_t proto;
+} __attribute__((packed));
+/*
+ * Print out result of unit test efd operation.
+ */
+#if defined(UNIT_TEST_EFD_VERBOSE)
+
+static void print_key_info(const char *msg, const struct flow_key *key,
+ efd_value_t val)
+{
+ const uint8_t *p = (const uint8_t *) key;
+ unsigned int i;
+
+ printf("%s key:0x", msg);
+ for (i = 0; i < sizeof(struct flow_key); i++)
+ printf("%02X", p[i]);
+
+ printf(" @ val %d\n", val);
+}
+#else
+
+static void print_key_info(__attribute__((unused)) const char *msg,
+ __attribute__((unused)) const struct flow_key *key,
+ __attribute__((unused)) efd_value_t val)
+{
+}
+#endif
+
+/* Keys used by unit test functions */
+static struct flow_key keys[5] = {
+ {
+ .ip_src = IPv4(0x03, 0x02, 0x01, 0x00),
+ .ip_dst = IPv4(0x07, 0x06, 0x05, 0x04),
+ .port_src = 0x0908,
+ .port_dst = 0x0b0a,
+ .proto = 0x0c,
+ },
+ {
+ .ip_src = IPv4(0x13, 0x12, 0x11, 0x10),
+ .ip_dst = IPv4(0x17, 0x16, 0x15, 0x14),
+ .port_src = 0x1918,
+ .port_dst = 0x1b1a,
+ .proto = 0x1c,
+ },
+ {
+ .ip_src = IPv4(0x23, 0x22, 0x21, 0x20),
+ .ip_dst = IPv4(0x27, 0x26, 0x25, 0x24),
+ .port_src = 0x2928,
+ .port_dst = 0x2b2a,
+ .proto = 0x2c,
+ },
+ {
+ .ip_src = IPv4(0x33, 0x32, 0x31, 0x30),
+ .ip_dst = IPv4(0x37, 0x36, 0x35, 0x34),
+ .port_src = 0x3938,
+ .port_dst = 0x3b3a,
+ .proto = 0x3c,
+ },
+ {
+ .ip_src = IPv4(0x43, 0x42, 0x41, 0x40),
+ .ip_dst = IPv4(0x47, 0x46, 0x45, 0x44),
+ .port_src = 0x4948,
+ .port_dst = 0x4b4a,
+ .proto = 0x4c,
+ }
+};
+/* Array to store the data */
+efd_value_t data[5];
+
+static inline uint8_t efd_get_all_sockets_bitmask(void)
+{
+ uint8_t all_cpu_sockets_bitmask = 0;
+ unsigned int i;
+ unsigned int next_lcore = rte_get_master_lcore();
+ const int val_true = 1, val_false = 0;
+ for (i = 0; i < rte_lcore_count(); i++) {
+ all_cpu_sockets_bitmask |= 1 << rte_lcore_to_socket_id(next_lcore);
+ next_lcore = rte_get_next_lcore(next_lcore, val_false, val_true);
+ }
+
+ return all_cpu_sockets_bitmask;
+}
+
+/*
+ * Basic sequence of operations for a single key:
+ * - add
+ * - lookup (hit)
+ * - delete
+ * Note: lookup (miss) is not applicable since this is a filter
+ */
+static int test_add_delete(void)
+{
+ struct rte_efd_table *handle;
+ /* test with standard add/lookup/delete functions */
+ efd_value_t prev_value;
+ printf("Entering %s\n", __func__);
+
+ handle = rte_efd_create("test_add_delete",
+ TABLE_SIZE, sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the EFD table\n");
+
+ data[0] = mrand48() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id, &keys[0],
+ data[0]),
+ "Error inserting the key");
+ print_key_info("Add", &keys[0], data[0]);
+
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id, &keys[0]),
+ data[0],
+ "failed to find key");
+
+ TEST_ASSERT_SUCCESS(rte_efd_delete(handle, test_socket_id, &keys[0],
+ &prev_value),
+ "failed to delete key");
+ TEST_ASSERT_EQUAL(prev_value, data[0],
+ "failed to delete the expected value, got %d, "
+ "expected %d", prev_value, data[0]);
+ print_key_info("Del", &keys[0], data[0]);
+
+ rte_efd_free(handle);
+
+ return 0;
+}
+
+/*
+ * Sequence of operations for a single key:
+ * - add
+ * - lookup: hit
+ * - add: update
+ * - lookup: hit (updated data)
+ * - delete: hit
+ */
+static int test_add_update_delete(void)
+{
+ struct rte_efd_table *handle;
+ printf("Entering %s\n", __func__);
+ /* test with standard add/lookup/delete functions */
+ efd_value_t prev_value;
+ data[1] = mrand48() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+
+ handle = rte_efd_create("test_add_update_delete", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the efd table\n");
+
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id, &keys[1],
+ data[1]), "Error inserting the key");
+ print_key_info("Add", &keys[1], data[1]);
+
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id, &keys[1]),
+ data[1], "failed to find key");
+ print_key_info("Lkp", &keys[1], data[1]);
+
+ data[1] = data[1] + 1;
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id, &keys[1],
+ data[1]), "Error re-inserting the key");
+ print_key_info("Add", &keys[1], data[1]);
+
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id, &keys[1]),
+ data[1], "failed to find key");
+ print_key_info("Lkp", &keys[1], data[1]);
+
+ TEST_ASSERT_SUCCESS(rte_efd_delete(handle, test_socket_id, &keys[1],
+ &prev_value), "failed to delete key");
+ TEST_ASSERT_EQUAL(prev_value, data[1],
+ "failed to delete the expected value, got %d, "
+ "expected %d", prev_value, data[1]);
+ print_key_info("Del", &keys[1], data[1]);
+
+
+ rte_efd_free(handle);
+ return 0;
+}
+
+/*
+ * Sequence of operations for find existing EFD table
+ *
+ * - create table
+ * - find existing table: hit
+ * - find non-existing table: miss
+ *
+ */
+static int test_efd_find_existing(void)
+{
+ struct rte_efd_table *handle = NULL, *result = NULL;
+
+ printf("Entering %s\n", __func__);
+
+ /* Create EFD table. */
+ handle = rte_efd_create("efd_find_existing", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the efd table\n");
+
+ /* Try to find existing EFD table */
+ result = rte_efd_find_existing("efd_find_existing");
+ TEST_ASSERT_EQUAL(result, handle, "could not find existing efd table");
+
+ /* Try to find non-existing EFD table */
+ result = rte_efd_find_existing("efd_find_non_existing");
+ TEST_ASSERT_NULL(result, "found table that shouldn't exist");
+
+ /* Cleanup. */
+ rte_efd_free(handle);
+
+ return 0;
+}
+
+/*
+ * Sequence of operations for 5 keys
+ * - add keys
+ * - lookup keys: hit (bulk)
+ * - add keys (update)
+ * - lookup keys: hit (updated data)
+ * - delete keys : hit
+ */
+static int test_five_keys(void)
+{
+ struct rte_efd_table *handle;
+ const void *key_array[5] = {0};
+ efd_value_t result[5] = {0};
+ efd_value_t prev_value;
+ unsigned int i;
+ printf("Entering %s\n", __func__);
+
+ handle = rte_efd_create("test_five_keys", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the efd table\n");
+
+ /* Setup data */
+ for (i = 0; i < 5; i++)
+ data[i] = mrand48() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+
+ /* Add */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id,
+ &keys[i], data[i]),
+ "Error inserting the key");
+ print_key_info("Add", &keys[i], data[i]);
+ }
+
+ /* Lookup */
+ for (i = 0; i < 5; i++)
+ key_array[i] = &keys[i];
+
+ rte_efd_lookup_bulk(handle, test_socket_id, 5,
+ (const void **) (void *) &key_array, result);
+
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_EQUAL(result[i], data[i],
+ "bulk: failed to find key. Expected %d, got %d",
+ data[i], result[i]);
+ print_key_info("Lkp", &keys[i], data[i]);
+ }
+
+ /* Modify data (bulk) */
+ for (i = 0; i < 5; i++)
+ data[i] = data[i] + 1;
+
+ /* Add - update */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id,
+ &keys[i], data[i]),
+ "Error inserting the key");
+ print_key_info("Add", &keys[i], data[i]);
+ }
+
+ /* Lookup */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id,
+ &keys[i]), data[i],
+ "failed to find key");
+ print_key_info("Lkp", &keys[i], data[i]);
+ }
+
+ /* Delete */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_SUCCESS(rte_efd_delete(handle, test_socket_id,
+ &keys[i], &prev_value),
+ "failed to delete key");
+ TEST_ASSERT_EQUAL(prev_value, data[i],
+ "failed to delete the expected value, got %d, "
+ "expected %d", prev_value, data[i]);
+ print_key_info("Del", &keys[i], data[i]);
+ }
+
+
+ rte_efd_free(handle);
+
+ return 0;
+}
+
+/*
+ * Test to see the average table utilization (entries added/max entries)
+ * before hitting a random entry that cannot be added
+ */
+static int test_average_table_utilization(void)
+{
+ struct rte_efd_table *handle = NULL;
+ uint32_t num_rules_in = TABLE_SIZE;
+ uint8_t simple_key[EFD_TEST_KEY_LEN];
+ unsigned int i, j;
+ unsigned int added_keys, average_keys_added = 0;
+
+ printf("Evaluating table utilization and correctness, please wait\n");
+ fflush(stdout);
+
+ for (j = 0; j < ITERATIONS; j++) {
+ handle = rte_efd_create("test_efd", num_rules_in,
+ EFD_TEST_KEY_LEN, efd_get_all_sockets_bitmask(),
+ test_socket_id);
+ if (handle == NULL) {
+ printf("efd table creation failed\n");
+ return -1;
+ }
+
+ unsigned int succeeded = 0;
+ unsigned int lost_keys = 0;
+
+ /* Add random entries until key cannot be added */
+ for (added_keys = 0; added_keys < num_rules_in; added_keys++) {
+
+ for (i = 0; i < EFD_TEST_KEY_LEN; i++)
+ simple_key[i] = rte_rand() & 0xFF;
+
+ efd_value_t val = simple_key[0];
+
+ if (rte_efd_update(handle, test_socket_id, simple_key,
+ val))
+ break; /* continue;*/
+ if (rte_efd_lookup(handle, test_socket_id, simple_key)
+ != val)
+ lost_keys++;
+ else
+ succeeded++;
+ }
+
+ average_keys_added += succeeded;
+
+ /* Reset the table */
+ rte_efd_free(handle);
+
+ /* Print progress on operations */
+ printf("Added %10u Succeeded %10u Lost %10u\n",
+ added_keys, succeeded, lost_keys);
+ fflush(stdout);
+ }
+
+ average_keys_added /= ITERATIONS;
+
+ printf("\nAverage table utilization = %.2f%% (%u/%u)\n",
+ ((double) average_keys_added / num_rules_in * 100),
+ average_keys_added, num_rules_in);
+
+ return 0;
+}
+
+/*
+ * Do tests for EFD creation with bad parameters.
+ */
+static int test_efd_creation_with_bad_parameters(void)
+{
+ struct rte_efd_table *handle, *tmp;
+ printf("Entering %s, **Errors are expected **\n", __func__);
+
+ handle = rte_efd_create("creation_with_bad_parameters_0", TABLE_SIZE, 0,
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ if (handle != NULL) {
+ rte_efd_free(handle);
+ printf("Impossible creating EFD table successfully "
+ "if key_len in parameter is zero\n");
+ return -1;
+ }
+
+ handle = rte_efd_create("creation_with_bad_parameters_1", TABLE_SIZE,
+ sizeof(struct flow_key), 0, test_socket_id);
+ if (handle != NULL) {
+ rte_efd_free(handle);
+ printf("Impossible creating EFD table successfully "
+ "with invalid socket bitmask\n");
+ return -1;
+ }
+
+ handle = rte_efd_create("creation_with_bad_parameters_2", TABLE_SIZE,
+ sizeof(struct flow_key), efd_get_all_sockets_bitmask(),
+ 255);
+ if (handle != NULL) {
+ rte_efd_free(handle);
+ printf("Impossible creating EFD table successfully "
+ "with invalid socket\n");
+ return -1;
+ }
+
+ /* test with same name should fail */
+ handle = rte_efd_create("same_name", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), 0);
+ if (handle == NULL) {
+ printf("Cannot create first EFD table with 'same_name'\n");
+ return -1;
+ }
+ tmp = rte_efd_create("same_name", TABLE_SIZE, sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), 0);
+ if (tmp != NULL) {
+ printf("Creation of EFD table with same name should fail\n");
+ rte_efd_free(handle);
+ rte_efd_free(tmp);
+ return -1;
+ }
+ rte_efd_free(handle);
+
+ printf("# Test successful. No more errors expected\n");
+
+ return 0;
+}
+
+static int
+test_efd(void)
+{
+
+ /* Unit tests */
+ if (test_add_delete() < 0)
+ return -1;
+ if (test_efd_find_existing() < 0)
+ return -1;
+ if (test_add_update_delete() < 0)
+ return -1;
+ if (test_five_keys() < 0)
+ return -1;
+ if (test_efd_creation_with_bad_parameters() < 0)
+ return -1;
+ if (test_average_table_utilization() < 0)
+ return -1;
+
+ return 0;
+}
+
+REGISTER_TEST_COMMAND(efd_autotest, test_efd);
diff --git a/app/test/test_efd_perf.c b/app/test/test_efd_perf.c
new file mode 100644
index 0000000..998a25b
--- /dev/null
+++ b/app/test/test_efd_perf.c
@@ -0,0 +1,407 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <inttypes.h>
+
+#include <rte_lcore.h>
+#include <rte_cycles.h>
+#include <rte_malloc.h>
+#include <rte_random.h>
+#include <rte_efd.h>
+#include <rte_memcpy.h>
+#include <rte_thash.h>
+
+#include "test.h"
+#define NUM_KEYSIZES 10
+#define NUM_SHUFFLES 10
+#define MAX_KEYSIZE 64
+#define MAX_ENTRIES (1 << 19)
+#define KEYS_TO_ADD (MAX_ENTRIES * 3 / 4) /* 75% table utilization */
+#define NUM_LOOKUPS (KEYS_TO_ADD * 5) /* Loop among keys added, several times */
+static unsigned int test_socket_id;
+
+static inline uint8_t efd_get_all_sockets_bitmask(void)
+{
+ uint8_t all_cpu_sockets_bitmask = 0;
+ unsigned int i;
+ unsigned int next_lcore = rte_get_master_lcore();
+ const int val_true = 1, val_false = 0;
+ for (i = 0; i < rte_lcore_count(); i++) {
+ all_cpu_sockets_bitmask |= 1 << rte_lcore_to_socket_id(next_lcore);
+ next_lcore = rte_get_next_lcore(next_lcore, val_false, val_true);
+ }
+
+ return all_cpu_sockets_bitmask;
+}
+
+enum operations {
+ ADD = 0,
+ LOOKUP,
+ LOOKUP_MULTI,
+ DELETE,
+ NUM_OPERATIONS
+};
+
+struct efd_perf_params {
+ struct rte_efd_table *efd_table;
+ uint32_t key_size;
+ unsigned int cycle;
+};
+
+static uint32_t hashtest_key_lens[] = {
+ /* standard key sizes */
+ 4, 8, 16, 32, 48, 64,
+ /* IPv4 SRC + DST + protocol, unpadded */
+ 9,
+ /* IPv4 5-tuple, unpadded */
+ 13,
+ /* IPv6 5-tuple, unpadded */
+ 37,
+ /* IPv6 5-tuple, padded to 8-byte boundary */
+ 40
+};
+
+/* Array to store number of cycles per operation */
+uint64_t cycles[NUM_KEYSIZES][NUM_OPERATIONS];
+
+/* Array to store the data */
+efd_value_t data[KEYS_TO_ADD];
+
+/* Array to store all input keys */
+uint8_t keys[KEYS_TO_ADD][MAX_KEYSIZE];
+
+/* Shuffle the keys that have been added, so lookups will be totally random */
+static void
+shuffle_input_keys(struct efd_perf_params *params)
+{
+ efd_value_t temp_data;
+ unsigned int i;
+ uint32_t swap_idx;
+ uint8_t temp_key[MAX_KEYSIZE];
+
+ for (i = KEYS_TO_ADD - 1; i > 0; i--) {
+ swap_idx = rte_rand() % i;
+
+ memcpy(temp_key, keys[i], hashtest_key_lens[params->cycle]);
+ temp_data = data[i];
+
+ memcpy(keys[i], keys[swap_idx], hashtest_key_lens[params->cycle]);
+ data[i] = data[swap_idx];
+
+ memcpy(keys[swap_idx], temp_key, hashtest_key_lens[params->cycle]);
+ data[swap_idx] = temp_data;
+ }
+}
+
+static int key_compare(const void *key1, const void *key2)
+{
+ return memcmp(key1, key2, MAX_KEYSIZE);
+}
+
+/*
+ * TODO: we could "error proof" these as done in test_hash_perf.c ln 165:
+ *
+ * The current setup may give errors if too full in some cases which we check
+ * for. However, since EFD allows for ~99% capacity, these errors are rare for
+ * #"KEYS_TO_ADD" which is 75% capacity.
+ */
+static int
+setup_keys_and_data(struct efd_perf_params *params, unsigned int cycle)
+{
+ unsigned int i, j;
+ int num_duplicates;
+
+ params->key_size = hashtest_key_lens[cycle];
+ params->cycle = cycle;
+
+ /* Reset all arrays */
+ for (i = 0; i < params->key_size; i++)
+ keys[0][i] = 0;
+
+ /* Generate a list of keys, some of which may be duplicates */
+ for (i = 0; i < KEYS_TO_ADD; i++) {
+ for (j = 0; j < params->key_size; j++)
+ keys[i][j] = rte_rand() & 0xFF;
+
+ data[i] = rte_rand() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+ }
+
+ /* Remove duplicates from the keys array */
+ do {
+ num_duplicates = 0;
+
+ /* Sort the list of keys to make it easier to find duplicates */
+ qsort(keys, KEYS_TO_ADD, MAX_KEYSIZE, key_compare);
+
+ /* Sift through the list of keys and look for duplicates */
+ int num_duplicates = 0;
+ for (i = 0; i < KEYS_TO_ADD - 1; i++) {
+ if (memcmp(keys[i], keys[i + 1], params->key_size) == 0) {
+ /* This key already exists, try again */
+ num_duplicates++;
+ for (j = 0; j < params->key_size; j++)
+ keys[i][j] = rte_rand() & 0xFF;
+ }
+ }
+ } while (num_duplicates != 0);
+
+ /* Shuffle the random values again */
+ shuffle_input_keys(params);
+
+ params->efd_table = rte_efd_create("test_efd_perf",
+ MAX_ENTRIES, params->key_size,
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(params->efd_table, "Error creating the efd table\n");
+
+ return 0;
+}
+
+static int
+timed_adds(struct efd_perf_params *params)
+{
+ const uint64_t start_tsc = rte_rdtsc();
+ unsigned int i, a;
+ int32_t ret;
+
+ for (i = 0; i < KEYS_TO_ADD; i++) {
+ ret = rte_efd_update(params->efd_table, test_socket_id, keys[i],
+ data[i]);
+ if (ret != 0) {
+ printf("Error %d in rte_efd_update - key=0x", ret);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x", keys[i][a]);
+ printf(" value=%d\n", data[i]);
+
+ return -1;
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][ADD] = time_taken / KEYS_TO_ADD;
+ return 0;
+}
+
+static int
+timed_lookups(struct efd_perf_params *params)
+{
+ unsigned int i, j, a;
+ const uint64_t start_tsc = rte_rdtsc();
+ efd_value_t ret_data;
+
+ for (i = 0; i < NUM_LOOKUPS / KEYS_TO_ADD; i++) {
+ for (j = 0; j < KEYS_TO_ADD; j++) {
+ ret_data = rte_efd_lookup(params->efd_table,
+ test_socket_id, keys[j]);
+ if (ret_data != data[j]) {
+ printf("Value mismatch using rte_efd_lookup: "
+ "key #%d (0x", i);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x", keys[i][a]);
+ printf(")\n");
+ printf(" Expected %d, got %d\n", data[i],
+ ret_data);
+
+ return -1;
+ }
+
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][LOOKUP] = time_taken / NUM_LOOKUPS;
+
+ return 0;
+}
+
+static int
+timed_lookups_multi(struct efd_perf_params *params)
+{
+ unsigned int i, j, k, a;
+ efd_value_t result[RTE_EFD_BURST_MAX] = {0};
+ const void *keys_burst[RTE_EFD_BURST_MAX];
+ const uint64_t start_tsc = rte_rdtsc();
+
+ for (i = 0; i < NUM_LOOKUPS / KEYS_TO_ADD; i++) {
+ for (j = 0; j < KEYS_TO_ADD / RTE_EFD_BURST_MAX; j++) {
+ for (k = 0; k < RTE_EFD_BURST_MAX; k++)
+ keys_burst[k] = keys[j * RTE_EFD_BURST_MAX + k];
+
+ rte_efd_lookup_bulk(params->efd_table, test_socket_id,
+ RTE_EFD_BURST_MAX,
+ keys_burst, result);
+
+ for (k = 0; k < RTE_EFD_BURST_MAX; k++) {
+ uint32_t data_idx = j * RTE_EFD_BURST_MAX + k;
+ if (result[k] != data[data_idx]) {
+ printf("Value mismatch using "
+ "rte_efd_lookup_bulk: key #%d "
+ "(0x", i);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x",
+ keys[data_idx][a]);
+ printf(")\n");
+ printf(" Expected %d, got %d\n",
+ data[data_idx], result[k]);
+
+ return -1;
+ }
+ }
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][LOOKUP_MULTI] = time_taken / NUM_LOOKUPS;
+
+ return 0;
+}
+
+static int
+timed_deletes(struct efd_perf_params *params)
+{
+ unsigned int i, a;
+ const uint64_t start_tsc = rte_rdtsc();
+ int32_t ret;
+
+ for (i = 0; i < KEYS_TO_ADD; i++) {
+ ret = rte_efd_delete(params->efd_table, test_socket_id, keys[i],
+ NULL);
+
+ if (ret != 0) {
+ printf("Error %d in rte_efd_delete - key=0x", ret);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x", keys[i][a]);
+ printf("\n");
+
+ return -1;
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][DELETE] = time_taken / KEYS_TO_ADD;
+
+ return 0;
+}
+
+static void
+perform_frees(struct efd_perf_params *params)
+{
+ if (params->efd_table != NULL) {
+ rte_efd_free(params->efd_table);
+ params->efd_table = NULL;
+ }
+}
+
+static int
+exit_with_fail(const char *testname, struct efd_perf_params *params,
+ unsigned int i)
+{
+
+ printf("<<<<<Test %s failed at keysize %d iteration %d >>>>>\n",
+ testname, hashtest_key_lens[params->cycle], i);
+ perform_frees(params);
+ return -1;
+}
+
+static int
+run_all_tbl_perf_tests(void)
+{
+ unsigned int i, j;
+ struct efd_perf_params params;
+
+ printf("Measuring performance, please wait\n");
+ fflush(stdout);
+
+ test_socket_id = rte_socket_id();
+
+ for (i = 0; i < NUM_KEYSIZES; i++) {
+
+ if (setup_keys_and_data(¶ms, i) < 0) {
+ printf("Could not create keys/data/table\n");
+ return -1;
+ }
+
+ if (timed_adds(¶ms) < 0)
+ return exit_with_fail("timed_adds", ¶ms, i);
+
+ for (j = 0; j < NUM_SHUFFLES; j++)
+ shuffle_input_keys(¶ms);
+
+ if (timed_lookups(¶ms) < 0)
+ return exit_with_fail("timed_lookups", ¶ms, i);
+
+ if (timed_lookups_multi(¶ms) < 0)
+ return exit_with_fail("timed_lookups_multi", ¶ms, i);
+
+ if (timed_deletes(¶ms) < 0)
+ return exit_with_fail("timed_deletes", ¶ms, i);
+
+ /* Print a dot to show progress on operations */
+ printf(".");
+ fflush(stdout);
+
+ perform_frees(¶ms);
+ }
+
+ printf("\nResults (in CPU cycles/operation)\n");
+ printf("-----------------------------------\n");
+ printf("\n%-18s%-18s%-18s%-18s%-18s\n",
+ "Keysize", "Add", "Lookup", "Lookup_bulk", "Delete");
+ for (i = 0; i < NUM_KEYSIZES; i++) {
+ printf("%-18d", hashtest_key_lens[i]);
+ for (j = 0; j < NUM_OPERATIONS; j++)
+ printf("%-18"PRIu64, cycles[i][j]);
+ printf("\n");
+ }
+ return 0;
+}
+
+static int
+test_efd_perf(void)
+{
+
+ if (run_all_tbl_perf_tests() < 0)
+ return -1;
+
+ return 0;
+}
+
+REGISTER_TEST_COMMAND(efd_perf_autotest, test_efd_perf);
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v5 3/5] examples/flow_distributor: sample app to demonstrate EFD usage
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 0/5] Elastic Flow Distributor Pablo de Lara
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 1/5] efd: new Elastic Flow Distributor library Pablo de Lara
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 2/5] app/test: add EFD functional and perf tests Pablo de Lara
@ 2017-01-16 9:43 ` Pablo de Lara
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 4/5] doc: add EFD library section in Programmers guide Pablo de Lara
` (3 subsequent siblings)
6 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-16 9:43 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Saikrishna Edupuganti
This new sample app, based on the client/server sample app,
shows the user an scenario using the EFD library.
It consists of:
- A front-end server which has an EFD table that stores the
node id for each flow key, which will distribute the incoming
packets to the different nodes
- A back-end node, which has a hash table where node checks,
after reading packets coming from the server, whether the packet
is meant to be used in such node, in which case it will be TXed,
or not, in which case, packet will be dropped.
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 1 +
doc/api/examples.dox | 4 +
examples/Makefile | 1 +
examples/flow_distributor/Makefile | 44 +++
examples/flow_distributor/distributor/Makefile | 57 ++++
examples/flow_distributor/distributor/args.c | 200 ++++++++++++
examples/flow_distributor/distributor/args.h | 39 +++
examples/flow_distributor/distributor/init.c | 371 ++++++++++++++++++++++
examples/flow_distributor/distributor/init.h | 76 +++++
examples/flow_distributor/distributor/main.c | 362 +++++++++++++++++++++
examples/flow_distributor/node/Makefile | 48 +++
examples/flow_distributor/node/node.c | 417 +++++++++++++++++++++++++
examples/flow_distributor/shared/common.h | 99 ++++++
13 files changed, 1719 insertions(+)
create mode 100644 examples/flow_distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/args.c
create mode 100644 examples/flow_distributor/distributor/args.h
create mode 100644 examples/flow_distributor/distributor/init.c
create mode 100644 examples/flow_distributor/distributor/init.h
create mode 100644 examples/flow_distributor/distributor/main.c
create mode 100644 examples/flow_distributor/node/Makefile
create mode 100644 examples/flow_distributor/node/node.c
create mode 100644 examples/flow_distributor/shared/common.h
diff --git a/MAINTAINERS b/MAINTAINERS
index d812962..b124f6e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -533,6 +533,7 @@ M: Byron Marohn <byron.marohn@intel.com>
M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
F: lib/librte_efd/
F: app/test/test_efd*
+F: examples/flow_distributor/
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
diff --git a/doc/api/examples.dox b/doc/api/examples.dox
index 1626852..c13e574 100644
--- a/doc/api/examples.dox
+++ b/doc/api/examples.dox
@@ -52,6 +52,10 @@
@example load_balancer/init.c
@example load_balancer/main.c
@example load_balancer/runtime.c
+@example flow_distributor/distributor/args.c
+@example flow_distributor/distributor/init.c
+@example flow_distributor/distributor/main.c
+@example flow_distributor/node/node.c
@example multi_process/client_server_mp/mp_client/client.c
@example multi_process/client_server_mp/mp_server/args.c
@example multi_process/client_server_mp/mp_server/init.c
diff --git a/examples/Makefile b/examples/Makefile
index d49c7f2..b404982 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -45,6 +45,7 @@ DIRS-y += dpdk_qat
endif
DIRS-y += ethtool
DIRS-y += exception_path
+DIRS-$(CONFIG_RTE_LIBRTE_EFD) += flow_distributor
DIRS-y += helloworld
DIRS-$(CONFIG_RTE_LIBRTE_PIPELINE) += ip_pipeline
ifeq ($(CONFIG_RTE_LIBRTE_LPM),y)
diff --git a/examples/flow_distributor/Makefile b/examples/flow_distributor/Makefile
new file mode 100644
index 0000000..5bae706
--- /dev/null
+++ b/examples/flow_distributor/Makefile
@@ -0,0 +1,44 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overridden by command line or environment
+RTE_TARGET ?= x86_64-native-linuxapp-gcc
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+DIRS-$(CONFIG_RTE_EXEC_ENV_LINUXAPP) += distributor
+DIRS-$(CONFIG_RTE_EXEC_ENV_LINUXAPP) += node
+
+include $(RTE_SDK)/mk/rte.extsubdir.mk
diff --git a/examples/flow_distributor/distributor/Makefile b/examples/flow_distributor/distributor/Makefile
new file mode 100644
index 0000000..8714151
--- /dev/null
+++ b/examples/flow_distributor/distributor/Makefile
@@ -0,0 +1,57 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overridden by command line or environment
+RTE_TARGET ?= x86_64-native-linuxapp-gcc
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+ifneq ($(CONFIG_RTE_EXEC_ENV), "linuxapp")
+$(error This application can only operate in a linuxapp environment, \
+please change the definition of the RTE_TARGET environment variable)
+endif
+
+# binary name
+APP = distributor
+
+# all source are stored in SRCS-y
+SRCS-y := main.c init.c args.c
+
+INC := $(wildcard *.h)
+
+CFLAGS += $(WERROR_FLAGS) -O3
+CFLAGS += -I$(SRCDIR)/../shared
+
+include $(RTE_SDK)/mk/rte.extapp.mk
diff --git a/examples/flow_distributor/distributor/args.c b/examples/flow_distributor/distributor/args.c
new file mode 100644
index 0000000..ee29203
--- /dev/null
+++ b/examples/flow_distributor/distributor/args.c
@@ -0,0 +1,200 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <getopt.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include <rte_memory.h>
+#include <rte_string_fns.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+/* 1M flows by default */
+#define DEFAULT_NUM_FLOWS 0x100000
+
+/* global var for number of nodes - extern in header */
+uint8_t num_nodes;
+/* global var for number of flows - extern in header */
+uint32_t num_flows = DEFAULT_NUM_FLOWS;
+
+static const char *progname;
+
+/**
+ * Prints out usage information to stdout
+ */
+static void
+usage(void)
+{
+ printf("%s [EAL options] -- -p PORTMASK -n NUM_NODES -f NUM_FLOWS\n"
+ " -p PORTMASK: hexadecimal bitmask of ports to use\n"
+ " -n NUM_NODES: number of node processes to use\n"
+ " -f NUM_FLOWS: number of flows to be added in the EFD table\n",
+ progname);
+}
+
+/**
+ * The ports to be used by the application are passed in
+ * the form of a bitmask. This function parses the bitmask
+ * and places the port numbers to be used into the port[]
+ * array variable
+ */
+static int
+parse_portmask(uint8_t max_ports, const char *portmask)
+{
+ char *end = NULL;
+ unsigned long pm;
+ uint8_t count = 0;
+
+ if (portmask == NULL || *portmask == '\0')
+ return -1;
+
+ /* convert parameter to a number and verify */
+ pm = strtoul(portmask, &end, 16);
+ if (end == NULL || *end != '\0' || pm == 0)
+ return -1;
+
+ /* loop through bits of the mask and mark ports */
+ while (pm != 0) {
+ if (pm & 0x01) { /* bit is set in mask, use port */
+ if (count >= max_ports)
+ printf("WARNING: requested port %u not present"
+ " - ignoring\n", (unsigned int)count);
+ else
+ info->id[info->num_ports++] = count;
+ }
+ pm = (pm >> 1);
+ count++;
+ }
+
+ return 0;
+}
+
+/**
+ * Take the number of nodes parameter passed to the app
+ * and convert to a number to store in the num_nodes variable
+ */
+static int
+parse_num_nodes(const char *nodes)
+{
+ char *end = NULL;
+ unsigned long temp;
+
+ if (nodes == NULL || *nodes == '\0')
+ return -1;
+
+ temp = strtoul(nodes, &end, 10);
+ if (end == NULL || *end != '\0' || temp == 0)
+ return -1;
+
+ num_nodes = (uint8_t)temp;
+ return 0;
+}
+
+static int
+parse_num_flows(const char *flows)
+{
+ char *end = NULL;
+
+ /* parse hexadecimal string */
+ num_flows = strtoul(flows, &end, 16);
+ if ((flows[0] == '\0') || (end == NULL) || (*end != '\0'))
+ return -1;
+
+ if (num_flows == 0)
+ return -1;
+
+ return 0;
+}
+
+/**
+ * The application specific arguments follow the DPDK-specific
+ * arguments which are stripped by the DPDK init. This function
+ * processes these application arguments, printing usage info
+ * on error.
+ */
+int
+parse_app_args(uint8_t max_ports, int argc, char *argv[])
+{
+ int option_index, opt;
+ char **argvopt = argv;
+ static struct option lgopts[] = { /* no long options */
+ {NULL, 0, 0, 0 }
+ };
+ progname = argv[0];
+
+ while ((opt = getopt_long(argc, argvopt, "n:f:p:", lgopts,
+ &option_index)) != EOF) {
+ switch (opt) {
+ case 'p':
+ if (parse_portmask(max_ports, optarg) != 0) {
+ usage();
+ return -1;
+ }
+ break;
+ case 'n':
+ if (parse_num_nodes(optarg) != 0) {
+ usage();
+ return -1;
+ }
+ break;
+ case 'f':
+ if (parse_num_flows(optarg) != 0) {
+ usage();
+ return -1;
+ }
+ break;
+ default:
+ printf("ERROR: Unknown option '%c'\n", opt);
+ usage();
+ return -1;
+ }
+ }
+
+ if (info->num_ports == 0 || num_nodes == 0) {
+ usage();
+ return -1;
+ }
+
+ if (info->num_ports % 2 != 0) {
+ printf("ERROR: application requires an even "
+ "number of ports to use\n");
+ return -1;
+ }
+ return 0;
+}
diff --git a/examples/flow_distributor/distributor/args.h b/examples/flow_distributor/distributor/args.h
new file mode 100644
index 0000000..cacf395
--- /dev/null
+++ b/examples/flow_distributor/distributor/args.h
@@ -0,0 +1,39 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _ARGS_H_
+#define _ARGS_H_
+
+int parse_app_args(uint8_t max_ports, int argc, char *argv[]);
+
+#endif /* ifndef _ARGS_H_ */
diff --git a/examples/flow_distributor/distributor/init.c b/examples/flow_distributor/distributor/init.c
new file mode 100644
index 0000000..3b0aa85
--- /dev/null
+++ b/examples/flow_distributor/distributor/init.c
@@ -0,0 +1,371 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/queue.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <inttypes.h>
+
+#include <rte_common.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_byteorder.h>
+#include <rte_atomic.h>
+#include <rte_launch.h>
+#include <rte_per_lcore.h>
+#include <rte_lcore.h>
+#include <rte_branch_prediction.h>
+#include <rte_debug.h>
+#include <rte_ring.h>
+#include <rte_log.h>
+#include <rte_mempool.h>
+#include <rte_memcpy.h>
+#include <rte_mbuf.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ether.h>
+#include <rte_ethdev.h>
+#include <rte_malloc.h>
+#include <rte_string_fns.h>
+#include <rte_cycles.h>
+#include <rte_efd.h>
+#include <rte_hash.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+#define MBUFS_PER_NODE 1536
+#define MBUFS_PER_PORT 1536
+#define MBUF_CACHE_SIZE 512
+
+#define RTE_MP_RX_DESC_DEFAULT 512
+#define RTE_MP_TX_DESC_DEFAULT 512
+#define NODE_QUEUE_RINGSIZE 128
+
+#define NO_FLAGS 0
+
+/* The mbuf pool for packet rx */
+struct rte_mempool *pktmbuf_pool;
+
+/* array of info/queues for nodes */
+struct node *nodes;
+
+/* Flow distributor table */
+struct rte_efd_table *efd_table;
+
+/* Shared info between distributor and nodes */
+struct shared_info *info;
+
+/**
+ * Initialise the mbuf pool for packet reception for the NIC, and any other
+ * buffer pools needed by the app - currently none.
+ */
+static int
+init_mbuf_pools(void)
+{
+ const unsigned int num_mbufs = (num_nodes * MBUFS_PER_NODE) +
+ (info->num_ports * MBUFS_PER_PORT);
+
+ /*
+ * Don't pass single-producer/single-consumer flags to mbuf create as it
+ * seems faster to use a cache instead
+ */
+ printf("Creating mbuf pool '%s' [%u mbufs] ...\n",
+ PKTMBUF_POOL_NAME, num_mbufs);
+ pktmbuf_pool = rte_pktmbuf_pool_create(PKTMBUF_POOL_NAME, num_mbufs,
+ MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
+
+ return pktmbuf_pool == NULL; /* 0 on success */
+}
+
+/**
+ * Initialise an individual port:
+ * - configure number of rx and tx rings
+ * - set up each rx ring, to pull from the main mbuf pool
+ * - set up each tx ring
+ * - start the port and report its status to stdout
+ */
+static int
+init_port(uint8_t port_num)
+{
+ /* for port configuration all features are off by default */
+ const struct rte_eth_conf port_conf = {
+ .rxmode = {
+ .mq_mode = ETH_MQ_RX_RSS
+ }
+ };
+ const uint16_t rx_rings = 1, tx_rings = num_nodes;
+ const uint16_t rx_ring_size = RTE_MP_RX_DESC_DEFAULT;
+ const uint16_t tx_ring_size = RTE_MP_TX_DESC_DEFAULT;
+
+ uint16_t q;
+ int retval;
+
+ printf("Port %u init ... ", (unsigned int)port_num);
+ fflush(stdout);
+
+ /*
+ * Standard DPDK port initialisation - config port, then set up
+ * rx and tx rings.
+ */
+ retval = rte_eth_dev_configure(port_num, rx_rings, tx_rings, &port_conf);
+ if (retval != 0)
+ return retval;
+
+ for (q = 0; q < rx_rings; q++) {
+ retval = rte_eth_rx_queue_setup(port_num, q, rx_ring_size,
+ rte_eth_dev_socket_id(port_num),
+ NULL, pktmbuf_pool);
+ if (retval < 0)
+ return retval;
+ }
+
+ for (q = 0; q < tx_rings; q++) {
+ retval = rte_eth_tx_queue_setup(port_num, q, tx_ring_size,
+ rte_eth_dev_socket_id(port_num),
+ NULL);
+ if (retval < 0)
+ return retval;
+ }
+
+ rte_eth_promiscuous_enable(port_num);
+
+ retval = rte_eth_dev_start(port_num);
+ if (retval < 0)
+ return retval;
+
+ printf("done:\n");
+
+ return 0;
+}
+
+/**
+ * Set up the DPDK rings which will be used to pass packets, via
+ * pointers, between the multi-process distributor and node processes.
+ * Each node needs one RX queue.
+ */
+static int
+init_shm_rings(void)
+{
+ unsigned int i;
+ unsigned int socket_id;
+ const char *q_name;
+ const unsigned int ringsize = NODE_QUEUE_RINGSIZE;
+
+ nodes = rte_malloc("node details",
+ sizeof(*nodes) * num_nodes, 0);
+ if (nodes == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot allocate memory for "
+ "node program details\n");
+
+ for (i = 0; i < num_nodes; i++) {
+ /* Create an RX queue for each node */
+ socket_id = rte_socket_id();
+ q_name = get_rx_queue_name(i);
+ nodes[i].rx_q = rte_ring_create(q_name,
+ ringsize, socket_id,
+ RING_F_SP_ENQ | RING_F_SC_DEQ);
+ if (nodes[i].rx_q == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot create rx ring queue "
+ "for node %u\n", i);
+ }
+ return 0;
+}
+
+/*
+ * Create flow distributor table which will contain all the flows
+ * that will be distributed among the nodes
+ */
+static void
+create_flow_distributor_table(void)
+{
+ uint8_t socket_id = rte_socket_id();
+
+ /* create table */
+ efd_table = rte_efd_create("flow table", num_flows * 2, sizeof(uint32_t),
+ 1 << socket_id, socket_id);
+
+ if (efd_table == NULL)
+ rte_exit(EXIT_FAILURE, "Problem creating the flow table\n");
+}
+
+static void
+populate_flow_distributor_table(void)
+{
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint8_t socket_id = rte_socket_id();
+ uint64_t node_id;
+
+ /* Add flows in table */
+ for (i = 0; i < num_flows; i++) {
+ node_id = i % num_nodes;
+
+ ip_dst = rte_cpu_to_be_32(i);
+ ret = rte_efd_update(efd_table, socket_id,
+ (void *)&ip_dst, (efd_value_t)node_id);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u in "
+ "flow distributor table\n", i);
+ }
+
+ printf("EFD table: Adding 0x%x keys\n", num_flows);
+}
+
+/* Check the link status of all ports in up to 9s, and print them finally */
+static void
+check_all_ports_link_status(uint8_t port_num, uint32_t port_mask)
+{
+#define CHECK_INTERVAL 100 /* 100ms */
+#define MAX_CHECK_TIME 90 /* 9s (90 * 100ms) in total */
+ uint8_t portid, count, all_ports_up, print_flag = 0;
+ struct rte_eth_link link;
+
+ printf("\nChecking link status");
+ fflush(stdout);
+ for (count = 0; count <= MAX_CHECK_TIME; count++) {
+ all_ports_up = 1;
+ for (portid = 0; portid < port_num; portid++) {
+ if ((port_mask & (1 << info->id[portid])) == 0)
+ continue;
+ memset(&link, 0, sizeof(link));
+ rte_eth_link_get_nowait(info->id[portid], &link);
+ /* print link status if flag set */
+ if (print_flag == 1) {
+ if (link.link_status)
+ printf("Port %d Link Up - speed %u "
+ "Mbps - %s\n", info->id[portid],
+ (unsigned int)link.link_speed,
+ (link.link_duplex == ETH_LINK_FULL_DUPLEX) ?
+ ("full-duplex") : ("half-duplex\n"));
+ else
+ printf("Port %d Link Down\n",
+ (uint8_t)info->id[portid]);
+ continue;
+ }
+ /* clear all_ports_up flag if any link down */
+ if (link.link_status == ETH_LINK_DOWN) {
+ all_ports_up = 0;
+ break;
+ }
+ }
+ /* after finally printing all link status, get out */
+ if (print_flag == 1)
+ break;
+
+ if (all_ports_up == 0) {
+ printf(".");
+ fflush(stdout);
+ rte_delay_ms(CHECK_INTERVAL);
+ }
+
+ /* set the print_flag if all ports up or timeout */
+ if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
+ print_flag = 1;
+ printf("done\n");
+ }
+ }
+}
+
+/**
+ * Main init function for the multi-process distributor app,
+ * calls subfunctions to do each stage of the initialisation.
+ */
+int
+init(int argc, char *argv[])
+{
+ int retval;
+ const struct rte_memzone *mz;
+ uint8_t i, total_ports;
+
+ /* init EAL, parsing EAL args */
+ retval = rte_eal_init(argc, argv);
+ if (retval < 0)
+ return -1;
+ argc -= retval;
+ argv += retval;
+
+ /* get total number of ports */
+ total_ports = rte_eth_dev_count();
+
+ /* set up array for port data */
+ mz = rte_memzone_reserve(MZ_SHARED_INFO, sizeof(*info),
+ rte_socket_id(), NO_FLAGS);
+ if (mz == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot reserve memory zone "
+ "for port information\n");
+ memset(mz->addr, 0, sizeof(*info));
+ info = mz->addr;
+
+ /* parse additional, application arguments */
+ retval = parse_app_args(total_ports, argc, argv);
+ if (retval != 0)
+ return -1;
+
+ /* initialise mbuf pools */
+ retval = init_mbuf_pools();
+ if (retval != 0)
+ rte_exit(EXIT_FAILURE, "Cannot create needed mbuf pools\n");
+
+ /* now initialise the ports we will use */
+ for (i = 0; i < info->num_ports; i++) {
+ retval = init_port(info->id[i]);
+ if (retval != 0)
+ rte_exit(EXIT_FAILURE, "Cannot initialise port %u\n",
+ (unsigned int) i);
+ }
+
+ check_all_ports_link_status(info->num_ports, (~0x0));
+
+ /* initialise the node queues/rings for inter-eu comms */
+ init_shm_rings();
+
+ /* Create the flow distributor table */
+ create_flow_distributor_table();
+
+ /* Populate the flow distributor table */
+ populate_flow_distributor_table();
+
+ /* Share the total number of nodes */
+ info->num_nodes = num_nodes;
+
+ /* Share the total number of flows */
+ info->num_flows = num_flows;
+ return 0;
+}
diff --git a/examples/flow_distributor/distributor/init.h b/examples/flow_distributor/distributor/init.h
new file mode 100644
index 0000000..d11aacf
--- /dev/null
+++ b/examples/flow_distributor/distributor/init.h
@@ -0,0 +1,76 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _INIT_H_
+#define _INIT_H_
+
+/*
+ * #include <rte_ring.h>
+ * #include "args.h"
+ */
+
+/*
+ * Define a node structure with all needed info, including
+ * stats from the nodes.
+ */
+struct node {
+ struct rte_ring *rx_q;
+ unsigned int node_id;
+ /* these stats hold how many packets the node will actually receive,
+ * and how many packets were dropped because the node's queue was full.
+ * The port-info stats, in contrast, record how many packets were received
+ * or transmitted on an actual NIC port.
+ */
+ struct {
+ uint64_t rx;
+ uint64_t rx_drop;
+ } stats;
+};
+
+extern struct rte_efd_table *efd_table;
+extern struct node *nodes;
+
+/*
+ * shared information between distributor and nodes: number of clients,
+ * port numbers, rx and tx stats etc.
+ */
+extern struct shared_info *info;
+
+extern struct rte_mempool *pktmbuf_pool;
+extern uint8_t num_nodes;
+extern unsigned int num_sockets;
+extern uint32_t num_flows;
+
+int init(int argc, char *argv[]);
+
+#endif /* ifndef _INIT_H_ */
diff --git a/examples/flow_distributor/distributor/main.c b/examples/flow_distributor/distributor/main.c
new file mode 100644
index 0000000..f97f003
--- /dev/null
+++ b/examples/flow_distributor/distributor/main.c
@@ -0,0 +1,362 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <inttypes.h>
+#include <sys/queue.h>
+#include <errno.h>
+#include <netinet/ip.h>
+
+#include <rte_common.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_byteorder.h>
+#include <rte_launch.h>
+#include <rte_per_lcore.h>
+#include <rte_lcore.h>
+#include <rte_branch_prediction.h>
+#include <rte_atomic.h>
+#include <rte_ring.h>
+#include <rte_log.h>
+#include <rte_debug.h>
+#include <rte_mempool.h>
+#include <rte_memcpy.h>
+#include <rte_mbuf.h>
+#include <rte_ether.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ethdev.h>
+#include <rte_byteorder.h>
+#include <rte_malloc.h>
+#include <rte_string_fns.h>
+#include <rte_efd.h>
+#include <rte_ip.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+/*
+ * When doing reads from the NIC or the node queues,
+ * use this batch size
+ */
+#define PACKET_READ_SIZE 32
+
+/*
+ * Local buffers to put packets in, used to send packets in bursts to the
+ * nodes
+ */
+struct node_rx_buf {
+ struct rte_mbuf *buffer[PACKET_READ_SIZE];
+ uint16_t count;
+};
+
+struct flow_distributor_stats {
+ uint64_t distributed;
+ uint64_t drop;
+} flow_dist_stats;
+
+/* One buffer per node rx queue - dynamically allocate array */
+static struct node_rx_buf *cl_rx_buf;
+
+static const char *
+get_printable_mac_addr(uint8_t port)
+{
+ static const char err_address[] = "00:00:00:00:00:00";
+ static char addresses[RTE_MAX_ETHPORTS][sizeof(err_address)];
+ struct ether_addr mac;
+
+ if (unlikely(port >= RTE_MAX_ETHPORTS))
+ return err_address;
+ if (unlikely(addresses[port][0] == '\0')) {
+ rte_eth_macaddr_get(port, &mac);
+ snprintf(addresses[port], sizeof(addresses[port]),
+ "%02x:%02x:%02x:%02x:%02x:%02x\n",
+ mac.addr_bytes[0], mac.addr_bytes[1],
+ mac.addr_bytes[2], mac.addr_bytes[3],
+ mac.addr_bytes[4], mac.addr_bytes[5]);
+ }
+ return addresses[port];
+}
+
+/*
+ * This function displays the recorded statistics for each port
+ * and for each node. It uses ANSI terminal codes to clear
+ * screen when called. It is called from a single non-master
+ * thread in the distributor process, when the process is run with more
+ * than one lcore enabled.
+ */
+static void
+do_stats_display(void)
+{
+ unsigned int i, j;
+ const char clr[] = {27, '[', '2', 'J', '\0'};
+ const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
+ uint64_t port_tx[RTE_MAX_ETHPORTS], port_tx_drop[RTE_MAX_ETHPORTS];
+ uint64_t node_tx[MAX_NODES], node_tx_drop[MAX_NODES];
+
+ /* to get TX stats, we need to do some summing calculations */
+ memset(port_tx, 0, sizeof(port_tx));
+ memset(port_tx_drop, 0, sizeof(port_tx_drop));
+ memset(node_tx, 0, sizeof(node_tx));
+ memset(node_tx_drop, 0, sizeof(node_tx_drop));
+
+ for (i = 0; i < num_nodes; i++) {
+ const struct tx_stats *tx = &info->tx_stats[i];
+
+ for (j = 0; j < info->num_ports; j++) {
+ const uint64_t tx_val = tx->tx[info->id[j]];
+ const uint64_t drop_val = tx->tx_drop[info->id[j]];
+
+ port_tx[j] += tx_val;
+ port_tx_drop[j] += drop_val;
+ node_tx[i] += tx_val;
+ node_tx_drop[i] += drop_val;
+ }
+ }
+
+ /* Clear screen and move to top left */
+ printf("%s%s", clr, topLeft);
+
+ printf("PORTS\n");
+ printf("-----\n");
+ for (i = 0; i < info->num_ports; i++)
+ printf("Port %u: '%s'\t", (unsigned int)info->id[i],
+ get_printable_mac_addr(info->id[i]));
+ printf("\n\n");
+ for (i = 0; i < info->num_ports; i++) {
+ printf("Port %u - rx: %9"PRIu64"\t"
+ "tx: %9"PRIu64"\n",
+ (unsigned int)info->id[i], info->rx_stats.rx[i],
+ port_tx[i]);
+ }
+
+ printf("\nFLOW DISTRIBUTOR\n");
+ printf("-----\n");
+ printf("distributed: %9"PRIu64", drop: %9"PRIu64"\n",
+ flow_dist_stats.distributed, flow_dist_stats.drop);
+
+ printf("\nNODES\n");
+ printf("-------\n");
+ for (i = 0; i < num_nodes; i++) {
+ const unsigned long long rx = nodes[i].stats.rx;
+ const unsigned long long rx_drop = nodes[i].stats.rx_drop;
+ const struct filter_stats *filter = &info->filter_stats[i];
+
+ printf("Node %2u - rx: %9llu, rx_drop: %9llu\n"
+ " tx: %9"PRIu64", tx_drop: %9"PRIu64"\n"
+ " filter_passed: %9"PRIu64", "
+ "filter_drop: %9"PRIu64"\n",
+ i, rx, rx_drop, node_tx[i], node_tx_drop[i],
+ filter->passed, filter->drop);
+ }
+
+ printf("\n");
+}
+
+/*
+ * The function called from each non-master lcore used by the process.
+ * The test_and_set function is used to randomly pick a single lcore on which
+ * the code to display the statistics will run. Otherwise, the code just
+ * repeatedly sleeps.
+ */
+static int
+sleep_lcore(__attribute__((unused)) void *dummy)
+{
+ /* Used to pick a display thread - static, so zero-initialised */
+ static rte_atomic32_t display_stats;
+
+ /* Only one core should display stats */
+ if (rte_atomic32_test_and_set(&display_stats)) {
+ const unsigned int sleeptime = 1;
+
+ printf("Core %u displaying statistics\n", rte_lcore_id());
+
+ /* Longer initial pause so above printf is seen */
+ sleep(sleeptime * 3);
+
+ /* Loop forever: sleep always returns 0 or <= param */
+ while (sleep(sleeptime) <= sleeptime)
+ do_stats_display();
+ }
+ return 0;
+}
+
+/*
+ * Function to set all the node statistic values to zero.
+ * Called at program startup.
+ */
+static void
+clear_stats(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_nodes; i++)
+ nodes[i].stats.rx = nodes[i].stats.rx_drop = 0;
+}
+
+/*
+ * send a burst of traffic to a node, assuming there are packets
+ * available to be sent to this node
+ */
+static void
+flush_rx_queue(uint16_t node)
+{
+ uint16_t j;
+ struct node *cl;
+
+ if (cl_rx_buf[node].count == 0)
+ return;
+
+ cl = &nodes[node];
+ if (rte_ring_enqueue_bulk(cl->rx_q, (void **)cl_rx_buf[node].buffer,
+ cl_rx_buf[node].count) != 0){
+ for (j = 0; j < cl_rx_buf[node].count; j++)
+ rte_pktmbuf_free(cl_rx_buf[node].buffer[j]);
+ cl->stats.rx_drop += cl_rx_buf[node].count;
+ } else
+ cl->stats.rx += cl_rx_buf[node].count;
+
+ cl_rx_buf[node].count = 0;
+}
+
+/*
+ * marks a packet down to be sent to a particular node process
+ */
+static inline void
+enqueue_rx_packet(uint8_t node, struct rte_mbuf *buf)
+{
+ cl_rx_buf[node].buffer[cl_rx_buf[node].count++] = buf;
+}
+
+/*
+ * This function takes a group of packets and routes them
+ * individually to the node process. Very simply round-robins the packets
+ * without checking any of the packet contents.
+ */
+static void
+process_packets(uint32_t port_num __rte_unused, struct rte_mbuf *pkts[],
+ uint16_t rx_count, unsigned int socket_id)
+{
+ uint16_t i;
+ uint8_t node;
+ efd_value_t data[RTE_EFD_BURST_MAX];
+ const void *key_ptrs[RTE_EFD_BURST_MAX];
+
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[RTE_EFD_BURST_MAX];
+
+ for (i = 0; i < rx_count; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(pkts[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = (void *)&ipv4_dst_ip[i];
+ }
+
+ rte_efd_lookup_bulk(efd_table, socket_id, rx_count,
+ (const void **) key_ptrs, data);
+ for (i = 0; i < rx_count; i++) {
+ node = (uint8_t) ((uintptr_t)data[i]);
+
+ if (node >= num_nodes) {
+ /*
+ * Node is out of range, which means that
+ * flow has not been inserted
+ */
+ flow_dist_stats.drop++;
+ rte_pktmbuf_free(pkts[i]);
+ } else {
+ flow_dist_stats.distributed++;
+ enqueue_rx_packet(node, pkts[i]);
+ }
+ }
+
+ for (i = 0; i < num_nodes; i++)
+ flush_rx_queue(i);
+}
+
+/*
+ * Function called by the master lcore of the DPDK process.
+ */
+static void
+do_packet_forwarding(void)
+{
+ unsigned int port_num = 0; /* indexes the port[] array */
+ unsigned int socket_id = rte_socket_id();
+
+ for (;;) {
+ struct rte_mbuf *buf[PACKET_READ_SIZE];
+ uint16_t rx_count;
+
+ /* read a port */
+ rx_count = rte_eth_rx_burst(info->id[port_num], 0,
+ buf, PACKET_READ_SIZE);
+ info->rx_stats.rx[port_num] += rx_count;
+
+ /* Now process the NIC packets read */
+ if (likely(rx_count > 0))
+ process_packets(port_num, buf, rx_count, socket_id);
+
+ /* move to next port */
+ if (++port_num == info->num_ports)
+ port_num = 0;
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ /* initialise the system */
+ if (init(argc, argv) < 0)
+ return -1;
+ RTE_LOG(INFO, APP, "Finished Process Init.\n");
+
+ cl_rx_buf = calloc(num_nodes, sizeof(cl_rx_buf[0]));
+
+ /* clear statistics */
+ clear_stats();
+
+ /* put all other cores to sleep bar master */
+ rte_eal_mp_remote_launch(sleep_lcore, NULL, SKIP_MASTER);
+
+ do_packet_forwarding();
+ return 0;
+}
diff --git a/examples/flow_distributor/node/Makefile b/examples/flow_distributor/node/Makefile
new file mode 100644
index 0000000..8cf7b65
--- /dev/null
+++ b/examples/flow_distributor/node/Makefile
@@ -0,0 +1,48 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overridden by command line or environment
+include $(RTE_SDK)/mk/rte.vars.mk
+
+# binary name
+APP = node
+
+# all source are stored in SRCS-y
+SRCS-y := node.c
+
+CFLAGS += $(WERROR_FLAGS) -O3
+CFLAGS += -I$(SRCDIR)/../shared
+
+include $(RTE_SDK)/mk/rte.extapp.mk
diff --git a/examples/flow_distributor/node/node.c b/examples/flow_distributor/node/node.c
new file mode 100644
index 0000000..1f1e7e7
--- /dev/null
+++ b/examples/flow_distributor/node/node.c
@@ -0,0 +1,417 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <sys/queue.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_malloc.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_atomic.h>
+#include <rte_branch_prediction.h>
+#include <rte_log.h>
+#include <rte_per_lcore.h>
+#include <rte_launch.h>
+#include <rte_lcore.h>
+#include <rte_ring.h>
+#include <rte_launch.h>
+#include <rte_lcore.h>
+#include <rte_debug.h>
+#include <rte_mempool.h>
+#include <rte_mbuf.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ether.h>
+#include <rte_ethdev.h>
+#include <rte_string_fns.h>
+#include <rte_ip.h>
+
+#include "common.h"
+
+/* Number of packets to attempt to read from queue */
+#define PKT_READ_SIZE ((uint16_t)32)
+
+/*
+ * Our node id number - tells us which rx queue to read, and NIC TX
+ * queue to write to.
+ */
+static uint8_t node_id;
+
+#define MBQ_CAPACITY 32
+
+/* maps input ports to output ports for packets */
+static uint8_t output_ports[RTE_MAX_ETHPORTS];
+
+/* buffers up a set of packet that are ready to send */
+struct rte_eth_dev_tx_buffer *tx_buffer[RTE_MAX_ETHPORTS];
+
+/* shared data from distributor. We update statistics here */
+static struct tx_stats *tx_stats;
+
+static struct filter_stats *filter_stats;
+
+/*
+ * print a usage message
+ */
+static void
+usage(const char *progname)
+{
+ printf("Usage: %s [EAL args] -- -n <node_id>\n\n", progname);
+}
+
+/*
+ * Convert the node id number from a string to an int.
+ */
+static int
+parse_node_num(const char *node)
+{
+ char *end = NULL;
+ unsigned long temp;
+
+ if (node == NULL || *node == '\0')
+ return -1;
+
+ temp = strtoul(node, &end, 10);
+ if (end == NULL || *end != '\0')
+ return -1;
+
+ node_id = (uint8_t)temp;
+ return 0;
+}
+
+/*
+ * Parse the application arguments to the node app.
+ */
+static int
+parse_app_args(int argc, char *argv[])
+{
+ int option_index, opt;
+ char **argvopt = argv;
+ const char *progname = NULL;
+ static struct option lgopts[] = { /* no long options */
+ {NULL, 0, 0, 0 }
+ };
+ progname = argv[0];
+
+ while ((opt = getopt_long(argc, argvopt, "n:", lgopts,
+ &option_index)) != EOF) {
+ switch (opt) {
+ case 'n':
+ if (parse_node_num(optarg) != 0) {
+ usage(progname);
+ return -1;
+ }
+ break;
+ default:
+ usage(progname);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Tx buffer error callback
+ */
+static void
+flush_tx_error_callback(struct rte_mbuf **unsent, uint16_t count,
+ void *userdata) {
+ int i;
+ uint8_t port_id = (uintptr_t)userdata;
+
+ tx_stats->tx_drop[port_id] += count;
+
+ /* free the mbufs which failed from transmit */
+ for (i = 0; i < count; i++)
+ rte_pktmbuf_free(unsent[i]);
+
+}
+
+static void
+configure_tx_buffer(uint8_t port_id, uint16_t size)
+{
+ int ret;
+
+ /* Initialize TX buffers */
+ tx_buffer[port_id] = rte_zmalloc_socket("tx_buffer",
+ RTE_ETH_TX_BUFFER_SIZE(size), 0,
+ rte_eth_dev_socket_id(port_id));
+ if (tx_buffer[port_id] == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot allocate buffer for tx "
+ "on port %u\n", (unsigned int) port_id);
+
+ rte_eth_tx_buffer_init(tx_buffer[port_id], size);
+
+ ret = rte_eth_tx_buffer_set_err_callback(tx_buffer[port_id],
+ flush_tx_error_callback, (void *)(intptr_t)port_id);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Cannot set error callback for "
+ "tx buffer on port %u\n", (unsigned int) port_id);
+}
+
+/*
+ * set up output ports so that all traffic on port gets sent out
+ * its paired port. Index using actual port numbers since that is
+ * what comes in the mbuf structure.
+ */
+static void
+configure_output_ports(const struct shared_info *info)
+{
+ int i;
+
+ if (info->num_ports > RTE_MAX_ETHPORTS)
+ rte_exit(EXIT_FAILURE, "Too many ethernet ports. "
+ "RTE_MAX_ETHPORTS = %u\n",
+ (unsigned int)RTE_MAX_ETHPORTS);
+ for (i = 0; i < info->num_ports - 1; i += 2) {
+ uint8_t p1 = info->id[i];
+ uint8_t p2 = info->id[i+1];
+
+ output_ports[p1] = p2;
+ output_ports[p2] = p1;
+
+ configure_tx_buffer(p1, MBQ_CAPACITY);
+ configure_tx_buffer(p2, MBQ_CAPACITY);
+
+ }
+}
+
+/*
+ * Create the hash table that will contain the flows that
+ * the node will handle, which will be used to decide if packet
+ * is transmitted or dropped.
+ */
+static struct rte_hash *
+create_hash_table(const struct shared_info *info)
+{
+ uint32_t num_flows_node = info->num_flows / info->num_nodes;
+ char name[RTE_HASH_NAMESIZE];
+ struct rte_hash *h;
+
+ /* create table */
+ struct rte_hash_parameters hash_params = {
+ .entries = num_flows_node * 2, /* table load = 50% */
+ .key_len = sizeof(uint32_t), /* Store IPv4 dest IP address */
+ .socket_id = rte_socket_id(),
+ .hash_func_init_val = 0,
+ };
+
+ snprintf(name, sizeof(name), "hash_table_%d", node_id);
+ hash_params.name = name;
+ h = rte_hash_create(&hash_params);
+
+ if (h == NULL)
+ rte_exit(EXIT_FAILURE,
+ "Problem creating the hash table for node %d\n",
+ node_id);
+ return h;
+}
+
+static void
+populate_hash_table(const struct rte_hash *h, const struct shared_info *info)
+{
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint32_t num_flows_node = 0;
+ uint64_t target_node;
+
+ /* Add flows in table */
+ for (i = 0; i < info->num_flows; i++) {
+ target_node = i % info->num_nodes;
+ if (target_node != node_id)
+ continue;
+
+ ip_dst = rte_cpu_to_be_32(i);
+
+ ret = rte_hash_add_key(h, (void *) &ip_dst);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u "
+ "in hash table\n", i);
+ else
+ num_flows_node++;
+
+ }
+
+ printf("Hash table: Adding 0x%x keys\n", num_flows_node);
+}
+
+/*
+ * This function performs routing of packets
+ * Just sends each input packet out an output port based solely on the input
+ * port it arrived on.
+ */
+static inline void
+transmit_packet(struct rte_mbuf *buf)
+{
+ int sent;
+ const uint8_t in_port = buf->port;
+ const uint8_t out_port = output_ports[in_port];
+ struct rte_eth_dev_tx_buffer *buffer = tx_buffer[out_port];
+
+ sent = rte_eth_tx_buffer(out_port, node_id, buffer, buf);
+ if (sent)
+ tx_stats->tx[out_port] += sent;
+
+}
+
+static inline void
+handle_packets(struct rte_hash *h, struct rte_mbuf **bufs, uint16_t num_packets)
+{
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[PKT_READ_SIZE];
+ const void *key_ptrs[PKT_READ_SIZE];
+ unsigned int i;
+ int32_t positions[PKT_READ_SIZE] = {0};
+
+ for (i = 0; i < num_packets; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(bufs[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = &ipv4_dst_ip[i];
+ }
+ /* Check if packets belongs to any flows handled by this node */
+ rte_hash_lookup_bulk(h, key_ptrs, num_packets, positions);
+
+ for (i = 0; i < num_packets; i++) {
+ if (likely(positions[i] >= 0)) {
+ filter_stats->passed++;
+ transmit_packet(bufs[i]);
+ } else {
+ filter_stats->drop++;
+ /* Drop packet, as flow is not handled by this node */
+ rte_pktmbuf_free(bufs[i]);
+ }
+ }
+}
+
+/*
+ * Application main function - loops through
+ * receiving and processing packets. Never returns
+ */
+int
+main(int argc, char *argv[])
+{
+ const struct rte_memzone *mz;
+ struct rte_ring *rx_ring;
+ struct rte_hash *h;
+ struct rte_mempool *mp;
+ struct shared_info *info;
+ int need_flush = 0; /* indicates whether we have unsent packets */
+ int retval;
+ void *pkts[PKT_READ_SIZE];
+ uint16_t sent;
+
+ retval = rte_eal_init(argc, argv);
+ if (retval < 0)
+ return -1;
+ argc -= retval;
+ argv += retval;
+
+ if (parse_app_args(argc, argv) < 0)
+ rte_exit(EXIT_FAILURE, "Invalid command-line arguments\n");
+
+ if (rte_eth_dev_count() == 0)
+ rte_exit(EXIT_FAILURE, "No Ethernet ports - bye\n");
+
+ rx_ring = rte_ring_lookup(get_rx_queue_name(node_id));
+ if (rx_ring == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get RX ring - "
+ "is distributor process running?\n");
+
+ mp = rte_mempool_lookup(PKTMBUF_POOL_NAME);
+ if (mp == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get mempool for mbufs\n");
+
+ mz = rte_memzone_lookup(MZ_SHARED_INFO);
+ if (mz == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get port info structure\n");
+ info = mz->addr;
+ tx_stats = &(info->tx_stats[node_id]);
+ filter_stats = &(info->filter_stats[node_id]);
+
+ configure_output_ports(info);
+
+ h = create_hash_table(info);
+
+ populate_hash_table(h, info);
+
+ RTE_LOG(INFO, APP, "Finished Process Init.\n");
+
+ printf("\nNode process %d handling packets\n", node_id);
+ printf("[Press Ctrl-C to quit ...]\n");
+
+ for (;;) {
+ uint16_t rx_pkts = PKT_READ_SIZE;
+ uint8_t port;
+
+ /*
+ * Try dequeuing max possible packets first, if that fails,
+ * get the most we can. Loop body should only execute once,
+ * maximum
+ */
+ while (rx_pkts > 0 &&
+ unlikely(rte_ring_dequeue_bulk(rx_ring, pkts,
+ rx_pkts) != 0))
+ rx_pkts = (uint16_t)RTE_MIN(rte_ring_count(rx_ring),
+ PKT_READ_SIZE);
+
+ if (unlikely(rx_pkts == 0)) {
+ if (need_flush)
+ for (port = 0; port < info->num_ports; port++) {
+ sent = rte_eth_tx_buffer_flush(
+ info->id[port],
+ node_id,
+ tx_buffer[port]);
+ if (unlikely(sent))
+ tx_stats->tx[port] += sent;
+ }
+ need_flush = 0;
+ continue;
+ }
+
+ handle_packets(h, (struct rte_mbuf **)pkts, rx_pkts);
+
+ need_flush = 1;
+ }
+}
diff --git a/examples/flow_distributor/shared/common.h b/examples/flow_distributor/shared/common.h
new file mode 100644
index 0000000..5dcffd6
--- /dev/null
+++ b/examples/flow_distributor/shared/common.h
@@ -0,0 +1,99 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _COMMON_H_
+#define _COMMON_H_
+
+#include <rte_hash_crc.h>
+#include <rte_hash.h>
+
+#define MAX_NODES 16
+/*
+ * Shared port info, including statistics information for display by distributor.
+ * Structure will be put in a memzone.
+ * - All port id values share one cache line as this data will be read-only
+ * during operation.
+ * - All rx statistic values share cache lines, as this data is written only
+ * by the distributor process. (rare reads by stats display)
+ * - The tx statistics have values for all ports per cache line, but the stats
+ * themselves are written by the nodes, so we have a distinct set, on different
+ * cache lines for each node to use.
+ */
+struct rx_stats {
+ uint64_t rx[RTE_MAX_ETHPORTS];
+} __rte_cache_aligned;
+
+struct tx_stats {
+ uint64_t tx[RTE_MAX_ETHPORTS];
+ uint64_t tx_drop[RTE_MAX_ETHPORTS];
+} __rte_cache_aligned;
+
+struct filter_stats {
+ uint64_t drop;
+ uint64_t passed;
+} __rte_cache_aligned;
+
+struct shared_info {
+ uint8_t num_nodes;
+ uint8_t num_ports;
+ uint32_t num_flows;
+ uint8_t id[RTE_MAX_ETHPORTS];
+ struct rx_stats rx_stats;
+ struct tx_stats tx_stats[MAX_NODES];
+ struct filter_stats filter_stats[MAX_NODES];
+};
+
+/* define common names for structures shared between distributor and node */
+#define MP_NODE_RXQ_NAME "MProc_Node_%u_RX"
+#define PKTMBUF_POOL_NAME "MProc_pktmbuf_pool"
+#define MZ_SHARED_INFO "MProc_shared_info"
+
+/*
+ * Given the rx queue name template above, get the queue name
+ */
+static inline const char *
+get_rx_queue_name(unsigned int id)
+{
+ /*
+ * Buffer for return value. Size calculated by %u being replaced
+ * by maximum 3 digits (plus an extra byte for safety)
+ */
+ static char buffer[sizeof(MP_NODE_RXQ_NAME) + 2];
+
+ snprintf(buffer, sizeof(buffer) - 1, MP_NODE_RXQ_NAME, id);
+ return buffer;
+}
+
+#define RTE_LOGTYPE_APP RTE_LOGTYPE_USER1
+
+#endif
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v5 4/5] doc: add EFD library section in Programmers guide
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 0/5] Elastic Flow Distributor Pablo de Lara
` (2 preceding siblings ...)
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 3/5] examples/flow_distributor: sample app to demonstrate EFD usage Pablo de Lara
@ 2017-01-16 9:43 ` Pablo de Lara
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 5/5] doc: add flow distributor guide Pablo de Lara
` (2 subsequent siblings)
6 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-16 9:43 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Sameh Gobriel
Signed-off-by: Sameh Gobriel <sameh.gobriel@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 1 +
doc/guides/prog_guide/efd_lib.rst | 440 +++++++++++
doc/guides/prog_guide/img/efd_i1.svg | 130 ++++
doc/guides/prog_guide/img/efd_i10.svg | 384 ++++++++++
doc/guides/prog_guide/img/efd_i11.svg | 319 ++++++++
doc/guides/prog_guide/img/efd_i12.svg | 1008 +++++++++++++++++++++++++
doc/guides/prog_guide/img/efd_i2.svg | 280 +++++++
doc/guides/prog_guide/img/efd_i3.svg | 634 ++++++++++++++++
doc/guides/prog_guide/img/efd_i4.svg | 203 ++++++
doc/guides/prog_guide/img/efd_i5.svg | 183 +++++
doc/guides/prog_guide/img/efd_i6.svg | 1254 ++++++++++++++++++++++++++++++++
doc/guides/prog_guide/img/efd_i7.svg | 790 ++++++++++++++++++++
doc/guides/prog_guide/img/efd_i8.svg | 182 +++++
doc/guides/prog_guide/img/efd_i9.svg | 390 ++++++++++
doc/guides/prog_guide/index.rst | 23 +
doc/guides/rel_notes/release_17_02.rst | 3 +
16 files changed, 6224 insertions(+)
create mode 100644 doc/guides/prog_guide/efd_lib.rst
create mode 100644 doc/guides/prog_guide/img/efd_i1.svg
create mode 100644 doc/guides/prog_guide/img/efd_i10.svg
create mode 100644 doc/guides/prog_guide/img/efd_i11.svg
create mode 100644 doc/guides/prog_guide/img/efd_i12.svg
create mode 100644 doc/guides/prog_guide/img/efd_i2.svg
create mode 100644 doc/guides/prog_guide/img/efd_i3.svg
create mode 100644 doc/guides/prog_guide/img/efd_i4.svg
create mode 100644 doc/guides/prog_guide/img/efd_i5.svg
create mode 100644 doc/guides/prog_guide/img/efd_i6.svg
create mode 100644 doc/guides/prog_guide/img/efd_i7.svg
create mode 100644 doc/guides/prog_guide/img/efd_i8.svg
create mode 100644 doc/guides/prog_guide/img/efd_i9.svg
diff --git a/MAINTAINERS b/MAINTAINERS
index b124f6e..66e9466 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -532,6 +532,7 @@ EFD
M: Byron Marohn <byron.marohn@intel.com>
M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
F: lib/librte_efd/
+F: doc/guides/prog_guide/efd_lib.rst
F: app/test/test_efd*
F: examples/flow_distributor/
diff --git a/doc/guides/prog_guide/efd_lib.rst b/doc/guides/prog_guide/efd_lib.rst
new file mode 100644
index 0000000..5b8e4e3
--- /dev/null
+++ b/doc/guides/prog_guide/efd_lib.rst
@@ -0,0 +1,440 @@
+.. BSD LICENSE
+ Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of Intel Corporation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+.. _Efd_Library:
+
+Elastic Flow Distributor Library
+================================
+
+Introduction
+------------
+
+In Data Centers today, clustering and scheduling of distributed workloads
+is a very common task. Many workloads require a deterministic
+partitioning of a flat key space among a cluster of machines. When a
+packet enters the cluster, the ingress node will direct the packet to
+its handling node. For example, data-centers with disaggregated storage
+use storage metadata tables to forward I/O requests to the correct back end
+storage cluster, stateful packet inspection will use match incoming
+flows to signatures in flow tables to send incoming packets to their
+intended deep packet inspection (DPI) devices, and so on.
+
+EFD is a distributor library that uses perfect hashing to determine a
+target/value for a given incoming flow key. It has the following
+advantages: first, because it uses perfect hashing it does not store the
+key itself and hence lookup performance is not dependent on the key
+size. Second, the target/value can be any arbitrary value hence the
+system designer and/or operator can better optimize service rates and
+inter-cluster network traffic locating. Third, since the storage
+requirement is much smaller than a hash-based flow table (i.e. better
+fit for CPU cache), EFD can scale to millions of flow keys. Finally,
+with the current optimized library implementation, performance is fully
+scalable with any number of CPU cores.
+
+Flow Based Distribution
+-----------------------
+
+Computation Based Schemes
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Flow distribution and/or load balancing can be simply done using a
+stateless computation, for instance using round-robin or a simple
+computation based on the flow key as an input. For example, a hash
+function can be used to direct a certain flow to a target based on
+the flow key (e.g. ``h(key) mod n``) where h(key) is the hash value of the
+flow key and n is the number of possible targets.
+
+.. _figure_efd1:
+
+.. figure:: img/efd_i1.*
+
+ Load Balancing Using Front End Node
+
+In this scheme (:numref:`figure_efd1`), the front end server/distributor/load balancer
+extracts the flow key from the input packet and applies a computation to determine where
+this flow should be directed. Intuitively, this scheme is very simple
+and requires no state to be kept at the front end node, and hence,
+storage requirements are minimum.
+
+.. _figure_efd2:
+
+.. figure:: img/efd_i2.*
+
+ Consistent Hashing
+
+A widely used flow distributor that belongs to the same category of
+computation-based schemes is ``consistent hashing``, shown in :numref:`figure_efd2`.
+Target destinations (shown in red) are hashed into the same space as the flow
+keys (shown in blue), and keys are mapped to the nearest target in a clockwise
+fashion. Dynamically adding and removing targets with consistent hashing
+requires only K/n keys to be remapped on average, where K is the number of
+keys, and n is the number of targets. In contrast, in a traditional hash-based
+scheme, a change in the number of targets causes nearly all keys to be
+remapped.
+
+Although computation-based schemes are simple and need very little
+storage requirement, they suffer from the drawback that the system
+designer/operator can’t fully control the target to assign a specific
+key, as this is dictated by the hash function.
+Deterministically co-locating of keys together (for example, to minimize
+inter-server traffic or to optimize for network traffic conditions,
+target load, etc.) is simply not possible.
+
+Flow-Table Based Schemes
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+When using a Flow-Table based scheme to handle flow distribution/load
+balancing, in contrast with computation-based schemes, the system designer
+has the flexibility of assigning a given flow to any given
+target. The flow table (e.g. DPDK RTE Hash Library) will simply store
+both the flow key and the target value.
+
+.. _figure_efd3:
+
+.. figure:: img/efd_i3.*
+
+ Table Based Flow Distribution
+
+As shown in :numref:`figure_efd3`, when doing a lookup, the flow-table
+is indexed with the hash of the flow key and the keys (more than one is possible,
+because of hash collision) stored in this index and corresponding values
+are retrieved. The retrieved key(s) is matched with the input flow key
+and if there is a match the value (target id) is returned.
+
+The drawback of using a hash table for flow distribution/load balancing
+is the storage requirement, since the flow table need to store keys,
+signatures and target values. This doesn't allow this scheme to scale to
+millions of flow keys. Large tables will usually not fit in
+the CPU cache, and hence, the lookup performance is degraded because of
+the latency to access the main memory.
+
+EFD Based Scheme
+~~~~~~~~~~~~~~~~
+
+EFD combines the advantages of both flow-table based and computation-based
+schemes. It doesn't require the large storage necessary for
+flow-table based schemes (because EFD doesn't store the key as explained
+below), and it supports any arbitrary value for any given key.
+
+.. _figure_efd4:
+
+.. figure:: img/efd_i4.*
+
+ Searching for Perfect Hash Function
+
+The basic idea of EFD is when a given key is to be inserted, a family of
+hash functions is searched until the correct hash function that maps the
+input key to the correct value is found, as shown in :numref:`figure_efd4`.
+However, rather than explicitly storing all keys and their associated values,
+EFD stores only indices of hash functions that map keys to values, and
+thereby consumes much less space than conventional flow-based tables.
+The lookup operation is very simple, similar to a computational-based
+scheme: given an input key the lookup operation is reduced to hashing
+that key with the correct hash function.
+
+.. _figure_efd5:
+
+.. figure:: img/efd_i5.*
+
+ Divide and Conquer for Millions of Keys
+
+Intuitively, finding a hash function that maps each of a large number
+(millions) of input keys to the correct output value is effectively
+impossible, as a result EFD, as shown in :numref:`figure_efd5`,
+breaks the problem into smaller pieces (divide and conquer).
+EFD divides the entire input key set into many small groups.
+Each group consists of approximately 20-28 keys (a configurable parameter
+for the library), then, for each small group, a brute force search to find
+a hash function that produces the correct outputs for each key in the group.
+
+It should be mentioned that, since the online lookup table for EFD
+doesn't store the key itself, the size of the EFD table is independent
+of the key size and hence EFD lookup performance which is almost
+constant irrespective of the length of the key which is a highly
+desirable feature especially for longer keys.
+
+In summary, EFD is a set separation data structure that supports millions of
+keys. It is used to distribute a given key to an intended target. By itself
+EFD is not a FIB data structure with an exact match the input flow key.
+
+.. _Efd_example:
+
+Example of EFD Library Usage
+----------------------------
+
+EFD can be used along the data path of many network functions and middleboxes.
+As previously mentioned, it can used as an index table for
+<key,value> pairs, meta-data for objects, a flow-level load balancer, etc.
+:numref:`figure_efd6` shows an example of using EFD as a flow-level load
+balancer, where flows are received at a front end server before being forwarded
+to the target back end server for processing. The system designer would
+deterministically co-locate flows together in order to minimize cross-server
+interaction.
+(For example, flows requesting certain webpage objects are co-located
+together, to minimize forwarding of common objects across servers).
+
+.. _figure_efd6:
+
+.. figure:: img/efd_i6.*
+
+ EFD as a Flow-Level Load Balancer
+
+As shown in :numref:`figure_efd6`, the front end server will have an EFD table that
+stores for each group what is the perfect hash index that satisfies the
+correct output. Because the table size is small and fits in cache (since
+keys are not stored), it sustains a large number of flows (N*X, where N
+is the maximum number of flows served by each back end server of the X
+possible targets).
+
+With an input flow key, the group id is computed (for example, using
+last few bits of CRC hash) and then the EFD table is indexed with the
+group id to retrieve the corresponding hash index to use. Once the index
+is retrieved the key is hashed using this hash function and the result
+will be the intended correct target where this flow is supposed to be
+processed.
+
+It should be noted that as a result of EFD not matching the exact key but
+rather distributing the flows to a target back end node based on the
+perfect hash index, a key that has not been inserted before
+will be distributed to a valid target. Hence, a local table which stores
+the flows served at each node is used and is
+exact matched with the input key to rule out new never seen before
+flows.
+
+.. _Efd_api:
+
+Library API Overview
+--------------------
+
+The EFD library API is created with a very similar semantics of a
+hash-index or a flow table. The application creates an EFD table for a
+given maximum number of flows, a function is called to insert a flow key
+with a specific target value, and another function is used to retrieve
+target values for a given individual flow key or a bulk of keys.
+
+EFD Table Create
+~~~~~~~~~~~~~~~~
+
+The function ``rte_efd_create()`` is used to create and return a pointer
+to an EFD table that is sized to hold up to num_flows key.
+The online version of the EFD table (the one that does
+not store the keys and is used for lookups) will be allocated and
+created in the last level cache (LLC) of the socket defined by the
+online_socket_bitmask, while the offline EFD table (the one that
+stores the keys and is used for key inserts and for computing the
+perfect hashing) is allocated and created in the LLC of the socket
+defined by offline_socket_bitmask. It should be noted, that for
+highest performance the socket id should match that where the thread is
+running, i.e. the online EFD lookup table should be created on the same
+socket as where the lookup thread is running.
+
+EFD Insert and Update
+~~~~~~~~~~~~~~~~~~~~~
+
+The EFD function to insert a key or update a key to a new value is
+``rte_efd_update()``. This function will update an existing key to
+a new value (target) if the key has already been inserted
+before, or will insert the <key,value> pair if this key has not been inserted
+before. It will return 0 upon success. It will return
+``EFD_UPDATE_WARN_GROUP_FULL (1)`` if the operation is insert, and the
+last available space in the key's group was just used. It will return
+``EFD_UPDATE_FAILED (2)`` when the insertion or update has failed (either it
+failed to find a suitable perfect hash or the group was full). The function
+will return ``EFD_UPDATE_NO_CHANGE (3)`` if there is no change to the EFD
+table (i.e, same value already exists).
+
+EFD Lookup
+~~~~~~~~~~
+
+To lookup a certain key in an EFD table, the function ``rte_efd_lookup()``
+is used to return the value associated with single key.
+As previously mentioned, if the key has been inserted, the correct value
+inserted is returned, if the key has not been inserted before,
+a ‘random’ value (based on hashing of the key) is returned.
+For better performance and to decrease the overhead of
+function calls per key, it is always recommended to use a bulk lookup
+function (simultaneous lookup of multiple keys) instead of a single key
+lookup function. ``rte_efd_lookup_bulk()`` is the bulk lookup function,
+that looks up num_keys simultaneously stored in the key_list and the
+corresponding return values will be returned in the value_list.
+
+EFD Delete
+~~~~~~~~~~
+
+To delete a certain key in an EFD table, the function
+``rte_efd_delete()`` can be used. The function returns zero upon success
+when the key has been found and deleted. Socket_id is the parameter to
+use to lookup the existing value, which is ideally the caller's socket id.
+The previous value associated with this key will be returned
+in the prev_value argument.
+
+.. _Efd_internals:
+
+Library Internals
+-----------------
+
+This section provides the brief high-level idea and an overview
+of the library internals to accompany the RFC. The intent of this
+section is to explain to readers the high-level implementation of
+insert, lookup and group rebalancing in the EFD library.
+
+Insert Function Internals
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As previously mentioned the EFD divides the whole set of keys into
+groups of a manageable size (e.g. 28 keys) and then searches for the
+perfect hash that satisfies the intended target value for each key. EFD
+stores two version of the <key,value> table:
+
+- Offline Version (in memory): Only used for the insertion/update
+ operation, which is less frequent than the lookup operation. In the
+ offline version the exact keys for each group is stored. When a new
+ key is added, the hash function is updated that will satisfy the
+ value for the new key together with the all old keys already inserted
+ in this group.
+
+- Online Version (in cache): Used for the frequent lookup operation. In
+ the online version, as previously mentioned, the keys are not stored
+ but rather only the hash index for each group.
+
+.. _figure_efd7:
+
+.. figure:: img/efd_i7.*
+
+ Group Assignment
+
+:numref:`figure_efd7` depicts the group assignment for 7 flow keys as an example.
+Given a flow key, a hash function (in our implementation CRC hash) is
+used to get the group id. As shown in the figure, the groups can be
+unbalanced. (We highlight group rebalancing further below).
+
+.. _figure_efd8:
+
+.. figure:: img/efd_i8.*
+
+ Perfect Hash Search - Assigned Keys & Target Value
+
+Focusing on one group that has four keys, :numref:`figure_efd8` depicts the search
+algorithm to find the perfect hash function. Assuming that the target
+value bit for the keys is as shown in the figure, then the online EFD
+table will store a 16 bit hash index and 16 bit lookup table per group
+per value bit.
+
+.. _figure_efd9:
+
+.. figure:: img/efd_i9.*
+
+ Perfect Hash Search - Satisfy Target Values
+
+For a given keyX, a hash function ``(h(keyX, seed1) + index * h(keyX, seed2))``
+is used to point to certain bit index in the 16bit lookup_table value,
+as shown in :numref:`figure_efd9`.
+The insert function will brute force search for all possible values for the
+hash index until a non conflicting lookup_table is found.
+
+.. _figure_efd10:
+
+.. figure:: img/efd_i10.*
+
+ Finding Hash Index for Conflict Free lookup_table
+
+For example, since both key3 and key7 have a target bit value of 1, it
+is okay if the hash function of both keys point to the same bit in the
+lookup table. A conflict will occur if a hash index is used that maps
+both Key4 and Key7 to the same index in the lookup_table,
+as shown in :numref:`figure_efd10`, since their target value bit are not the same.
+Once a hash index is found that produces a lookup_table with no
+contradictions, this index is stored for this group. This procedure is
+repeated for each bit of target value.
+
+Lookup Function Internals
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The design principle of EFD is that lookups are much more frequent than
+inserts, and hence, EFD's design optimizes for the lookups which are
+faster and much simpler than the slower insert procedure (inserts are
+slow, because of perfect hash search as previously discussed).
+
+.. _figure_efd11:
+
+.. figure:: img/efd_i11.*
+
+ EFD Lookup Operation
+
+:numref:`figure_efd11` depicts the lookup operation for EFD. Given an input key,
+the group id is computed (using CRC hash) and then the hash index for this
+group is retrieved from the EFD table. Using the retrieved hash index,
+the hash function ``h(key, seed1) + index *h(key, seed2)`` is used which will
+result in an index in the lookup_table, the bit corresponding to this
+index will be the target value bit. This procedure is repeated for each
+bit of the target value.
+
+Group Rebalancing Function Internals
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When discussing EFD inserts and lookups, the discussion is simplified by
+assuming that a group id is simply a result of hash function. However,
+since hashing in general is not perfect and will not always produce a
+uniform output, this simplified assumption will lead to unbalanced
+groups, i.e., some group will have more keys than other groups.
+Typically, and to minimize insert time with an increasing number of keys,
+it is preferable that all groups will have a balanced number of keys, so
+the brute force search for the perfect hash terminates with a valid hash
+index. In order to achieve this target, groups are rebalanced during
+runtime inserts, and keys are moved around from a busy group to a less
+crowded group as the more keys are inserted.
+
+.. _figure_efd12:
+
+.. figure:: img/efd_i12.*
+
+ Runtime Group Rebalancing
+
+:numref:`figure_efd12` depicts the high level idea of group rebalancing, given an
+input key the hash result is split into two parts a chunk id and 8-bit
+bin id. A chunk contains 64 different groups and 256 bins (i.e. for any
+given bin it can map to 4 distinct groups). When a key is inserted, the
+bin id is computed, for example in :numref:`figure_efd12` bin_id=2,
+and since each bin can be mapped to one of four different groups (2 bit storage),
+the four possible mappings are evaluated and the one that will result in a
+balanced key distribution across these four is selected the mapping result
+is stored in these two bits.
+
+
+.. _Efd_references:
+
+References
+-----------
+
+1- EFD is based on collaborative research work between Intel and
+Carnegie Mellon University (CMU), interested readers can refer to the paper
+“Scaling Up Clustered Network Appliances with ScaleBricks;” Dong Zhou et al.
+at SIGCOMM 2015 (`http://conferences.sigcomm.org/sigcomm/2015/pdf/papers/p241.pdf`)
+for more information.
diff --git a/doc/guides/prog_guide/img/efd_i1.svg b/doc/guides/prog_guide/img/efd_i1.svg
new file mode 100644
index 0000000..7f8fcb3
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i1.svg
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i1.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="3.25609in" height="3.375in"
+ viewBox="0 0 234.439 243" xml:space="preserve" color-interpolation-filters="sRGB" class="st10">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st5 {marker-end:url(#mrkr5-12);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:6}
+ .st6 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.70422535211268}
+ .st7 {stroke:#5b9bd5;stroke-dasharray:2.25,4.5;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.25}
+ .st8 {marker-end:url(#mrkr5-39);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st9 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st10 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-12" class="st6" v:arrowType="5" v:arrowSize="2" v:setback="2.485" refX="-2.485" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-1.42,-1.42) "/>
+ </marker>
+ <marker id="mrkr5-39" class="st9" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(77.718,-113.348)">
+ <title>Square</title>
+ <desc>LB</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="18" cy="225" width="36" height="36"/>
+ <g id="shadow2-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="207" width="36" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="207" width="36" height="36" class="st3"/>
+ <text x="13.18" y="228" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>LB</text> </g>
+ <g id="shape3-7" v:mID="3" v:groupContext="shape" transform="translate(37.0513,-131.348)">
+ <title>Sheet.3</title>
+ <path d="M0 243 L25.76 243" class="st5"/>
+ </g>
+ <g id="shape4-13" v:mID="4" v:groupContext="shape" transform="translate(167.718,-178.598)">
+ <title>Square.4</title>
+ <desc>Target 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="220.5" width="45" height="45"/>
+ <g id="shadow4-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="198" width="45" height="45" class="st2"/>
+ </g>
+ <rect x="0" y="198" width="45" height="45" class="st3"/>
+ <text x="5.74" y="223.5" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target 1</text> </g>
+ <g id="shape5-19" v:mID="5" v:groupContext="shape" transform="translate(167.718,-121.005)">
+ <title>Square.5</title>
+ <desc>Target 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="220.5" width="45" height="45"/>
+ <g id="shadow5-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="198" width="45" height="45" class="st2"/>
+ </g>
+ <rect x="0" y="198" width="45" height="45" class="st3"/>
+ <text x="5.74" y="223.5" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target 2</text> </g>
+ <g id="shape7-25" v:mID="7" v:groupContext="shape" transform="translate(167.718,-23.3478)">
+ <title>Square.7</title>
+ <desc>Target N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="220.5" width="45" height="45"/>
+ <g id="shadow7-26" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="198" width="45" height="45" class="st2"/>
+ </g>
+ <rect x="0" y="198" width="45" height="45" class="st3"/>
+ <text x="5.05" y="223.5" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target N</text> </g>
+ <g id="shape8-31" v:mID="8" v:groupContext="shape" transform="translate(433.218,132.402) rotate(90)">
+ <title>Sheet.8</title>
+ <path d="M0 243 L34.59 243" class="st7"/>
+ </g>
+ <g id="shape9-34" v:mID="9" v:groupContext="shape" transform="translate(-78.4279,-37.1059) rotate(-52.2532)">
+ <title>Sheet.9</title>
+ <path d="M0 243 L81.18 243" class="st8"/>
+ </g>
+ <g id="shape11-40" v:mID="11" v:groupContext="shape" transform="translate(60.3469,-125.414) rotate(-12.6875)">
+ <title>Sheet.11</title>
+ <path d="M0 243 L48.32 243" class="st8"/>
+ </g>
+ <g id="shape12-45" v:mID="12" v:groupContext="shape" transform="translate(319.172,-18.1081) rotate(57.7244)">
+ <title>Sheet.12</title>
+ <path d="M0 243 L94.09 243" class="st8"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i10.svg b/doc/guides/prog_guide/img/efd_i10.svg
new file mode 100644
index 0000000..d26ec61
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i10.svg
@@ -0,0 +1,384 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i11.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="9.76715in" height="2.82917in"
+ viewBox="0 0 703.234 203.701" xml:space="preserve" color-interpolation-filters="sRGB" class="st15">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st4 {fill:#000000;font-family:Arial;font-size:0.998566em}
+ .st5 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st6 {fill:#000000;font-family:Arial;font-size:0.918686em;font-style:italic}
+ .st7 {fill:#000000;font-family:Arial;font-size:0.918686em}
+ .st8 {fill:#7e8d96;font-family:Arial;font-size:0.998566em;font-weight:bold}
+ .st9 {fill:#00b050;font-family:Arial;font-size:0.998566em;font-weight:bold}
+ .st10 {fill:#ff0000;font-family:Arial;font-size:0.998566em;font-weight:bold}
+ .st11 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st12 {fill:#ffffff;font-family:Arial;font-size:1.49785em}
+ .st13 {stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st14 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st15 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(19.0195,-93.4328)">
+ <title>Sheet.3</title>
+ <path d="M0 182.93 C0 180.64 1.87 178.78 4.16 178.78 L109.18 178.78 C111.47 178.78 113.33 180.64 113.33 182.93 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.16 203.7 C1.87 203.7 0 201.84 0 199.55 L0 182.93 Z"
+ class="st1"/>
+ </g>
+ <g id="shape4-3" v:mID="4" v:groupContext="shape" transform="translate(19.0195,-93.4328)">
+ <title>Sheet.4</title>
+ <path d="M0 182.93 C0 180.64 1.87 178.78 4.16 178.78 L109.18 178.78 C111.47 178.78 113.33 180.64 113.33 182.93 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.16 203.7 C1.87 203.7 0 201.84 0 199.55 L0 182.93 Z"
+ class="st2"/>
+ </g>
+ <g id="shape5-5" v:mID="5" v:groupContext="shape" transform="translate(19.0195,-96.9057)">
+ <title>Sheet.5</title>
+ <desc>Key1: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.0575" cy="196.509" width="116.12" height="14.3829"/>
+ <path d="M116.11 189.32 L0 189.32 L0 203.7 L116.11 203.7 L116.11 189.32" class="st3"/>
+ <text x="15.59" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1: Value = 0</text> </g>
+ <g id="shape6-9" v:mID="6" v:groupContext="shape" transform="translate(19.0195,-68.6284)">
+ <title>Sheet.6</title>
+ <path d="M0 182.93 C0 180.64 1.87 178.78 4.16 178.78 L109.18 178.78 C111.47 178.78 113.33 180.64 113.33 182.93 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.16 203.7 C1.87 203.7 0 201.84 0 199.55 L0 182.93 Z"
+ class="st1"/>
+ </g>
+ <g id="shape7-11" v:mID="7" v:groupContext="shape" transform="translate(19.0195,-68.6284)">
+ <title>Sheet.7</title>
+ <path d="M0 182.93 C0 180.64 1.87 178.78 4.16 178.78 L109.18 178.78 C111.47 178.78 113.33 180.64 113.33 182.93 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.16 203.7 C1.87 203.7 0 201.84 0 199.55 L0 182.93 Z"
+ class="st2"/>
+ </g>
+ <g id="shape8-13" v:mID="8" v:groupContext="shape" transform="translate(19.0195,-72.0832)">
+ <title>Sheet.8</title>
+ <desc>Key3: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.0575" cy="196.509" width="116.12" height="14.3829"/>
+ <path d="M116.11 189.32 L0 189.32 L0 203.7 L116.11 203.7 L116.11 189.32" class="st3"/>
+ <text x="15.59" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3: Value = 1</text> </g>
+ <g id="shape9-17" v:mID="9" v:groupContext="shape" transform="translate(19.0195,-43.5843)">
+ <title>Sheet.9</title>
+ <path d="M0 182.84 C-0 180.53 1.88 178.66 4.19 178.66 L109.15 178.66 C111.46 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.53 C113.33 201.84 111.46 203.7 109.15 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.53 L0 182.84 Z"
+ class="st1"/>
+ </g>
+ <g id="shape10-19" v:mID="10" v:groupContext="shape" transform="translate(19.0195,-43.5843)">
+ <title>Sheet.10</title>
+ <path d="M0 182.84 C-0 180.53 1.88 178.66 4.19 178.66 L109.15 178.66 C111.46 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.53 C113.33 201.84 111.46 203.7 109.15 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.53 L0 182.84 Z"
+ class="st2"/>
+ </g>
+ <g id="shape11-21" v:mID="11" v:groupContext="shape" transform="translate(19.0195,-47.1109)">
+ <title>Sheet.11</title>
+ <desc>Key4: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.0575" cy="196.509" width="116.12" height="14.3829"/>
+ <path d="M116.11 189.32 L0 189.32 L0 203.7 L116.11 203.7 L116.11 189.32" class="st3"/>
+ <text x="15.59" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4: Value = 0</text> </g>
+ <g id="shape12-25" v:mID="12" v:groupContext="shape" transform="translate(19.0195,-19.0195)">
+ <title>Sheet.12</title>
+ <path d="M0 182.84 C-0 180.53 1.88 178.66 4.19 178.66 L109.15 178.66 C111.46 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.53 C113.33 201.84 111.46 203.7 109.15 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.53 L0 182.84 Z"
+ class="st1"/>
+ </g>
+ <g id="shape13-27" v:mID="13" v:groupContext="shape" transform="translate(19.0195,-19.0195)">
+ <title>Sheet.13</title>
+ <path d="M0 182.84 C-0 180.53 1.88 178.66 4.19 178.66 L109.15 178.66 C111.46 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.53 C113.33 201.84 111.46 203.7 109.15 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.53 L0 182.84 Z"
+ class="st2"/>
+ </g>
+ <g id="shape14-29" v:mID="14" v:groupContext="shape" transform="translate(19.0195,-22.5475)">
+ <title>Sheet.14</title>
+ <desc>Key7: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.0575" cy="196.509" width="116.12" height="14.3829"/>
+ <path d="M116.11 189.32 L0 189.32 L0 203.7 L116.11 203.7 L116.11 189.32" class="st3"/>
+ <text x="15.59" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7: Value = 1</text> </g>
+ <g id="shape15-33" v:mID="15" v:groupContext="shape" transform="translate(141.656,-45.5615)">
+ <title>Sheet.15</title>
+ <path d="M0 169.01 L22.75 169.01 L22.75 157.45 L45.5 180.57 L22.75 203.7 L22.75 192.14 L0 192.14 L0 169.01 Z"
+ class="st5"/>
+ </g>
+ <g id="shape16-35" v:mID="16" v:groupContext="shape" transform="translate(193.22,-56.0464)">
+ <title>Sheet.16</title>
+ <path d="M0 182.84 C0 180.53 1.88 178.66 4.19 178.66 L96.55 178.66 C98.87 178.66 100.73 180.53 100.73 182.84 L100.73
+ 199.54 C100.73 201.84 98.87 203.7 96.55 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.54 L0 182.84 Z"
+ class="st1"/>
+ </g>
+ <g id="shape17-37" v:mID="17" v:groupContext="shape" transform="translate(193.22,-56.0464)">
+ <title>Sheet.17</title>
+ <path d="M0 182.84 C0 180.53 1.88 178.66 4.19 178.66 L96.55 178.66 C98.87 178.66 100.73 180.53 100.73 182.84 L100.73
+ 199.54 C100.73 201.84 98.87 203.7 96.55 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.54 L0 182.84 Z"
+ class="st2"/>
+ </g>
+ <g id="shape18-39" v:mID="18" v:groupContext="shape" transform="translate(228.157,-66.9545)">
+ <title>Sheet.18</title>
+ <desc>F</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.63538" cy="197.084" width="11.28" height="13.2327"/>
+ <path d="M11.27 190.47 L0 190.47 L0 203.7 L11.27 203.7 L11.27 190.47" class="st3"/>
+ <text x="2.27" y="200.39" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>F</text> </g>
+ <g id="shape19-43" v:mID="19" v:groupContext="shape" transform="translate(234.88,-66.9545)">
+ <title>Sheet.19</title>
+ <desc>(key,</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.261" cy="197.084" width="34.53" height="13.2327"/>
+ <path d="M34.52 190.47 L0 190.47 L0 203.7 L34.52 203.7 L34.52 190.47" class="st3"/>
+ <text x="5.32" y="200.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(key, </text> </g>
+ <g id="shape20-47" v:mID="20" v:groupContext="shape" transform="translate(198.215,-53.7734)">
+ <title>Sheet.20</title>
+ <desc>hash_index =</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="41.4128" cy="197.084" width="82.83" height="13.2327"/>
+ <path d="M82.83 190.47 L0 190.47 L0 203.7 L82.83 203.7 L82.83 190.47" class="st3"/>
+ <text x="8.47" y="200.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash_index = </text> </g>
+ <g id="shape21-51" v:mID="21" v:groupContext="shape" transform="translate(274.858,-53.7734)">
+ <title>Sheet.21</title>
+ <desc>i)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.28241" cy="197.084" width="10.57" height="13.2327"/>
+ <path d="M10.56 190.47 L0 190.47 L0 203.7 L10.56 203.7 L10.56 190.47" class="st3"/>
+ <text x="2.22" y="200.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>i)</text> </g>
+ <g id="shape22-55" v:mID="22" v:groupContext="shape" transform="translate(351.453,-93.7923)">
+ <title>Sheet.22</title>
+ <path d="M0 182.84 C0 180.53 1.88 178.66 4.19 178.66 L109.16 178.66 C111.47 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.54 C113.33 201.84 111.47 203.7 109.16 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.54 L0 182.84 Z"
+ class="st1"/>
+ </g>
+ <g id="shape23-57" v:mID="23" v:groupContext="shape" transform="translate(351.453,-93.7923)">
+ <title>Sheet.23</title>
+ <path d="M0 182.84 C0 180.53 1.88 178.66 4.19 178.66 L109.16 178.66 C111.47 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.54 C113.33 201.84 111.47 203.7 109.16 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.54 L0 182.84 Z"
+ class="st2"/>
+ </g>
+ <g id="shape24-59" v:mID="24" v:groupContext="shape" transform="translate(355.798,-97.3147)">
+ <title>Sheet.24</title>
+ <desc>Key1: Position 4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="51.7083" cy="196.509" width="103.42" height="14.3829"/>
+ <path d="M103.42 189.32 L0 189.32 L0 203.7 L103.42 203.7 L103.42 189.32" class="st3"/>
+ <text x="8.41" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1: Position 4</text> </g>
+ <g id="shape25-63" v:mID="25" v:groupContext="shape" transform="translate(351.453,-68.9879)">
+ <title>Sheet.25</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape26-65" v:mID="26" v:groupContext="shape" transform="translate(351.453,-68.9879)">
+ <title>Sheet.26</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape27-67" v:mID="27" v:groupContext="shape" transform="translate(355.798,-72.4921)">
+ <title>Sheet.27</title>
+ <desc>Key3: Position 6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="51.7083" cy="196.509" width="103.42" height="14.3829"/>
+ <path d="M103.42 189.32 L0 189.32 L0 203.7 L103.42 203.7 L103.42 189.32" class="st3"/>
+ <text x="8.41" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3: Position 6</text> </g>
+ <g id="shape28-71" v:mID="28" v:groupContext="shape" transform="translate(351.453,-44.0636)">
+ <title>Sheet.28</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape29-73" v:mID="29" v:groupContext="shape" transform="translate(351.453,-44.0636)">
+ <title>Sheet.29</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape30-75" v:mID="30" v:groupContext="shape" transform="translate(351.215,-47.5198)">
+ <title>Sheet.30</title>
+ <desc>Key4: Position 14</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="55.5403" cy="196.509" width="111.09" height="14.3829"/>
+ <path d="M111.08 189.32 L0 189.32 L0 203.7 L111.08 203.7 L111.08 189.32" class="st3"/>
+ <text x="8.91" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4: Position 14</text> </g>
+ <g id="shape31-79" v:mID="31" v:groupContext="shape" transform="translate(351.453,-19.4988)">
+ <title>Sheet.31</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape32-81" v:mID="32" v:groupContext="shape" transform="translate(351.453,-19.4988)">
+ <title>Sheet.32</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape33-83" v:mID="33" v:groupContext="shape" transform="translate(351.215,-22.9565)">
+ <title>Sheet.33</title>
+ <desc>Key7: Position 14</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="55.5403" cy="196.509" width="111.09" height="14.3829"/>
+ <path d="M111.08 189.32 L0 189.32 L0 203.7 L111.08 203.7 L111.08 189.32" class="st3"/>
+ <text x="8.91" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7: Position 14</text> </g>
+ <g id="shape34-87" v:mID="34" v:groupContext="shape" transform="translate(299.89,-46.0408)">
+ <title>Sheet.34</title>
+ <path d="M0 169.01 L22.75 169.01 L22.75 157.45 L45.5 180.57 L22.75 203.7 L22.75 192.14 L0 192.14 L0 169.01 Z"
+ class="st5"/>
+ </g>
+ <g id="shape35-89" v:mID="35" v:groupContext="shape" transform="translate(528.896,-117.518)">
+ <title>Sheet.35</title>
+ <path d="M0 182.94 C0 180.66 1.89 178.78 4.17 178.78 L137.64 178.78 C139.92 178.78 141.79 180.66 141.79 182.94 L141.79
+ 199.57 C141.79 201.84 139.92 203.7 137.64 203.7 L4.17 203.7 C1.89 203.7 0 201.84 0 199.57 L0 182.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape36-91" v:mID="36" v:groupContext="shape" transform="translate(528.896,-117.518)">
+ <title>Sheet.36</title>
+ <path d="M0 182.94 C0 180.66 1.89 178.78 4.17 178.78 L137.64 178.78 C139.92 178.78 141.79 180.66 141.79 182.94 L141.79
+ 199.57 C141.79 201.84 139.92 203.7 137.64 203.7 L4.17 203.7 C1.89 203.7 0 201.84 0 199.57 L0 182.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape37-93" v:mID="37" v:groupContext="shape" transform="translate(530.056,-121.017)">
+ <title>Sheet.37</title>
+ <desc>0000</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="19.1585" cy="196.509" width="38.32" height="14.3829"/>
+ <path d="M38.32 189.32 L0 189.32 L0 203.7 L38.32 203.7 L38.32 189.32" class="st3"/>
+ <text x="5.83" y="200.1" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0000 </text> </g>
+ <g id="shape38-97" v:mID="38" v:groupContext="shape" transform="translate(567.215,-121.017)">
+ <title>Sheet.38</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.7483" cy="196.509" width="11.5" height="14.3829"/>
+ <path d="M11.5 189.32 L0 189.32 L0 203.7 L11.5 203.7 L11.5 189.32" class="st3"/>
+ <text x="2.42" y="200.1" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape39-101" v:mID="39" v:groupContext="shape" transform="translate(576.215,-121.017)">
+ <title>Sheet.39</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.7483" cy="196.509" width="11.5" height="14.3829"/>
+ <path d="M11.5 189.32 L0 189.32 L0 203.7 L11.5 203.7 L11.5 189.32" class="st3"/>
+ <text x="2.42" y="200.1" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape40-105" v:mID="40" v:groupContext="shape" transform="translate(584.486,-121.017)">
+ <title>Sheet.40</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.7483" cy="196.509" width="11.5" height="14.3829"/>
+ <path d="M11.5 189.32 L0 189.32 L0 203.7 L11.5 203.7 L11.5 189.32" class="st3"/>
+ <text x="2.42" y="200.1" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape41-109" v:mID="41" v:groupContext="shape" transform="translate(588.646,-121.017)">
+ <title>Sheet.41</title>
+ <desc>0 0000 00</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="32.5687" cy="196.509" width="65.14" height="14.3829"/>
+ <path d="M65.14 189.32 L0 189.32 L0 203.7 L65.14 203.7 L65.14 189.32" class="st3"/>
+ <text x="5.91" y="200.1" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0 0000 00</text> </g>
+ <g id="shape42-113" v:mID="42" v:groupContext="shape" transform="translate(644.965,-121.017)">
+ <title>Sheet.42</title>
+ <desc>?</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="6.12511" cy="196.509" width="12.26" height="14.3829"/>
+ <path d="M12.25 189.32 L0 189.32 L0 203.7 L12.25 203.7 L12.25 189.32" class="st3"/>
+ <text x="2.47" y="200.1" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>?</text> </g>
+ <g id="shape43-117" v:mID="43" v:groupContext="shape" transform="translate(654.718,-121.017)">
+ <title>Sheet.43</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.7483" cy="196.509" width="11.5" height="14.3829"/>
+ <path d="M11.5 189.32 L0 189.32 L0 203.7 L11.5 203.7 L11.5 189.32" class="st3"/>
+ <text x="2.42" y="200.1" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape44-121" v:mID="44" v:groupContext="shape" transform="translate(464.786,-105.296)">
+ <title>Sheet.44</title>
+ <path d="M0 203.7 L108.29 203.7 C108.86 203.7 109.31 203.22 109.31 202.68 L109.31 189.5 L107.27 189.5 L107.27 202.68
+ L108.29 201.66 L0 201.66 L0 203.7 ZM111.35 190.52 L108.29 184.41 L105.23 190.55 L111.35 190.52 Z"
+ class="st11"/>
+ </g>
+ <g id="shape45-123" v:mID="45" v:groupContext="shape" transform="translate(464.786,-80.4315)">
+ <title>Sheet.45</title>
+ <path d="M0 203.7 L123.63 203.7 C124.2 203.7 124.65 203.25 124.65 202.68 L124.65 164.28 L122.61 164.28 L122.61 202.68
+ L123.63 201.66 L0 201.66 L0 203.7 ZM126.69 165.3 L123.6 159.18 L120.57 165.33 L126.69 165.3 Z"
+ class="st11"/>
+ </g>
+ <g id="shape46-125" v:mID="46" v:groupContext="shape" transform="translate(464.786,-55.4772)">
+ <title>Sheet.46</title>
+ <path d="M0 203.7 L186.48 203.7 C186.75 203.7 186.99 203.61 187.2 203.4 C187.38 203.22 187.5 202.95 187.5 202.68 L187.41
+ 139.32 L185.37 139.32 L185.46 202.68 L186.48 201.66 L0 201.66 L0 203.7 ZM189.51 140.07 L185.94 134.23 L183.41
+ 140.61 L189.51 140.07 Z" class="st11"/>
+ </g>
+ <g id="shape47-127" v:mID="47" v:groupContext="shape" transform="translate(464.786,-30.9125)">
+ <title>Sheet.47</title>
+ <path d="M0 203.7 L186.48 203.7 C186.75 203.7 186.99 203.61 187.2 203.4 C187.38 203.22 187.5 202.95 187.5 202.68 L187.41
+ 114.76 L185.37 114.76 L185.46 202.68 L186.48 201.66 L0 201.66 L0 203.7 ZM189.51 115.51 L185.94 109.67 L183.41
+ 116.05 L189.51 115.51 Z" class="st11"/>
+ </g>
+ <g id="shape48-129" v:mID="48" v:groupContext="shape" transform="translate(442.996,-151.106)">
+ <title>Sheet.48</title>
+ <path d="M0 179.56 C0 176.89 2.19 174.7 4.86 174.7 L70.8 174.7 C73.47 174.7 75.64 176.89 75.64 179.56 L75.64 198.88 C75.64
+ 201.54 73.47 203.7 70.8 203.7 L4.86 203.7 C2.19 203.7 0 201.54 0 198.88 L0 179.56 Z" class="st5"/>
+ </g>
+ <g id="shape49-131" v:mID="49" v:groupContext="shape" transform="translate(443.529,-155.018)">
+ <title>Sheet.49</title>
+ <desc>Values</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="37.8175" cy="192.914" width="75.64" height="21.5726"/>
+ <path d="M75.64 182.13 L0 182.13 L0 203.7 L75.64 203.7 L75.64 182.13" class="st3"/>
+ <text x="10.34" y="198.31" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Values</text> </g>
+ <g id="shape50-135" v:mID="50" v:groupContext="shape" transform="translate(102.458,-122.192)">
+ <title>Sheet.50</title>
+ <path d="M0 203.7 C-0 199.21 0.62 195.55 1.37 195.55 L11.67 195.55 C12.42 195.55 13.03 191.9 13.03 187.4 C13.03 191.9
+ 13.64 195.55 14.39 195.55 L24.69 195.55 C25.44 195.55 26.05 199.21 26.05 203.7" class="st13"/>
+ </g>
+ <g id="shape51-138" v:mID="51" v:groupContext="shape" transform="translate(115.454,-137.5)">
+ <title>Sheet.51</title>
+ <path d="M0.2 203.7 L322.66 174.12 L322.48 172.1 L0 201.68 L0.2 203.7 L0.2 203.7 ZM321.84 176.24 L327.66 172.64 L321.28
+ 170.16 L321.84 176.24 L321.84 176.24 Z" class="st14"/>
+ </g>
+ <g id="shape52-140" v:mID="52" v:groupContext="shape" transform="translate(518.211,-142.473)">
+ <title>Sheet.52</title>
+ <path d="M0.99 176.74 L44.78 200.38 L43.82 202.17 L0 178.51 L0.99 176.74 L0.99 176.74 ZM44.87 198.1 L48.8 203.7 L41.96
+ 203.46 L44.87 198.1 L44.87 198.1 Z" class="st11"/>
+ </g>
+ <g id="shape53-142" v:mID="53" v:groupContext="shape" transform="translate(518.331,-141.963)">
+ <title>Sheet.53</title>
+ <path d="M0.75 176.17 L60.09 200.32 L59.34 202.2 L0 178.06 L0.75 176.17 L0.75 176.17 ZM59.91 198.04 L64.44 203.19 L57.6
+ 203.7 L59.91 198.04 L59.91 198.04 Z" class="st11"/>
+ </g>
+ <g id="shape54-144" v:mID="54" v:groupContext="shape" transform="translate(576.558,-153.706)">
+ <title>Sheet.54</title>
+ <path d="M0 177.04 C0 174.1 2.4 171.71 5.34 171.71 L101.51 171.71 C104.48 171.71 106.85 174.1 106.85 177.04 L106.85 198.37
+ C106.85 201.33 104.48 203.7 101.51 203.7 L5.34 203.7 C2.4 203.7 0 201.33 0 198.37 L0 177.04 Z" class="st1"/>
+ </g>
+ <g id="shape55-146" v:mID="55" v:groupContext="shape" transform="translate(577.365,-151.611)">
+ <title>Sheet.55</title>
+ <path d="M0 177.04 C0 174.1 2.4 171.71 5.34 171.71 L101.51 171.71 C104.48 171.71 106.85 174.1 106.85 177.04 L106.85 198.37
+ C106.85 201.33 104.48 203.7 101.51 203.7 L5.34 203.7 C2.4 203.7 0 201.33 0 198.37 L0 177.04 Z" class="st2"/>
+ </g>
+ <g id="shape56-148" v:mID="56" v:groupContext="shape" transform="translate(593.952,-167.894)">
+ <title>Sheet.56</title>
+ <desc>Lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.2942" cy="196.509" width="86.59" height="14.3829"/>
+ <path d="M86.59 189.32 L0 189.32 L0 203.7 L86.59 203.7 L86.59 189.32" class="st3"/>
+ <text x="7.31" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Lookup_table</text> </g>
+ <g id="shape57-152" v:mID="57" v:groupContext="shape" transform="translate(608.239,-153.515)">
+ <title>Sheet.57</title>
+ <desc>(16 bits)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="26.8054" cy="196.509" width="53.62" height="14.3829"/>
+ <path d="M53.61 189.32 L0 189.32 L0 203.7 L53.61 203.7 L53.61 189.32" class="st3"/>
+ <text x="5.16" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(16 bits)</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i11.svg b/doc/guides/prog_guide/img/efd_i11.svg
new file mode 100644
index 0000000..f2cc656
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i11.svg
@@ -0,0 +1,319 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i12.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="10.2783in" height="4.28958in"
+ viewBox="0 0 740.039 308.85" xml:space="preserve" color-interpolation-filters="sRGB" class="st21">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st4 {fill:#000000;font-family:Arial;font-size:0.998566em}
+ .st5 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st6 {stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st7 {stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st8 {fill:#000000;font-family:Arial;font-size:0.918686em;font-weight:bold}
+ .st9 {fill:#00b050;font-size:1em}
+ .st10 {fill:#c00000;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st11 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.00749637}
+ .st12 {fill:#ffffff;font-family:Arial;font-size:1.16833em}
+ .st13 {fill:#2e75b5;stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st14 {fill:#ffffff;font-family:Arial;font-size:1.16666em}
+ .st15 {font-size:1em}
+ .st16 {fill:none;stroke:none;stroke-width:0.25}
+ .st17 {fill:#000000;font-family:Calibri;font-size:1.00001em}
+ .st18 {marker-end:url(#mrkr5-121);stroke:#5b9bd5;stroke-dasharray:1.5,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st19 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st20 {fill:#000000;font-family:Calibri;font-size:1.16666em}
+ .st21 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-121" class="st19" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape5-1" v:mID="5" v:groupContext="shape" transform="translate(36.0674,-256.878)">
+ <title>Sheet.5</title>
+ <path d="M0 291.88 C0 290 1.52 288.48 3.41 288.48 L68.51 288.48 C70.4 288.48 71.91 290 71.91 291.88 L71.91 305.46 C71.91
+ 307.33 70.4 308.85 68.51 308.85 L3.41 308.85 C1.52 308.85 0 307.33 0 305.46 L0 291.88 Z" class="st1"/>
+ </g>
+ <g id="shape6-3" v:mID="6" v:groupContext="shape" transform="translate(36.0674,-256.878)">
+ <title>Sheet.6</title>
+ <path d="M0 291.88 C0 290 1.52 288.48 3.41 288.48 L68.51 288.48 C70.4 288.48 71.91 290 71.91 291.88 L71.91 305.46 C71.91
+ 307.33 70.4 308.85 68.51 308.85 L3.41 308.85 C1.52 308.85 0 307.33 0 305.46 L0 291.88 Z" class="st2"/>
+ </g>
+ <g id="shape7-5" v:mID="7" v:groupContext="shape" transform="translate(61.6502,-258.089)">
+ <title>Sheet.7</title>
+ <desc>Key</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="13.7891" cy="301.658" width="27.58" height="14.3829"/>
+ <path d="M27.58 294.47 L0 294.47 L0 308.85 L27.58 308.85 L27.58 294.47" class="st3"/>
+ <text x="3.46" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key</text> </g>
+ <g id="shape8-9" v:mID="8" v:groupContext="shape" transform="translate(51.9748,-236.328)">
+ <title>Sheet.8</title>
+ <path d="M0 298.54 L9.81 298.54 L9.81 288.24 L29.44 288.24 L29.44 298.54 L39.26 298.54 L19.63 308.85 L0 298.54 Z"
+ class="st5"/>
+ </g>
+ <g id="shape9-11" v:mID="9" v:groupContext="shape" transform="translate(36.0674,-215.298)">
+ <title>Sheet.9</title>
+ <path d="M0 291.77 C0 289.89 1.54 288.36 3.42 288.36 L68.49 288.36 C70.38 288.36 71.91 289.89 71.91 291.77 L71.91 305.43
+ C71.91 307.32 70.38 308.85 68.49 308.85 L3.42 308.85 C1.54 308.85 0 307.32 0 305.43 L0 291.77 Z"
+ class="st1"/>
+ </g>
+ <g id="shape10-13" v:mID="10" v:groupContext="shape" transform="translate(36.0674,-215.298)">
+ <title>Sheet.10</title>
+ <path d="M0 291.77 C0 289.89 1.54 288.36 3.42 288.36 L68.49 288.36 C70.38 288.36 71.91 289.89 71.91 291.77 L71.91 305.43
+ C71.91 307.32 70.38 308.85 68.49 308.85 L3.42 308.85 C1.54 308.85 0 307.32 0 305.43 L0 291.77 Z"
+ class="st2"/>
+ </g>
+ <g id="shape11-15" v:mID="11" v:groupContext="shape" transform="translate(58.8889,-216.57)">
+ <title>Sheet.11</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.8573" cy="301.658" width="33.72" height="14.3829"/>
+ <path d="M33.71 294.47 L0 294.47 L0 308.85 L33.71 308.85 L33.71 294.47" class="st3"/>
+ <text x="3.86" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape12-19" v:mID="12" v:groupContext="shape" transform="translate(27.3033,-174.437)">
+ <title>Sheet.12</title>
+ <path d="M0 292.58 C0 290.78 1.46 289.32 3.26 289.32 L87.15 289.32 C88.95 289.32 90.4 290.78 90.4 292.58 L90.4 305.6
+ C90.4 307.4 88.95 308.85 87.15 308.85 L3.26 308.85 C1.46 308.85 0 307.4 0 305.6 L0 292.58 Z" class="st1"/>
+ </g>
+ <g id="shape13-21" v:mID="13" v:groupContext="shape" transform="translate(27.3033,-174.437)">
+ <title>Sheet.13</title>
+ <path d="M0 292.58 C0 290.78 1.46 289.32 3.26 289.32 L87.15 289.32 C88.95 289.32 90.4 290.78 90.4 292.58 L90.4 305.6
+ C90.4 307.4 88.95 308.85 87.15 308.85 L3.26 308.85 C1.46 308.85 0 307.4 0 305.6 L0 292.58 Z" class="st2"/>
+ </g>
+ <g id="shape14-23" v:mID="14" v:groupContext="shape" transform="translate(36.0515,-175.256)">
+ <title>Sheet.14</title>
+ <desc>0x0102ABCD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.6644" cy="301.658" width="87.33" height="14.3829"/>
+ <path d="M87.33 294.47 L0 294.47 L0 308.85 L87.33 308.85 L87.33 294.47" class="st3"/>
+ <text x="7.36" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102ABCD</text> </g>
+ <g id="shape15-27" v:mID="15" v:groupContext="shape" transform="translate(51.9748,-194.029)">
+ <title>Sheet.15</title>
+ <path d="M0 298.48 L9.81 298.48 L9.81 288.12 L29.44 288.12 L29.44 298.48 L39.26 298.48 L19.63 308.85 L0 298.48 Z"
+ class="st5"/>
+ </g>
+ <g id="shape16-29" v:mID="16" v:groupContext="shape" transform="translate(48.9133,-159.818)">
+ <title>Sheet.16</title>
+ <path d="M26.41 296.87 C26.41 300.18 25.97 302.86 25.41 302.86 L14.21 302.86 C13.66 302.86 13.21 305.55 13.21 308.85
+ C13.21 305.55 12.76 302.86 12.21 302.86 L1.01 302.86 C0.45 302.86 0 300.18 0 296.87" class="st6"/>
+ </g>
+ <g id="shape17-32" v:mID="17" v:groupContext="shape" transform="translate(19.0195,-19.0195)">
+ <title>Sheet.17</title>
+ <path d="M0 196.93 L0 308.85 L145.15 308.85 L145.15 196.93 L0 196.93 L0 196.93 Z" class="st1"/>
+ </g>
+ <g id="shape18-34" v:mID="18" v:groupContext="shape" transform="translate(19.0195,-19.0195)">
+ <title>Sheet.18</title>
+ <path d="M0 196.93 L145.15 196.93 L145.15 308.85 L0 308.85 L0 196.93" class="st7"/>
+ </g>
+ <g id="shape19-37" v:mID="19" v:groupContext="shape" transform="translate(28.2638,-70.6655)">
+ <title>Sheet.19</title>
+ <path d="M0 280.69 C0 277.58 2.53 275.06 5.64 275.06 L124.14 275.06 C127.26 275.06 129.78 277.58 129.78 280.69 L129.78
+ 303.22 C129.78 306.33 127.26 308.85 124.14 308.85 L5.64 308.85 C2.53 308.85 0 306.33 0 303.22 L0 280.69
+ Z" class="st1"/>
+ </g>
+ <g id="shape20-39" v:mID="20" v:groupContext="shape" transform="translate(28.2638,-70.6655)">
+ <title>Sheet.20</title>
+ <path d="M0 280.69 C0 277.58 2.53 275.06 5.64 275.06 L124.14 275.06 C127.26 275.06 129.78 277.58 129.78 280.69 L129.78
+ 303.22 C129.78 306.33 127.26 308.85 124.14 308.85 L5.64 308.85 C2.53 308.85 0 306.33 0 303.22 L0 280.69
+ Z" class="st2"/>
+ </g>
+ <g id="shape21-41" v:mID="21" v:groupContext="shape" transform="translate(57.4514,-85.7513)">
+ <title>Sheet.21</title>
+ <desc>hash_index =</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="45.0133" cy="301.658" width="90.03" height="14.3829"/>
+ <path d="M90.03 294.47 L0 294.47 L0 308.85 L90.03 308.85 L90.03 294.47" class="st3"/>
+ <text x="9.2" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash_index = </text> </g>
+ <g id="shape22-45" v:mID="22" v:groupContext="shape" transform="translate(76.3001,-71.3719)">
+ <title>Sheet.22</title>
+ <desc>38123</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.0762" cy="301.658" width="42.16" height="14.3829"/>
+ <path d="M42.15 294.47 L0 294.47 L0 308.85 L42.15 308.85 L42.15 294.47" class="st3"/>
+ <text x="4.42" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>38123</text> </g>
+ <g id="shape23-49" v:mID="23" v:groupContext="shape" transform="translate(28.2638,-27.048)">
+ <title>Sheet.23</title>
+ <path d="M0 280.69 C0 277.59 2.54 275.06 5.64 275.06 L124.14 275.06 C127.26 275.06 129.78 277.59 129.78 280.69 L129.78
+ 303.22 C129.78 306.33 127.26 308.85 124.14 308.85 L5.64 308.85 C2.54 308.85 0 306.33 0 303.22 L0 280.69
+ Z" class="st1"/>
+ </g>
+ <g id="shape24-51" v:mID="24" v:groupContext="shape" transform="translate(28.2638,-27.048)">
+ <title>Sheet.24</title>
+ <path d="M0 280.69 C0 277.59 2.54 275.06 5.64 275.06 L124.14 275.06 C127.26 275.06 129.78 277.59 129.78 280.69 L129.78
+ 303.22 C129.78 306.33 127.26 308.85 124.14 308.85 L5.64 308.85 C2.54 308.85 0 306.33 0 303.22 L0 280.69
+ Z" class="st2"/>
+ </g>
+ <g id="shape25-53" v:mID="25" v:groupContext="shape" transform="translate(54.0924,-41.564)">
+ <title>Sheet.25</title>
+ <desc>lookup_table =</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="46.931" cy="301.658" width="93.87" height="14.3829"/>
+ <path d="M93.86 294.47 L0 294.47 L0 308.85 L93.86 308.85 L93.86 294.47" class="st3"/>
+ <text x="7.79" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table =</text> </g>
+ <g id="shape26-57" v:mID="26" v:groupContext="shape" transform="translate(28.0195,-28.5506)">
+ <title>Sheet.26</title>
+ <desc>0110 1100 0101 1101</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="64.89" cy="302.233" width="129.79" height="13.2327"/>
+ <path d="M129.78 295.62 L0 295.62 L0 308.85 L129.78 308.85 L129.78 295.62" class="st3"/>
+ <text x="11.25" y="305.54" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0110 11<tspan
+ class="st9">0</tspan>0 0101 1101</text> </g>
+ <g id="shape27-62" v:mID="27" v:groupContext="shape" transform="translate(26.2461,-113.863)">
+ <title>Sheet.27</title>
+ <desc>Group ID: 0x0102</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="48.6286" cy="302.881" width="97.26" height="11.9384"/>
+ <path d="M97.26 296.91 L0 296.91 L0 308.85 L97.26 308.85 L97.26 296.91" class="st3"/>
+ <text x="7.73" y="305.86" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group ID: 0x0102</text> </g>
+ <g id="shape28-66" v:mID="28" v:groupContext="shape" transform="translate(42.3703,-135.313)">
+ <title>Sheet.28</title>
+ <path d="M0 298.48 L9.84 298.48 L9.84 288.12 L29.53 288.12 L29.53 298.48 L39.38 298.48 L19.69 308.85 L0 298.48 Z"
+ class="st5"/>
+ </g>
+ <g id="shape29-68" v:mID="29" v:groupContext="shape" transform="translate(117.645,-244.476)">
+ <title>Sheet.29</title>
+ <path d="M0 274.07 L22.75 274.07 L22.75 262.48 L45.5 285.66 L22.75 308.85 L22.75 297.26 L0 297.26 L0 274.07 Z"
+ class="st5"/>
+ </g>
+ <g id="shape30-70" v:mID="30" v:groupContext="shape" transform="translate(169.209,-251.966)">
+ <title>Sheet.30</title>
+ <path d="M0 283.69 C0 280.91 2.27 278.65 5.04 278.65 L111.77 278.65 C114.56 278.65 116.81 280.91 116.81 283.69 L116.81
+ 303.82 C116.81 306.6 114.56 308.85 111.77 308.85 L5.04 308.85 C2.27 308.85 0 306.6 0 303.82 L0 283.69 Z"
+ class="st1"/>
+ </g>
+ <g id="shape31-72" v:mID="31" v:groupContext="shape" transform="translate(169.209,-251.966)">
+ <title>Sheet.31</title>
+ <path d="M0 283.69 C0 280.91 2.27 278.65 5.04 278.65 L111.77 278.65 C114.56 278.65 116.81 280.91 116.81 283.69 L116.81
+ 303.82 C116.81 306.6 114.56 308.85 111.77 308.85 L5.04 308.85 C2.27 308.85 0 306.6 0 303.82 L0 283.69 Z"
+ class="st2"/>
+ </g>
+ <g id="shape35-74" v:mID="35" v:groupContext="shape" transform="translate(291.966,-244.476)">
+ <title>Sheet.35</title>
+ <path d="M0 274.07 L22.69 274.07 L22.69 262.48 L45.38 285.66 L22.69 308.85 L22.69 297.26 L0 297.26 L0 274.07 Z"
+ class="st5"/>
+ </g>
+ <g id="shape36-76" v:mID="36" v:groupContext="shape" transform="translate(343.17,-254.482)">
+ <title>Sheet.36</title>
+ <path d="M0 288.09 C0 285.8 1.88 283.93 4.17 283.93 L109.18 283.93 C111.47 283.93 113.33 285.8 113.33 288.09 L113.33
+ 304.7 C113.33 306.99 111.47 308.85 109.18 308.85 L4.17 308.85 C1.88 308.85 0 306.99 0 304.7 L0 288.09 Z"
+ class="st1"/>
+ </g>
+ <g id="shape37-78" v:mID="37" v:groupContext="shape" transform="translate(343.17,-254.482)">
+ <title>Sheet.37</title>
+ <path d="M0 288.09 C0 285.8 1.88 283.93 4.17 283.93 L109.18 283.93 C111.47 283.93 113.33 285.8 113.33 288.09 L113.33
+ 304.7 C113.33 306.99 111.47 308.85 109.18 308.85 L4.17 308.85 C1.88 308.85 0 306.99 0 304.7 L0 288.09 Z"
+ class="st2"/>
+ </g>
+ <g id="shape38-80" v:mID="38" v:groupContext="shape" transform="translate(368.337,-257.958)">
+ <title>Sheet.38</title>
+ <desc>Position = 6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="38.1131" cy="301.658" width="76.23" height="14.3829"/>
+ <path d="M76.23 294.47 L0 294.47 L0 308.85 L76.23 308.85 L76.23 294.47" class="st3"/>
+ <text x="6.64" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Position = 6</text> </g>
+ <g id="shape39-84" v:mID="39" v:groupContext="shape" transform="translate(158.044,-86.5202)">
+ <title>Sheet.39</title>
+ <path d="M0 308.85 L69.59 308.85 C70.16 308.85 70.62 308.39 70.62 307.83 L70.62 148.5 L68.57 148.5 L68.57 307.83 L69.59
+ 306.81 L0 306.81 L0 308.85 ZM72.66 149.52 L69.59 143.4 L66.53 149.52 L72.66 149.52 Z" class="st11"/>
+ </g>
+ <g id="shape41-86" v:mID="41" v:groupContext="shape" transform="translate(335.112,-199.647)">
+ <title>Sheet.41</title>
+ <desc>Apply the equation</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="71.2648" cy="300.436" width="142.53" height="16.8275"/>
+ <path d="M142.53 292.02 L0 292.02 L0 308.85 L142.53 308.85 L142.53 292.02" class="st3"/>
+ <text x="13.19" y="304.64" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Apply the equation </text> </g>
+ <g id="shape42-90" v:mID="42" v:groupContext="shape" transform="translate(341.115,-182.871)">
+ <title>Sheet.42</title>
+ <desc>to retrieve the bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="64.5256" cy="300.436" width="129.06" height="16.8275"/>
+ <path d="M129.05 292.02 L0 292.02 L0 308.85 L129.05 308.85 L129.05 292.02" class="st3"/>
+ <text x="12.31" y="304.64" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>to retrieve the bit </text> </g>
+ <g id="shape43-94" v:mID="43" v:groupContext="shape" transform="translate(349.999,-166.095)">
+ <title>Sheet.43</title>
+ <desc>position in the</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="54.2285" cy="300.436" width="108.46" height="16.8275"/>
+ <path d="M108.46 292.02 L0 292.02 L0 308.85 L108.46 308.85 L108.46 292.02" class="st3"/>
+ <text x="10.97" y="304.64" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>position in the </text> </g>
+ <g id="shape44-98" v:mID="44" v:groupContext="shape" transform="translate(353.361,-149.319)">
+ <title>Sheet.44</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="47.9619" cy="300.436" width="95.93" height="16.8275"/>
+ <path d="M95.92 292.02 L0 292.02 L0 308.85 L95.92 308.85 L95.92 292.02" class="st3"/>
+ <text x="8.21" y="304.64" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape47-102" v:mID="47" v:groupContext="shape" transform="translate(115.17,255.2) rotate(-90)">
+ <title>1-D word balloon</title>
+ <desc>Retrieve the value “0' from the specified location in the loo...</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ <v:ud v:nameU="Scale" v:val="VT0(1):26"/>
+ <v:ud v:nameU="AntiScale" v:val="VT0(1):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="29.2016" cy="218.85" width="180" height="58.4032" transform="rotate(90)"/>
+ <path d="M0 308.85 L58.4 308.85 L58.4 128.85 L0 128.85 L0 204.67 L-11.87 38.85 L-7.09 233.03 L0 233.03 L0 308.85 Z"
+ class="st13"/>
+ <text x="136.98" y="-41.8" transform="rotate(90)" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Retrieve the value “0' from <tspan
+ x="134.41" dy="1.2em" class="st15">the specified location in the </tspan><tspan x="181.1" dy="1.2em"
+ class="st15">lookup table</tspan></text> </g>
+ <g id="shape48-107" v:mID="48" v:groupContext="shape" transform="translate(169.209,-251.966)">
+ <title>Sheet.48</title>
+ <desc>F(Key, hash_index = 38123</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54.2285" cy="295.35" width="108.46" height="27"/>
+ <rect x="0" y="281.85" width="108.457" height="27" class="st16"/>
+ <text x="5.86" y="291.75" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>F(Key, hash_index = <tspan
+ x="39.02" dy="1.2em" class="st15">38123</tspan></text> </g>
+ <g id="shape49-111" v:mID="49" v:groupContext="shape" transform="translate(553.962,99) rotate(90)">
+ <title>1-D word balloon.49</title>
+ <desc>Apply the equation to retrieve the bit position in the lookup...</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ <v:ud v:nameU="Scale" v:val="VT0(1):26"/>
+ <v:ud v:nameU="AntiScale" v:val="VT0(1):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="29.2016" cy="218.85" width="180" height="58.4032" transform="rotate(-90)"/>
+ <path d="M0 308.85 L58.4 308.85 L58.4 128.85 L0 128.85 L0 204.67 L-51.13 299.85 L0 233.03 L0 308.85 Z" class="st13"/>
+ <text x="-284.62" y="16.6" transform="rotate(-90)" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Apply the equation to <tspan
+ x="-296.67" dy="1.2em" class="st15">retrieve the bit position in </tspan><tspan x="-270.22" dy="1.2em"
+ class="st15">the lookup</tspan>_table</text> </g>
+ <g id="shape50-116" v:mID="50" v:groupContext="shape" transform="translate(640.132,-104.709) rotate(44.1224)">
+ <title>Sheet.50</title>
+ <path d="M0 308.85 L54.13 308.85" class="st18"/>
+ </g>
+ <g id="shape51-122" v:mID="51" v:groupContext="shape" transform="translate(433.02,-122.267)">
+ <title>Sheet.51</title>
+ <desc>(Hash(key,seed1)+38123*hash(key,seed2))%16</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="144" cy="295.35" width="288" height="27"/>
+ <rect x="0" y="281.85" width="288" height="27" class="st2"/>
+ <text x="9.86" y="299.55" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(Hash(key,seed1)+38123*hash(key,seed2))%16</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i12.svg b/doc/guides/prog_guide/img/efd_i12.svg
new file mode 100644
index 0000000..a309d58
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i12.svg
@@ -0,0 +1,1008 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i13.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="10.2932in" height="5.27505in"
+ viewBox="0 0 741.108 379.804" xml:space="preserve" color-interpolation-filters="sRGB" class="st30">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st2 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st3 {fill:#004280;font-family:Arial;font-size:0.828804em}
+ .st4 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st5 {stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st6 {fill:#7030a0;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st7 {fill:#d0d6d9;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st8 {fill:#006fc5;stroke:#006fc5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.00749637}
+ .st9 {fill:#006fc5;stroke:#006fc5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st10 {fill:#d0d6d9;stroke:#d0d6d9;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st11 {fill:#d0d6d9;stroke:#d0d6d9;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st12 {fill:#004280;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st13 {fill:#00b050;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st14 {fill:#ff0000;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st15 {fill:#00b050;stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st16 {fill:#c00000;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st17 {fill:#000000;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st18 {fill:#7f6d00;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st19 {fill:#ff0000;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st20 {fill:#7e8d96;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st21 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st22 {fill:#000000;font-family:Arial;font-size:0.998566em}
+ .st23 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st24 {stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st25 {fill:#ffffff;font-family:Arial;font-size:0.998566em}
+ .st26 {fill:#ff6600;stroke:#ff6600;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.00749637}
+ .st27 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st28 {fill:#ff0000;stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st29 {fill:#ffffff;font-family:Arial;font-size:1.49785em}
+ .st30 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(304.703,-329.32)">
+ <title>Sheet.3</title>
+ <path d="M0 379.8 C-0 375.37 0.6 371.78 1.35 371.78 L205.05 371.78 C205.78 371.78 206.38 368.18 206.38 363.75 C206.38
+ 368.18 206.98 371.78 207.73 371.78 L411.43 371.78 C412.15 371.78 412.75 375.37 412.75 379.8" class="st1"/>
+ </g>
+ <g id="shape4-4" v:mID="4" v:groupContext="shape" transform="translate(219.943,-329.32)">
+ <title>Sheet.4</title>
+ <path d="M0 379.8 C0 375.64 0.57 372.25 1.26 372.25 L29.77 372.25 C30.48 372.25 31.03 368.88 31.03 364.71 C31.03 368.88
+ 31.6 372.25 32.29 372.25 L60.81 372.25 C61.51 372.25 62.07 375.64 62.07 379.8" class="st1"/>
+ </g>
+ <g id="shape5-7" v:mID="5" v:groupContext="shape" transform="translate(241.175,-343.9)">
+ <title>Sheet.5</title>
+ <desc>Bins</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="12.7158" cy="373.835" width="25.44" height="11.9384"/>
+ <path d="M25.43 367.87 L0 367.87 L0 379.8 L25.43 379.8 L25.43 367.87" class="st2"/>
+ <text x="3.04" y="376.82" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Bins</text> </g>
+ <g id="shape6-11" v:mID="6" v:groupContext="shape" transform="translate(496.212,-344.504)">
+ <title>Sheet.6</title>
+ <desc>Groups</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="20.3447" cy="373.835" width="40.69" height="11.9384"/>
+ <path d="M40.69 367.87 L0 367.87 L0 379.8 L40.69 379.8 L40.69 367.87" class="st2"/>
+ <text x="4.04" y="376.82" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Groups</text> </g>
+ <g id="shape7-15" v:mID="7" v:groupContext="shape" transform="translate(131.823,-260.299)">
+ <title>Sheet.7</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape8-17" v:mID="8" v:groupContext="shape" transform="translate(131.823,-260.299)">
+ <title>Sheet.8</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape9-20" v:mID="9" v:groupContext="shape" transform="translate(134.706,-310.738)">
+ <title>Sheet.9</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape10-24" v:mID="10" v:groupContext="shape" transform="translate(122.218,-329.32)">
+ <title>Sheet.10</title>
+ <path d="M0 379.8 C-0 375.64 0.57 372.25 1.26 372.25 L29.77 372.25 C30.47 372.25 31.03 368.88 31.03 364.71 C31.03 368.88
+ 31.6 372.25 32.29 372.25 L60.81 372.25 C61.51 372.25 62.07 375.64 62.07 379.8" class="st1"/>
+ </g>
+ <g id="shape11-27" v:mID="11" v:groupContext="shape" transform="translate(137.598,-343.9)">
+ <title>Sheet.11</title>
+ <desc>Chunks</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="20.9813" cy="373.835" width="41.97" height="11.9384"/>
+ <path d="M41.96 367.87 L0 367.87 L0 379.8 L41.96 379.8 L41.96 367.87" class="st2"/>
+ <text x="4.12" y="376.82" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Chunks</text> </g>
+ <g id="shape12-31" v:mID="12" v:groupContext="shape" transform="translate(131.823,-195.232)">
+ <title>Sheet.12</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape13-33" v:mID="13" v:groupContext="shape" transform="translate(131.823,-195.232)">
+ <title>Sheet.13</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape14-36" v:mID="14" v:groupContext="shape" transform="translate(134.706,-245.682)">
+ <title>Sheet.14</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape15-40" v:mID="15" v:groupContext="shape" transform="translate(131.823,-130.525)">
+ <title>Sheet.15</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st7"/>
+ </g>
+ <g id="shape16-42" v:mID="16" v:groupContext="shape" transform="translate(131.823,-130.525)">
+ <title>Sheet.16</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape17-45" v:mID="17" v:groupContext="shape" transform="translate(134.706,-180.952)">
+ <title>Sheet.17</title>
+ <desc>…</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.30974" cy="373.835" width="14.62" height="11.9384"/>
+ <path d="M14.62 367.87 L0 367.87 L0 379.8 L14.62 379.8 L14.62 367.87" class="st2"/>
+ <text x="2.34" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>…</text> </g>
+ <g id="shape18-49" v:mID="18" v:groupContext="shape" transform="translate(131.823,-65.4584)">
+ <title>Sheet.18</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape19-51" v:mID="19" v:groupContext="shape" transform="translate(131.823,-65.4584)">
+ <title>Sheet.19</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape20-54" v:mID="20" v:groupContext="shape" transform="translate(130.403,-115.896)">
+ <title>Sheet.20</title>
+ <desc>variable</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="24.7986" cy="373.835" width="49.6" height="11.9384"/>
+ <path d="M49.6 367.87 L0 367.87 L0 379.8 L49.6 379.8 L49.6 367.87" class="st2"/>
+ <text x="6" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>variable </text> </g>
+ <g id="shape21-58" v:mID="21" v:groupContext="shape" transform="translate(130.403,-103.913)">
+ <title>Sheet.21</title>
+ <desc># of</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="13.347" cy="373.835" width="26.7" height="11.9384"/>
+ <path d="M26.69 367.87 L0 367.87 L0 379.8 L26.69 379.8 L26.69 367.87" class="st2"/>
+ <text x="4.51" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/># of </text> </g>
+ <g id="shape22-62" v:mID="22" v:groupContext="shape" transform="translate(130.403,-91.93)">
+ <title>Sheet.22</title>
+ <desc>chunks</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.6122" cy="373.835" width="43.23" height="11.9384"/>
+ <path d="M43.22 367.87 L0 367.87 L0 379.8 L43.22 379.8 L43.22 367.87" class="st2"/>
+ <text x="4.2" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>chunks</text> </g>
+ <g id="shape23-66" v:mID="23" v:groupContext="shape" transform="translate(130.403,-79.9472)">
+ <title>Sheet.23</title>
+ <desc>(power</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.9251" cy="373.835" width="43.86" height="11.9384"/>
+ <path d="M43.85 367.87 L0 367.87 L0 379.8 L43.85 379.8 L43.85 367.87" class="st2"/>
+ <text x="5.62" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(power </text> </g>
+ <g id="shape24-70" v:mID="24" v:groupContext="shape" transform="translate(130.403,-67.9643)">
+ <title>Sheet.24</title>
+ <desc>of 2)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="13.6626" cy="373.835" width="27.33" height="11.9384"/>
+ <path d="M27.33 367.87 L0 367.87 L0 379.8 L27.33 379.8 L27.33 367.87" class="st2"/>
+ <text x="3.17" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>of 2)</text> </g>
+ <g id="shape25-74" v:mID="25" v:groupContext="shape" transform="translate(172.289,-260.838)">
+ <title>Sheet.25</title>
+ <path d="M1.43 379.8 L3.29 375.51 L1.85 374.9 L0 379.19 L1.43 379.8 L1.43 379.8 ZM3.9 374.08 L5.76 369.79 L4.32 369.18
+ L2.47 373.47 L3.9 374.08 L3.9 374.08 ZM6.37 368.36 L8.22 364.07 L6.79 363.45 L4.94 367.75 L6.37 368.36 L6.37
+ 368.36 ZM8.84 362.64 L10.69 358.35 L9.26 357.73 L7.41 362.02 L8.84 362.64 L8.84 362.64 ZM11.31 356.92 L13.16
+ 352.62 L11.73 352 L9.87 356.3 L11.31 356.92 L11.31 356.92 ZM13.78 351.19 L15.63 346.9 L14.2 346.28 L12.34
+ 350.57 L13.78 351.19 L13.78 351.19 ZM16.25 345.47 L18.1 341.17 L16.67 340.56 L14.81 344.85 L16.25 345.47
+ L16.25 345.47 ZM18.71 339.74 L20.57 335.45 L19.13 334.84 L17.28 339.13 L18.71 339.74 L18.71 339.74 ZM21.18
+ 334.02 L23.04 329.73 L21.6 329.12 L19.75 333.41 L21.18 334.02 L21.18 334.02 ZM23.65 328.3 L25.5 324.01 L24.07
+ 323.39 L22.22 327.68 L23.65 328.3 L23.65 328.3 ZM26.12 322.58 L27.97 318.28 L26.54 317.67 L24.69 321.96
+ L26.12 322.58 L26.12 322.58 ZM28.59 316.85 L29.44 314.87 L28.01 314.25 L27.16 316.24 L28.59 316.85 L28.59
+ 316.85 Z" class="st8"/>
+ </g>
+ <g id="shape26-76" v:mID="26" v:groupContext="shape" transform="translate(172.476,-20.463)">
+ <title>Sheet.26</title>
+ <path d="M1.55 203.84 L2.28 208.45 L0.74 208.7 L0 204.09 L1.55 203.84 L1.55 203.84 ZM2.52 209.99 L3.27 214.61 L1.73 214.86
+ L0.99 210.23 L2.52 209.99 L2.52 209.99 ZM3.51 216.15 L4.25 220.76 L2.7 221 L1.97 216.39 L3.51 216.15 L3.51
+ 216.15 ZM4.49 222.3 L5.24 226.92 L3.69 227.16 L2.96 222.54 L4.49 222.3 L4.49 222.3 ZM5.48 228.45 L6.21 233.07
+ L4.67 233.31 L3.93 228.7 L5.48 228.45 L5.48 228.45 ZM6.47 234.6 L7.2 239.22 L5.66 239.47 L4.92 234.86 L6.47
+ 234.6 L6.47 234.6 ZM7.44 240.76 L8.18 245.37 L6.65 245.63 L5.9 241 L7.44 240.76 L7.44 240.76 ZM8.43 246.91
+ L9.17 251.53 L7.62 251.77 L6.89 247.15 L8.43 246.91 L8.43 246.91 ZM9.41 253.07 L10.14 257.68 L8.61 257.92
+ L7.88 253.31 L9.41 253.07 L9.41 253.07 ZM10.4 259.21 L11.14 263.84 L9.59 264.08 L8.85 259.47 L10.4 259.21
+ L10.4 259.21 ZM11.38 265.37 L12.13 269.98 L10.58 270.24 L9.84 265.62 L11.38 265.37 L11.38 265.37 ZM12.37
+ 271.52 L13.1 276.14 L11.56 276.39 L10.82 271.76 L12.37 271.52 L12.37 271.52 ZM13.34 277.68 L14.09 282.29
+ L12.55 282.53 L11.81 277.92 L13.34 277.68 L13.34 277.68 ZM14.33 283.84 L15.07 288.45 L13.52 288.69 L12.79
+ 284.08 L14.33 283.84 L14.33 283.84 ZM15.32 289.99 L16.06 294.61 L14.51 294.85 L13.78 290.23 L15.32 289.99
+ L15.32 289.99 ZM16.3 296.13 L17.03 300.75 L15.5 301 L14.75 296.39 L16.3 296.13 L16.3 296.13 ZM17.29 302.29
+ L18.02 306.9 L16.48 307.16 L15.74 302.53 L17.29 302.29 L17.29 302.29 ZM18.26 308.45 L19 313.06 L17.47 313.3
+ L16.73 308.69 L18.26 308.45 L18.26 308.45 ZM19.25 314.6 L19.99 319.22 L18.44 319.46 L17.71 314.84 L19.25
+ 314.6 L19.25 314.6 ZM20.23 320.76 L20.96 325.37 L19.43 325.61 L18.7 321 L20.23 320.76 L20.23 320.76 ZM21.22
+ 326.9 L21.96 331.51 L20.41 331.77 L19.67 327.15 L21.22 326.9 L21.22 326.9 ZM22.2 333.06 L22.95 337.67 L21.4
+ 337.92 L20.66 333.31 L22.2 333.06 L22.2 333.06 ZM23.19 339.21 L23.92 343.83 L22.38 344.07 L21.64 339.45
+ L23.19 339.21 L23.19 339.21 ZM24.18 345.37 L24.91 349.98 L23.37 350.22 L22.63 345.61 L24.18 345.37 L24.18
+ 345.37 ZM25.15 351.52 L25.89 356.14 L24.36 356.38 L23.61 351.76 L25.15 351.52 L25.15 351.52 ZM26.14 357.67
+ L26.88 362.28 L25.33 362.53 L24.6 357.92 L26.14 357.67 L26.14 357.67 ZM27.12 363.82 L27.85 368.44 L26.32
+ 368.69 L25.59 364.08 L27.12 363.82 L27.12 363.82 ZM28.11 369.98 L28.84 374.59 L27.3 374.83 L26.56 370.22
+ L28.11 369.98 L28.11 369.98 ZM29.08 376.13 L29.64 379.55 L28.09 379.8 L27.55 376.37 L29.08 376.13 L29.08
+ 376.13 Z" class="st9"/>
+ </g>
+ <g id="shape27-78" v:mID="27" v:groupContext="shape" transform="translate(276.159,-233.368)">
+ <title>Sheet.27</title>
+ <path d="M0.45 294.85 L354.04 376.06 L353.59 378.04 L0 296.85 L0.45 294.85 L0.45 294.85 ZM353.5 373.84 L358.79 378.19
+ L352.12 379.8 L353.5 373.84 L353.5 373.84 Z" class="st10"/>
+ </g>
+ <g id="shape28-80" v:mID="28" v:groupContext="shape" transform="translate(275.859,-178.726)">
+ <title>Sheet.28</title>
+ <path d="M1.05 240.32 L231.44 376.33 L230.39 378.1 L0 242.09 L1.05 240.32 L1.05 240.32 ZM231.59 374.05 L235.31 379.8
+ L228.47 379.32 L231.59 374.05 L231.59 374.05 Z" class="st10"/>
+ </g>
+ <g id="shape29-82" v:mID="29" v:groupContext="shape" transform="translate(275.379,-87.6866)">
+ <title>Sheet.29</title>
+ <path d="M2 149.94 L50.05 374.61 L48.05 375.04 L0 150.38 L2 149.94 L2 149.94 ZM51.83 373.18 L50.12 379.8 L45.85 374.47
+ L51.83 373.18 L51.83 373.18 Z" class="st11"/>
+ </g>
+ <g id="shape30-84" v:mID="30" v:groupContext="shape" transform="translate(276.279,-177.108)">
+ <title>Sheet.30</title>
+ <path d="M0.21 353.74 L229.55 375.85 L229.34 377.89 L0 355.75 L0.21 353.74 L0.21 353.74 ZM228.71 373.72 L234.53 377.35
+ L228.14 379.8 L228.71 373.72 L228.71 373.72 Z" class="st10"/>
+ </g>
+ <g id="shape31-86" v:mID="31" v:groupContext="shape" transform="translate(275.919,-213.926)">
+ <title>Sheet.31</title>
+ <path d="M0.45 308.72 L312.65 376.06 L312.2 378.04 L0 310.72 L0.45 308.72 L0.45 308.72 ZM312.08 373.84 L317.43 378.13
+ L310.79 379.8 L312.08 373.84 L312.08 373.84 Z" class="st10"/>
+ </g>
+ <g id="shape32-88" v:mID="32" v:groupContext="shape" transform="translate(275.439,-143.377)">
+ <title>Sheet.32</title>
+ <path d="M1.4 238.41 L150.34 375.59 L148.96 377.09 L0 239.9 L1.4 238.41 L1.4 238.41 ZM150.98 373.41 L153.4 379.8 L146.83
+ 377.9 L150.98 373.41 L150.98 373.41 Z" class="st11"/>
+ </g>
+ <g id="shape33-90" v:mID="33" v:groupContext="shape" transform="translate(275.274,-108.821)">
+ <title>Sheet.33</title>
+ <path d="M1.73 236.53 L90.79 374.97 L89.08 376.07 L0 237.63 L1.73 236.53 L1.73 236.53 ZM91.96 373 L92.7 379.8 L86.82
+ 376.31 L91.96 373 L91.96 373 Z" class="st11"/>
+ </g>
+ <g id="shape34-92" v:mID="34" v:groupContext="shape" transform="translate(275.364,-124.069)">
+ <title>Sheet.34</title>
+ <path d="M1.55 251.66 L108.22 375.28 L106.67 376.61 L0 253 L1.55 251.66 L1.55 251.66 ZM109.1 373.18 L110.78 379.8 L104.46
+ 377.17 L109.1 373.18 L109.1 373.18 Z" class="st11"/>
+ </g>
+ <g id="shape35-94" v:mID="35" v:groupContext="shape" transform="translate(275.154,-87.7165)">
+ <title>Sheet.35</title>
+ <path d="M1.97 215.68 L49.85 374.64 L47.9 375.22 L0 216.27 L1.97 215.68 L1.97 215.68 ZM51.52 373.08 L50.35 379.8 L45.65
+ 374.83 L51.52 373.08 L51.52 373.08 Z" class="st11"/>
+ </g>
+ <g id="shape36-96" v:mID="36" v:groupContext="shape" transform="translate(276.009,-143.736)">
+ <title>Sheet.36</title>
+ <path d="M0.74 320.41 L147.92 376.36 L147.2 378.26 L0 322.32 L0.74 320.41 L0.74 320.41 ZM147.7 374.08 L152.34 379.11
+ L145.52 379.8 L147.7 374.08 L147.7 374.08 Z" class="st11"/>
+ </g>
+ <g id="shape37-98" v:mID="37" v:groupContext="shape" transform="translate(275.649,-108.821)">
+ <title>Sheet.37</title>
+ <path d="M1.46 285.74 L89.46 375.45 L88 376.87 L0 287.16 L1.46 285.74 L1.46 285.74 ZM90.21 373.29 L92.29 379.8 L85.82
+ 377.57 L90.21 373.29 L90.21 373.29 Z" class="st11"/>
+ </g>
+ <g id="shape38-100" v:mID="38" v:groupContext="shape" transform="translate(275.934,-108.686)">
+ <title>Sheet.38</title>
+ <path d="M0.89 335.24 L87.85 376.57 L86.97 378.41 L0 337.09 L0.89 335.24 L0.89 335.24 ZM87.81 374.29 L92.01 379.67 L85.16
+ 379.8 L87.81 374.29 L87.81 374.29 Z" class="st11"/>
+ </g>
+ <g id="shape39-102" v:mID="39" v:groupContext="shape" transform="translate(275.574,-89.454)">
+ <title>Sheet.39</title>
+ <path d="M1.61 316.29 L48.49 375.18 L46.88 376.45 L0 317.57 L1.61 316.29 L1.61 316.29 ZM49.45 373.11 L50.86 379.8 L44.65
+ 376.91 L49.45 373.11 L49.45 373.11 Z" class="st11"/>
+ </g>
+ <g id="shape40-104" v:mID="40" v:groupContext="shape" transform="translate(276.324,-141.744)">
+ <title>Sheet.40</title>
+ <path d="M0.11 368.21 L146.74 375.79 L146.62 377.83 L0 370.23 L0.11 368.21 L0.11 368.21 ZM145.82 373.71 L151.78 377.08
+ L145.51 379.8 L145.82 373.71 L145.82 373.71 Z" class="st11"/>
+ </g>
+ <g id="shape41-106" v:mID="41" v:groupContext="shape" transform="translate(230.508,-309.069)">
+ <title>Sheet.41</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st4"/>
+ </g>
+ <g id="shape42-108" v:mID="42" v:groupContext="shape" transform="translate(230.508,-309.069)">
+ <title>Sheet.42</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape43-111" v:mID="43" v:groupContext="shape" transform="translate(233.39,-309.868)">
+ <title>Sheet.43</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape44-115" v:mID="44" v:groupContext="shape" transform="translate(263.764,-309.869)">
+ <title>Sheet.44</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape45-119" v:mID="45" v:groupContext="shape" transform="translate(230.508,-292.413)">
+ <title>Sheet.45</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape46-121" v:mID="46" v:groupContext="shape" transform="translate(230.508,-292.413)">
+ <title>Sheet.46</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape47-124" v:mID="47" v:groupContext="shape" transform="translate(233.39,-293.221)">
+ <title>Sheet.47</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape48-128" v:mID="48" v:groupContext="shape" transform="translate(230.508,-275.757)">
+ <title>Sheet.48</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st4"/>
+ </g>
+ <g id="shape49-130" v:mID="49" v:groupContext="shape" transform="translate(230.508,-275.757)">
+ <title>Sheet.49</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape50-133" v:mID="50" v:groupContext="shape" transform="translate(233.39,-276.574)">
+ <title>Sheet.50</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape51-137" v:mID="51" v:groupContext="shape" transform="translate(252.478,-276.574)">
+ <title>Sheet.51</title>
+ <desc>3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>3</text> </g>
+ <g id="shape52-141" v:mID="52" v:groupContext="shape" transform="translate(258.001,-276.574)">
+ <title>Sheet.52</title>
+ <desc>+1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="8.11122" cy="373.835" width="16.23" height="11.9384"/>
+ <path d="M16.22 367.87 L0 367.87 L0 379.8 L16.22 379.8 L16.22 367.87" class="st2"/>
+ <text x="2.44" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>+1</text> </g>
+ <g id="shape53-145" v:mID="53" v:groupContext="shape" transform="translate(230.508,-259.7)">
+ <title>Sheet.53</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape54-147" v:mID="54" v:groupContext="shape" transform="translate(230.508,-259.7)">
+ <title>Sheet.54</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape55-150" v:mID="55" v:groupContext="shape" transform="translate(233.39,-260.497)">
+ <title>Sheet.55</title>
+ <desc>3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>3</text> </g>
+ <g id="shape56-154" v:mID="56" v:groupContext="shape" transform="translate(230.508,-243.164)">
+ <title>Sheet.56</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st4"/>
+ </g>
+ <g id="shape57-156" v:mID="57" v:groupContext="shape" transform="translate(230.508,-243.164)">
+ <title>Sheet.57</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape58-159" v:mID="58" v:groupContext="shape" transform="translate(233.39,-244.053)">
+ <title>Sheet.58</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape59-163" v:mID="59" v:groupContext="shape" transform="translate(263.764,-244.053)">
+ <title>Sheet.59</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape60-167" v:mID="60" v:groupContext="shape" transform="translate(230.508,-227.107)">
+ <title>Sheet.60</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape61-169" v:mID="61" v:groupContext="shape" transform="translate(230.508,-227.107)">
+ <title>Sheet.61</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape62-172" v:mID="62" v:groupContext="shape" transform="translate(233.39,-227.976)">
+ <title>Sheet.62</title>
+ <desc>5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>5</text> </g>
+ <g id="shape63-176" v:mID="63" v:groupContext="shape" transform="translate(230.508,-210.211)">
+ <title>Sheet.63</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape64-178" v:mID="64" v:groupContext="shape" transform="translate(230.508,-210.211)">
+ <title>Sheet.64</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape65-181" v:mID="65" v:groupContext="shape" transform="translate(233.39,-211.085)">
+ <title>Sheet.65</title>
+ <desc>6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>6</text> </g>
+ <g id="shape66-185" v:mID="66" v:groupContext="shape" transform="translate(230.508,-193.794)">
+ <title>Sheet.66</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st4"/>
+ </g>
+ <g id="shape67-187" v:mID="67" v:groupContext="shape" transform="translate(230.508,-193.794)">
+ <title>Sheet.67</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape68-190" v:mID="68" v:groupContext="shape" transform="translate(233.39,-194.681)">
+ <title>Sheet.68</title>
+ <desc>7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>7</text> </g>
+ <g id="shape69-194" v:mID="69" v:groupContext="shape" transform="translate(263.764,-194.681)">
+ <title>Sheet.69</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape70-198" v:mID="70" v:groupContext="shape" transform="translate(230.508,-177.258)">
+ <title>Sheet.70</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape71-200" v:mID="71" v:groupContext="shape" transform="translate(230.508,-177.258)">
+ <title>Sheet.71</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape72-203" v:mID="72" v:groupContext="shape" transform="translate(233.39,-178.117)">
+ <title>Sheet.72</title>
+ <desc>8</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>8</text> </g>
+ <g id="shape73-207" v:mID="73" v:groupContext="shape" transform="translate(230.508,-160.602)">
+ <title>Sheet.73</title>
+ <path d="M0 363.15 L0 379.8 L41.18 379.8 L41.18 363.15 L0 363.15 L0 363.15 Z" class="st7"/>
+ </g>
+ <g id="shape74-209" v:mID="74" v:groupContext="shape" transform="translate(230.508,-160.602)">
+ <title>Sheet.74</title>
+ <path d="M0 363.15 L41.18 363.15 L41.18 379.8 L0 379.8 L0 363.15" class="st5"/>
+ </g>
+ <g id="shape75-212" v:mID="75" v:groupContext="shape" transform="translate(233.39,-161.505)">
+ <title>Sheet.75</title>
+ <desc>9</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>9</text> </g>
+ <g id="shape76-216" v:mID="76" v:groupContext="shape" transform="translate(230.508,-143.946)">
+ <title>Sheet.76</title>
+ <path d="M0 363.15 L0 379.8 L41.18 379.8 L41.18 363.15 L0 363.15 L0 363.15 Z" class="st4"/>
+ </g>
+ <g id="shape77-218" v:mID="77" v:groupContext="shape" transform="translate(230.508,-143.946)">
+ <title>Sheet.77</title>
+ <path d="M0 363.15 L41.18 363.15 L41.18 379.8 L0 379.8 L0 363.15" class="st5"/>
+ </g>
+ <g id="shape78-221" v:mID="78" v:groupContext="shape" transform="translate(233.39,-144.841)">
+ <title>Sheet.78</title>
+ <desc>10</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>10</text> </g>
+ <g id="shape79-225" v:mID="79" v:groupContext="shape" transform="translate(263.764,-144.841)">
+ <title>Sheet.79</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape80-229" v:mID="80" v:groupContext="shape" transform="translate(230.508,-127.529)">
+ <title>Sheet.80</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape81-231" v:mID="81" v:groupContext="shape" transform="translate(230.508,-127.529)">
+ <title>Sheet.81</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape82-234" v:mID="82" v:groupContext="shape" transform="translate(233.39,-128.329)">
+ <title>Sheet.82</title>
+ <desc>11</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>11</text> </g>
+ <g id="shape83-238" v:mID="83" v:groupContext="shape" transform="translate(230.508,-110.754)">
+ <title>Sheet.83</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape84-240" v:mID="84" v:groupContext="shape" transform="translate(230.508,-110.754)">
+ <title>Sheet.84</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape85-243" v:mID="85" v:groupContext="shape" transform="translate(233.39,-111.64)">
+ <title>Sheet.85</title>
+ <desc>12</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>12</text> </g>
+ <g id="shape86-247" v:mID="86" v:groupContext="shape" transform="translate(230.508,-94.9362)">
+ <title>Sheet.86</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape87-249" v:mID="87" v:groupContext="shape" transform="translate(230.508,-94.9362)">
+ <title>Sheet.87</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape88-252" v:mID="88" v:groupContext="shape" transform="translate(233.39,-95.7375)">
+ <title>Sheet.88</title>
+ <desc>…</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.30974" cy="373.835" width="14.62" height="11.9384"/>
+ <path d="M14.62 367.87 L0 367.87 L0 379.8 L14.62 379.8 L14.62 367.87" class="st2"/>
+ <text x="2.34" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>…</text> </g>
+ <g id="shape89-256" v:mID="89" v:groupContext="shape" transform="translate(230.508,-78.999)">
+ <title>Sheet.89</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape90-258" v:mID="90" v:groupContext="shape" transform="translate(230.508,-78.999)">
+ <title>Sheet.90</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape91-261" v:mID="91" v:groupContext="shape" transform="translate(233.39,-79.8525)">
+ <title>Sheet.91</title>
+ <desc>255</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1326" cy="373.835" width="22.27" height="11.9384"/>
+ <path d="M22.27 367.87 L0 367.87 L0 379.8 L22.27 379.8 L22.27 367.87" class="st2"/>
+ <text x="2.84" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>255</text> </g>
+ <g id="shape92-265" v:mID="92" v:groupContext="shape" transform="translate(276.219,-250.503)">
+ <title>Sheet.92</title>
+ <path d="M0.33 311.98 L396.81 375.94 L396.48 377.95 L0 313.99 L0.33 311.98 L0.33 311.98 ZM396.12 373.75 L401.68 377.74
+ L395.16 379.8 L396.12 373.75 L396.12 373.75 Z" class="st15"/>
+ </g>
+ <g id="shape93-267" v:mID="93" v:groupContext="shape" transform="translate(275.859,-178.426)">
+ <title>Sheet.93</title>
+ <path d="M0.57 305.72 L230.93 376.21 L230.33 378.16 L0 307.67 L0.57 305.72 L0.57 305.72 ZM230.57 373.96 L235.52 378.67
+ L228.77 379.8 L230.57 373.96 L230.57 373.96 Z" class="st15"/>
+ </g>
+ <g id="shape94-269" v:mID="94" v:groupContext="shape" transform="translate(276.279,-151.285)">
+ <title>Sheet.94</title>
+ <path d="M0.21 379.8 L230.12 353.17 L229.88 351.14 L0 377.8 L0.21 379.8 L0.21 379.8 ZM229.34 355.3 L235.07 351.55 L228.65
+ 349.25 L229.34 355.3 L229.34 355.3 Z" class="st15"/>
+ </g>
+ <g id="shape95-271" v:mID="95" v:groupContext="shape" transform="translate(276.009,-232.679)">
+ <title>Sheet.95</title>
+ <path d="M0.27 327.47 L354.22 375.91 L353.95 377.92 L0 329.48 L0.27 327.47 L0.27 327.47 ZM353.5 373.75 L359.15 377.62
+ L352.66 379.8 L353.5 373.75 L353.5 373.75 Z" class="st10"/>
+ </g>
+ <g id="shape96-273" v:mID="96" v:groupContext="shape" transform="translate(276.279,-201.134)">
+ <title>Sheet.96</title>
+ <path d="M0.21 379.8 L353.86 348.14 L353.68 346.1 L0 377.77 L0.21 379.8 L0.21 379.8 ZM353.05 350.24 L358.88 346.64 L352.48
+ 344.16 L353.05 350.24 L353.05 350.24 Z" class="st15"/>
+ </g>
+ <g id="shape97-275" v:mID="97" v:groupContext="shape" transform="translate(346.482,-41.2531)">
+ <title>Sheet.97</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st7"/>
+ </g>
+ <g id="shape98-277" v:mID="98" v:groupContext="shape" transform="translate(346.482,-41.2531)">
+ <title>Sheet.98</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape99-280" v:mID="99" v:groupContext="shape" transform="translate(349.371,-91.6514)">
+ <title>Sheet.99</title>
+ <desc>…</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.30974" cy="373.835" width="14.62" height="11.9384"/>
+ <path d="M14.62 367.87 L0 367.87 L0 379.8 L14.62 379.8 L14.62 367.87" class="st2"/>
+ <text x="2.34" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>…</text> </g>
+ <g id="shape100-284" v:mID="100" v:groupContext="shape" transform="translate(470.019,-94.337)">
+ <title>Sheet.100</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st7"/>
+ </g>
+ <g id="shape101-286" v:mID="101" v:groupContext="shape" transform="translate(470.019,-94.337)">
+ <title>Sheet.101</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape102-289" v:mID="102" v:groupContext="shape" transform="translate(472.925,-144.778)">
+ <title>Sheet.102</title>
+ <desc>5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>5</text> </g>
+ <g id="shape103-293" v:mID="103" v:groupContext="shape" transform="translate(511.558,-113.749)">
+ <title>Sheet.103</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape104-295" v:mID="104" v:groupContext="shape" transform="translate(511.558,-113.749)">
+ <title>Sheet.104</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape105-298" v:mID="105" v:groupContext="shape" transform="translate(514.441,-164.138)">
+ <title>Sheet.105</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape106-302" v:mID="106" v:groupContext="shape" transform="translate(542.148,-164.138)">
+ <title>Sheet.106</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape107-306" v:mID="107" v:groupContext="shape" transform="translate(542.148,-152.155)">
+ <title>Sheet.107</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape108-310" v:mID="108" v:groupContext="shape" transform="translate(536.626,-140.172)">
+ <title>Sheet.108</title>
+ <desc>10</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>10</text> </g>
+ <g id="shape109-314" v:mID="109" v:groupContext="shape" transform="translate(514.201,-114.441)">
+ <title>Sheet.109</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape110-318" v:mID="110" v:groupContext="shape" transform="translate(519.723,-114.441)">
+ <title>Sheet.110</title>
+ <desc>+4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="8.11122" cy="373.835" width="16.23" height="11.9384"/>
+ <path d="M16.22 367.87 L0 367.87 L0 379.8 L16.22 379.8 L16.22 367.87" class="st2"/>
+ <text x="2.44" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>+4</text> </g>
+ <g id="shape111-322" v:mID="111" v:groupContext="shape" transform="translate(552.257,-130.525)">
+ <title>Sheet.111</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st7"/>
+ </g>
+ <g id="shape112-324" v:mID="112" v:groupContext="shape" transform="translate(552.257,-130.525)">
+ <title>Sheet.112</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape113-327" v:mID="113" v:groupContext="shape" transform="translate(555.203,-180.952)">
+ <title>Sheet.113</title>
+ <desc>3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>3</text> </g>
+ <g id="shape114-331" v:mID="114" v:groupContext="shape" transform="translate(634.615,-169.11)">
+ <title>Sheet.114</title>
+ <path d="M0 313.9 L0 379.8 L41.18 379.8 L41.18 313.9 L0 313.9 L0 313.9 Z" class="st4"/>
+ </g>
+ <g id="shape115-333" v:mID="115" v:groupContext="shape" transform="translate(634.615,-169.11)">
+ <title>Sheet.115</title>
+ <path d="M0 313.9 L41.18 313.9 L41.18 379.8 L0 379.8 L0 313.9" class="st5"/>
+ </g>
+ <g id="shape116-336" v:mID="116" v:groupContext="shape" transform="translate(637.526,-219.595)">
+ <title>Sheet.116</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape117-340" v:mID="117" v:groupContext="shape" transform="translate(665.234,-219.595)">
+ <title>Sheet.117</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape118-344" v:mID="118" v:groupContext="shape" transform="translate(665.2,-225.489)">
+ <title>Sheet.118</title>
+ <path d="M0 379.32 L0 379.8 L5.52 379.8 L5.52 379.32 L0 379.32 L0 379.32 Z" class="st19"/>
+ </g>
+ <g id="shape119-346" v:mID="119" v:groupContext="shape" transform="translate(665.234,-207.612)">
+ <title>Sheet.119</title>
+ <desc>7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>7</text> </g>
+ <g id="shape120-350" v:mID="120" v:groupContext="shape" transform="translate(637.286,-169.898)">
+ <title>Sheet.120</title>
+ <desc>5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>5</text> </g>
+ <g id="shape121-354" v:mID="121" v:groupContext="shape" transform="translate(642.809,-169.898)">
+ <title>Sheet.121</title>
+ <desc>-</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="3.49545" cy="373.835" width="7" height="11.9384"/>
+ <path d="M6.99 367.87 L0 367.87 L0 379.8 L6.99 379.8 L6.99 367.87" class="st2"/>
+ <text x="1.84" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>-</text> </g>
+ <g id="shape122-358" v:mID="122" v:groupContext="shape" transform="translate(646.17,-169.898)">
+ <title>Sheet.122</title>
+ <desc>3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>3</text> </g>
+ <g id="shape123-362" v:mID="123" v:groupContext="shape" transform="translate(676.275,-186.725)">
+ <title>Sheet.123</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape124-364" v:mID="124" v:groupContext="shape" transform="translate(676.275,-186.725)">
+ <title>Sheet.124</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape125-367" v:mID="125" v:groupContext="shape" transform="translate(679.141,-237.17)">
+ <title>Sheet.125</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape126-371" v:mID="126" v:groupContext="shape" transform="translate(706.849,-237.17)">
+ <title>Sheet.126</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape127-375" v:mID="127" v:groupContext="shape" transform="translate(678.901,-187.474)">
+ <title>Sheet.127</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape128-379" v:mID="128" v:groupContext="shape" transform="translate(304.943,-21.841)">
+ <title>Sheet.128</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape129-381" v:mID="129" v:groupContext="shape" transform="translate(304.943,-21.841)">
+ <title>Sheet.129</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape130-384" v:mID="130" v:groupContext="shape" transform="translate(307.855,-72.2917)">
+ <title>Sheet.130</title>
+ <desc>64</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>64</text> </g>
+ <g id="shape131-388" v:mID="131" v:groupContext="shape" transform="translate(330.041,-72.2917)">
+ <title>Sheet.131</title>
+ <desc>96</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>96</text> </g>
+ <g id="shape132-392" v:mID="132" v:groupContext="shape" transform="translate(307.616,-22.5952)">
+ <title>Sheet.132</title>
+ <desc>7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>7</text> </g>
+ <g id="shape133-396" v:mID="133" v:groupContext="shape" transform="translate(428.72,-77.4413)">
+ <title>Sheet.133</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape134-398" v:mID="134" v:groupContext="shape" transform="translate(428.72,-77.4413)">
+ <title>Sheet.134</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape135-401" v:mID="135" v:groupContext="shape" transform="translate(431.648,-127.825)">
+ <title>Sheet.135</title>
+ <desc>6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>6</text> </g>
+ <g id="shape136-405" v:mID="136" v:groupContext="shape" transform="translate(453.834,-127.825)">
+ <title>Sheet.136</title>
+ <desc>98</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>98</text> </g>
+ <g id="shape137-409" v:mID="137" v:groupContext="shape" transform="translate(431.409,-78.1289)">
+ <title>Sheet.137</title>
+ <desc>5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>5</text> </g>
+ <g id="shape138-413" v:mID="138" v:groupContext="shape" transform="translate(593.796,-149.818)">
+ <title>Sheet.138</title>
+ <path d="M0 313.9 L0 379.8 L41.18 379.8 L41.18 313.9 L0 313.9 L0 313.9 Z" class="st4"/>
+ </g>
+ <g id="shape139-415" v:mID="139" v:groupContext="shape" transform="translate(593.796,-149.818)">
+ <title>Sheet.139</title>
+ <path d="M0 313.9 L41.18 313.9 L41.18 379.8 L0 379.8 L0 313.9" class="st5"/>
+ </g>
+ <g id="shape140-418" v:mID="140" v:groupContext="shape" transform="translate(596.718,-200.312)">
+ <title>Sheet.140</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape141-422" v:mID="141" v:groupContext="shape" transform="translate(618.904,-200.312)">
+ <title>Sheet.141</title>
+ <desc>99</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>99</text> </g>
+ <g id="shape142-426" v:mID="142" v:groupContext="shape" transform="translate(596.478,-150.615)">
+ <title>Sheet.142</title>
+ <desc>9</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>9</text> </g>
+ <g id="shape143-430" v:mID="143" v:groupContext="shape" transform="translate(387.181,-58.0291)">
+ <title>Sheet.143</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape144-432" v:mID="144" v:groupContext="shape" transform="translate(387.181,-58.0291)">
+ <title>Sheet.144</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape145-435" v:mID="145" v:groupContext="shape" transform="translate(390.133,-108.466)">
+ <title>Sheet.145</title>
+ <desc>7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>7</text> </g>
+ <g id="shape146-439" v:mID="146" v:groupContext="shape" transform="translate(412.318,-108.466)">
+ <title>Sheet.146</title>
+ <desc>97</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>97</text> </g>
+ <g id="shape147-443" v:mID="147" v:groupContext="shape" transform="translate(389.893,-58.7692)">
+ <title>Sheet.147</title>
+ <desc>6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>6</text> </g>
+ <g id="shape148-447" v:mID="148" v:groupContext="shape" transform="translate(31.8163,-277.674)">
+ <title>Sheet.148</title>
+ <path d="M0 362.83 C0 360.95 1.52 359.43 3.41 359.43 L68.51 359.43 C70.4 359.43 71.91 360.95 71.91 362.83 L71.91 376.41
+ C71.91 378.28 70.4 379.8 68.51 379.8 L3.41 379.8 C1.52 379.8 0 378.28 0 376.41 L0 362.83 Z" class="st4"/>
+ </g>
+ <g id="shape149-449" v:mID="149" v:groupContext="shape" transform="translate(31.8163,-277.674)">
+ <title>Sheet.149</title>
+ <path d="M0 362.83 C0 360.95 1.52 359.43 3.41 359.43 L68.51 359.43 C70.4 359.43 71.91 360.95 71.91 362.83 L71.91 376.41
+ C71.91 378.28 70.4 379.8 68.51 379.8 L3.41 379.8 C1.52 379.8 0 378.28 0 376.41 L0 362.83 Z" class="st21"/>
+ </g>
+ <g id="shape150-451" v:mID="150" v:groupContext="shape" transform="translate(36,-278.851)">
+ <title>Sheet.150</title>
+ <desc>Insert key</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="35.613" cy="372.612" width="71.23" height="14.3829"/>
+ <path d="M71.23 365.42 L0 365.42 L0 379.8 L71.23 379.8 L71.23 365.42" class="st2"/>
+ <text x="9.64" y="376.21" class="st22" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Insert key </text> </g>
+ <g id="shape151-455" v:mID="151" v:groupContext="shape" transform="translate(47.7236,-257.004)">
+ <title>Sheet.151</title>
+ <path d="M0 369.44 L9.81 369.44 L9.81 359.07 L29.44 359.07 L29.44 369.44 L39.26 369.44 L19.63 379.8 L0 369.44 Z"
+ class="st23"/>
+ </g>
+ <g id="shape152-457" v:mID="152" v:groupContext="shape" transform="translate(31.8163,-236.094)">
+ <title>Sheet.152</title>
+ <path d="M0 362.73 C0 360.85 1.54 359.31 3.42 359.31 L68.49 359.31 C70.38 359.31 71.91 360.85 71.91 362.73 L71.91 376.39
+ C71.91 378.28 70.38 379.8 68.49 379.8 L3.42 379.8 C1.54 379.8 0 378.28 0 376.39 L0 362.73 Z" class="st4"/>
+ </g>
+ <g id="shape153-459" v:mID="153" v:groupContext="shape" transform="translate(31.8163,-236.094)">
+ <title>Sheet.153</title>
+ <path d="M0 362.73 C0 360.85 1.54 359.31 3.42 359.31 L68.49 359.31 C70.38 359.31 71.91 360.85 71.91 362.73 L71.91 376.39
+ C71.91 378.28 70.38 379.8 68.49 379.8 L3.42 379.8 C1.54 379.8 0 378.28 0 376.39 L0 362.73 Z" class="st21"/>
+ </g>
+ <g id="shape154-461" v:mID="154" v:groupContext="shape" transform="translate(54.6845,-237.332)">
+ <title>Sheet.154</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.8573" cy="372.612" width="33.72" height="14.3829"/>
+ <path d="M33.71 365.42 L0 365.42 L0 379.8 L33.71 379.8 L33.71 365.42" class="st2"/>
+ <text x="3.86" y="376.21" class="st22" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape155-465" v:mID="155" v:groupContext="shape" transform="translate(23.0522,-195.232)">
+ <title>Sheet.155</title>
+ <path d="M0 363.53 C0 361.73 1.46 360.27 3.26 360.27 L87.15 360.27 C88.95 360.27 90.4 361.73 90.4 363.53 L90.4 376.55
+ C90.4 378.35 88.95 379.8 87.15 379.8 L3.26 379.8 C1.46 379.8 0 378.35 0 376.55 L0 363.53 Z" class="st4"/>
+ </g>
+ <g id="shape156-467" v:mID="156" v:groupContext="shape" transform="translate(23.0522,-195.232)">
+ <title>Sheet.156</title>
+ <path d="M0 363.53 C0 361.73 1.46 360.27 3.26 360.27 L87.15 360.27 C88.95 360.27 90.4 361.73 90.4 363.53 L90.4 376.55
+ C90.4 378.35 88.95 379.8 87.15 379.8 L3.26 379.8 C1.46 379.8 0 378.35 0 376.55 L0 363.53 Z" class="st21"/>
+ </g>
+ <g id="shape157-469" v:mID="157" v:groupContext="shape" transform="translate(27,-196.017)">
+ <title>Sheet.157</title>
+ <desc>0x0102ABCD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.6644" cy="372.612" width="87.33" height="14.3829"/>
+ <path d="M87.33 365.42 L0 365.42 L0 379.8 L87.33 379.8 L87.33 365.42" class="st2"/>
+ <text x="7.36" y="376.21" class="st22" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102ABCD</text> </g>
+ <g id="shape158-473" v:mID="158" v:groupContext="shape" transform="translate(47.7236,-214.824)">
+ <title>Sheet.158</title>
+ <path d="M0 369.5 L9.81 369.5 L9.81 359.19 L29.44 359.19 L29.44 369.5 L39.26 369.5 L19.63 379.8 L0 369.5 Z"
+ class="st23"/>
+ </g>
+ <g id="shape159-475" v:mID="159" v:groupContext="shape" transform="translate(49.8539,-181.212)">
+ <title>Sheet.159</title>
+ <path d="M11.89 368.42 C11.89 371.57 11.47 374.11 10.94 374.11 L6.9 374.11 C6.37 374.11 5.94 376.67 5.94 379.8 C5.94
+ 376.67 5.52 374.11 5 374.11 L0.95 374.11 C0.43 374.11 0 371.57 0 368.42" class="st24"/>
+ </g>
+ <g id="shape160-478" v:mID="160" v:groupContext="shape" transform="translate(64.2606,-180.973)">
+ <title>Sheet.160</title>
+ <path d="M9.54 368.54 C9.54 371.66 9.21 374.17 8.79 374.17 L5.53 374.17 C5.11 374.17 4.77 376.7 4.77 379.8 C4.77 376.7
+ 4.43 374.17 4.02 374.17 L0.76 374.17 C0.34 374.17 0 371.66 0 368.54" class="st24"/>
+ </g>
+ <g id="shape161-481" v:mID="161" v:groupContext="shape" transform="translate(18.19,-60.9649)">
+ <title>Sheet.161</title>
+ <path d="M0 354.74 C0 351.97 2.25 349.73 5.03 349.73 L10.77 349.73 L30.27 267.14 L26.92 349.73 L59.58 349.73 C62.35 349.73
+ 64.59 351.97 64.59 354.74 L64.59 354.74 L64.59 362.26 L64.59 374.8 C64.59 377.57 62.35 379.8 59.58 379.8
+ L26.92 379.8 L10.77 379.8 L10.77 379.8 L5.03 379.8 C2.25 379.8 0 377.57 0 374.8 L0 362.26 L0 354.74 L0 354.74
+ Z" class="st23"/>
+ </g>
+ <g id="shape162-483" v:mID="162" v:groupContext="shape" transform="translate(28.141,-66.9569)">
+ <title>Sheet.162</title>
+ <desc>chunk id</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="27.5794" cy="372.612" width="55.16" height="14.3829"/>
+ <path d="M55.16 365.42 L0 365.42 L0 379.8 L55.16 379.8 L55.16 365.42" class="st2"/>
+ <text x="5.26" y="376.21" class="st25" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>chunk id</text> </g>
+ <g id="shape163-487" v:mID="163" v:groupContext="shape" transform="translate(50.8451,-112.132)">
+ <title>Sheet.163</title>
+ <path d="M0 354.64 C0 351.87 2.27 349.61 5.04 349.61 L10.74 349.61 L16.27 313.66 L26.86 349.61 L59.43 349.61 C62.22 349.61
+ 64.47 351.87 64.47 354.64 L64.47 354.64 L64.47 362.19 L64.47 374.77 C64.47 377.56 62.22 379.8 59.43 379.8
+ L26.86 379.8 L10.74 379.8 L10.74 379.8 L5.04 379.8 C2.27 379.8 0 377.56 0 374.77 L0 362.19 L0 354.64 L0
+ 354.64 Z" class="st23"/>
+ </g>
+ <g id="shape164-489" v:mID="164" v:groupContext="shape" transform="translate(68.8168,-118.181)">
+ <title>Sheet.164</title>
+ <desc>bin id</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="18.3881" cy="372.612" width="36.78" height="14.3829"/>
+ <path d="M36.78 365.42 L0 365.42 L0 379.8 L36.78 379.8 L36.78 365.42" class="st2"/>
+ <text x="4.06" y="376.21" class="st25" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bin id</text> </g>
+ <g id="shape165-493" v:mID="165" v:groupContext="shape" transform="translate(113.454,-225.085)">
+ <title>Sheet.165</title>
+ <path d="M0.01 375.68 L13.23 375.73 L13.22 377.77 L0 377.72 L0.01 375.68 L0.01 375.68 ZM12.22 373.69 L18.33 376.76 L12.2
+ 379.8 L12.22 373.69 L12.22 373.69 Z" class="st26"/>
+ </g>
+ <g id="shape166-495" v:mID="166" v:groupContext="shape" transform="translate(200.975,-280.969)">
+ <title>Sheet.166</title>
+ <path d="M0 375.73 L20.11 375.73 L20.11 377.77 L0 377.77 L0 375.73 L0 375.73 ZM19.09 373.69 L25.21 376.75 L19.09 379.8
+ L19.09 373.69 L19.09 373.69 Z" class="st26"/>
+ </g>
+ <g id="shape167-497" v:mID="167" v:groupContext="shape" transform="translate(275.739,-179.745)">
+ <title>Sheet.167</title>
+ <path d="M0.81 274.59 L231.38 376.48 L230.54 378.37 L0 276.48 L0.81 274.59 L0.81 274.59 ZM231.26 374.2 L235.64 379.47
+ L228.8 379.8 L231.26 374.2 L231.26 374.2 Z" class="st27"/>
+ </g>
+ <g id="shape168-499" v:mID="168" v:groupContext="shape" transform="translate(521.823,-96.8834)">
+ <title>Sheet.168</title>
+ <path d="M127.17 309.02 L127.17 378.79 C127.17 379.35 126.72 379.8 126.15 379.8 L3.06 379.8 C2.52 379.8 2.04 379.35 2.04
+ 378.79 L2.04 369.59 L4.08 369.59 L4.08 378.79 L3.06 377.77 L126.15 377.77 L125.13 378.79 L125.13 309.02
+ L127.17 309.02 ZM0 370.61 L3.06 364.5 L6.12 370.61 L0 370.61 Z" class="st28"/>
+ </g>
+ <g id="shape169-501" v:mID="169" v:groupContext="shape" transform="translate(478.603,-39.7553)">
+ <title>Sheet.169</title>
+ <path d="M0 347.57 C0 344.01 2.91 341.1 6.48 341.1 L237.86 341.1 C241.43 341.1 244.31 344.01 244.31 347.57 L244.31 373.36
+ C244.31 376.93 241.43 379.8 237.86 379.8 L6.48 379.8 C2.91 379.8 0 376.93 0 373.36 L0 347.57 Z"
+ class="st23"/>
+ </g>
+ <g id="shape170-503" v:mID="170" v:groupContext="shape" transform="translate(487.717,-45.5378)">
+ <title>Sheet.170</title>
+ <desc>Move bin from group 1 to 4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="126.387" cy="369.018" width="252.78" height="21.5726"/>
+ <path d="M252.77 358.23 L0 358.23 L0 379.8 L252.77 379.8 L252.77 358.23" class="st2"/>
+ <text x="18.98" y="374.41" class="st29" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Move bin from group 1 to 4</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i2.svg b/doc/guides/prog_guide/img/efd_i2.svg
new file mode 100644
index 0000000..a5f43f9
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i2.svg
@@ -0,0 +1,280 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i2.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="2.85156in" height="2.98777in"
+ viewBox="0 0 205.313 215.12" xml:space="preserve" color-interpolation-filters="sRGB" class="st18">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#deebf6;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st5 {fill:#ff0000;stroke:#c7c8c8;stroke-width:0.25}
+ .st6 {fill:none;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st7 {fill:none;stroke:#0070c0;stroke-width:1.5}
+ .st8 {marker-end:url(#mrkr5-91);stroke:#0070c0;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st9 {fill:#0070c0;fill-opacity:1;stroke:#0070c0;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st10 {fill:none;stroke:none;stroke-width:0.25}
+ .st11 {fill:#ff0000;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st12 {font-size:1em}
+ .st13 {marker-end:url(#mrkr5-101);stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st14 {fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st15 {fill:#5b9bd5;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st16 {marker-end:url(#mrkr5-110);stroke:#41719c;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st17 {fill:#41719c;fill-opacity:1;stroke:#41719c;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st18 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-91" class="st9" v:arrowType="5" v:arrowSize="2" v:setback="4.45" refX="-4.45" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ <marker id="mrkr5-101" class="st14" v:arrowType="5" v:arrowSize="2" v:setback="6.16" refX="-6.16" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ <marker id="mrkr5-110" class="st17" v:arrowType="5" v:arrowSize="2" v:setback="6.16" refX="-6.16" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(24.4044,-42.7174)">
+ <title>Circle</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow2-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 138.62 A76.5 76.5 0 0 1 153 138.62 A76.5 76.5 0 1 1 0 138.62 Z" class="st2"/>
+ </g>
+ <path d="M0 138.62 A76.5 76.5 0 0 1 153 138.62 A76.5 76.5 0 1 1 0 138.62 Z" class="st3"/>
+ </g>
+ <g id="shape3-6" v:mID="3" v:groupContext="shape" transform="translate(24.4044,-144.53)">
+ <title>Circle.3</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow3-7" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape4-11" v:mID="4" v:groupContext="shape" transform="translate(21.0294,-102.342)">
+ <title>Circle.4</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow4-12" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape5-16" v:mID="5" v:groupContext="shape" transform="translate(69.4044,-183.342)">
+ <title>Circle.5</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow5-17" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape6-21" v:mID="6" v:groupContext="shape" transform="translate(117.217,-183.342)">
+ <title>Circle.6</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow6-22" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st5"/>
+ </g>
+ <g id="shape7-26" v:mID="7" v:groupContext="shape" transform="translate(171.217,-104.03)">
+ <title>Circle.7</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow7-27" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st5"/>
+ </g>
+ <g id="shape8-31" v:mID="8" v:groupContext="shape" transform="translate(109.904,-38.2174)">
+ <title>Circle.8</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow8-32" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st5"/>
+ </g>
+ <g id="shape9-36" v:mID="9" v:groupContext="shape" transform="translate(21.0294,-124.842)">
+ <title>Circle.9</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow9-37" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st5"/>
+ </g>
+ <g id="shape10-41" v:mID="10" v:groupContext="shape" transform="translate(147.029,-168.717)">
+ <title>Circle.10</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow10-42" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape11-46" v:mID="11" v:groupContext="shape" transform="translate(138.029,-48.3424)">
+ <title>Circle.11</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow11-47" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape12-51" v:mID="12" v:groupContext="shape" transform="translate(160.529,-74.2174)">
+ <title>Circle.12</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow12-52" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape13-56" v:mID="13" v:groupContext="shape" transform="translate(40.7169,-57.3424)">
+ <title>Circle.13</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow13-57" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape14-61" v:mID="14" v:groupContext="shape" transform="translate(42.4044,-168.717)">
+ <title>Circle.14</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow14-62" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape15-66" v:mID="15" v:groupContext="shape" transform="translate(66.0294,-42.7174)">
+ <title>Circle.15</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow15-67" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape16-71" v:mID="16" v:groupContext="shape" transform="translate(25.5294,-79.8424)">
+ <title>Circle.16</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow16-72" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape17-76" v:mID="17" v:groupContext="shape" transform="translate(165.029,-143.405)">
+ <title>Circle.17</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow17-77" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape18-81" v:mID="18" v:groupContext="shape" transform="translate(276.618,4.50201) rotate(45)">
+ <title>Ellipse</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow18-82" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,1.63935,1.1506)" class="st1">
+ <path d="M0 187.01 A14.7383 28.1086 0 1 1 29.48 187.01 A14.7383 28.1086 0 1 1 0 187.01 Z" class="st6"/>
+ </g>
+ <path d="M0 187.01 A14.7383 28.1086 0 1 1 29.48 187.01 A14.7383 28.1086 0 1 1 0 187.01 Z" class="st7"/>
+ </g>
+ <g id="shape19-86" v:mID="19" v:groupContext="shape" transform="translate(251.273,355.436) rotate(156.038)">
+ <title>Sheet.19</title>
+ <path d="M-0 215.12 A73.4538 31.2572 85.43 0 1 40.92 208.96 L41.1 209.27" class="st8"/>
+ </g>
+ <g id="shape20-92" v:mID="20" v:groupContext="shape" transform="translate(62.705,-78.7174)">
+ <title>Sheet.20</title>
+ <desc>Target Hashed Value</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="42.6994" cy="203.87" width="85.4" height="22.5"/>
+ <rect x="0" y="192.62" width="85.3987" height="22.5" class="st10"/>
+ <text x="6.73" y="200.27" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target Hashed <tspan
+ x="28.48" dy="1.2em" class="st12">Value</tspan></text> </g>
+ <g id="shape21-96" v:mID="21" v:groupContext="shape" transform="translate(314.101,88.728) rotate(75.9638)">
+ <title>Sheet.21</title>
+ <path d="M0 215.12 L16.92 215.12" class="st13"/>
+ </g>
+ <g id="shape23-102" v:mID="23" v:groupContext="shape" transform="translate(60.4044,-138.342)">
+ <title>Sheet.23</title>
+ <desc>Keys</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="24.75" cy="203.87" width="49.5" height="22.5"/>
+ <rect x="0" y="192.62" width="49.5" height="22.5" class="st10"/>
+ <text x="13.21" y="207.47" class="st15" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Keys</text> </g>
+ <g id="shape24-105" v:mID="24" v:groupContext="shape" transform="translate(-125.293,114.034) rotate(-104.574)">
+ <title>Sheet.24</title>
+ <path d="M0 215.12 L22.9 215.12" class="st16"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i3.svg b/doc/guides/prog_guide/img/efd_i3.svg
new file mode 100644
index 0000000..ae22903
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i3.svg
@@ -0,0 +1,634 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i3.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="6.56036in" height="5.44284in"
+ viewBox="0 0 472.346 391.884" xml:space="preserve" color-interpolation-filters="sRGB" class="st22">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false"/>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st5 {marker-end:url(#mrkr5-24);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st6 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st7 {fill:none;stroke:#2e75b5;stroke-width:1}
+ .st8 {fill:#5b9bd5;font-family:Calibri;font-size:1.00001em}
+ .st9 {font-size:1em}
+ .st10 {fill:none;stroke:none;stroke-width:1}
+ .st11 {fill:#feffff;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st12 {fill:#5b9bd5;fill-opacity:0.25;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.25}
+ .st13 {fill:#4f87bb;stroke:#40709c;stroke-width:0.75}
+ .st14 {fill:#feffff;font-family:Calibri;font-size:0.75em}
+ .st15 {fill:none;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st16 {fill:none;stroke:#2e75b5;stroke-width:2.25}
+ .st17 {stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st18 {fill:#305497;stroke:#2e75b5;stroke-width:1}
+ .st19 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:none}
+ .st20 {fill:#92d050;fill-opacity:0.3;stroke:none;stroke-width:0.25}
+ .st21 {fill:#feffff;font-family:Calibri;font-size:1.16666em}
+ .st22 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-24" class="st6" v:arrowType="5" v:arrowSize="2" v:setback="6.16" refX="-6.16" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <v:layer v:name="Connector" v:index="0"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(111.25,-354.482)">
+ <title>Rectangle</title>
+ <desc>Packet Header</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="42.75" cy="382.884" width="85.5" height="18"/>
+ <g id="shadow2-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="85.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="85.5" height="18" class="st3"/>
+ <text x="13.24" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Packet Header</text> </g>
+ <g id="shape3-7" v:mID="3" v:groupContext="shape" transform="translate(192.25,-354.482)">
+ <title>Rectangle.3</title>
+ <desc>Payload</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="382.884" width="108" height="18"/>
+ <g id="shadow3-8" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="108" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="108" height="18" class="st3"/>
+ <text x="37.95" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Payload</text> </g>
+ <g id="shape4-13" v:mID="4" v:groupContext="shape" transform="translate(136,-311.232)">
+ <title>Rectangle.4</title>
+ <desc>Flow Key</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="382.884" width="54" height="18"/>
+ <g id="shadow4-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="54" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="54" height="18" class="st3"/>
+ <text x="8.87" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Flow Key</text> </g>
+ <g id="shape5-19" v:mID="5" v:groupContext="shape" transform="translate(465.501,-160.057) rotate(59.7436)">
+ <title>Sheet.5</title>
+ <path d="M0 391.88 L25.1 391.88" class="st5"/>
+ </g>
+ <g id="shape8-25" v:mID="8" v:groupContext="shape" transform="translate(219.25,-320.169)">
+ <title>Sheet.8</title>
+ <desc>Fields of the packet are used to form a flow Key</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="377.822" width="135" height="28.125"/>
+ <rect x="0" y="363.759" width="135" height="28.125" class="st7"/>
+ <text x="10.7" y="374.22" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Fields of the packet are <tspan
+ x="9.67" dy="1.2em" class="st9">used to form a flow Key</tspan></text> </g>
+ <g id="group13-29" transform="translate(120.25,-266.897)" v:mID="13" v:groupContext="group">
+ <title>Sheet.13</title>
+ <g id="shape11-30" v:mID="11" v:groupContext="shape" transform="translate(85.5,751.143) rotate(180)">
+ <title>Trapezoid</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow11-31" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,-0.345598,-1.97279)" class="st1">
+ <path d="M0 391.88 L85.5 391.88 L60.19 359.26 L25.31 359.26 L0 391.88 Z" class="st2"/>
+ </g>
+ <path d="M0 391.88 L85.5 391.88 L60.19 359.26 L25.31 359.26 L0 391.88 Z" class="st3"/>
+ </g>
+ <g id="shape12-35" v:mID="12" v:groupContext="shape" transform="translate(13.5,-6.525)">
+ <title>Sheet.12</title>
+ <desc>H(..)</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="381.689" width="54" height="20.3906"/>
+ <rect x="0" y="371.494" width="54" height="20.3906" class="st10"/>
+ <text x="16.27" y="385.29" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H(..)</text> </g>
+ </g>
+ <g id="shape14-38" v:mID="14" v:groupContext="shape" transform="translate(-229.872,96.3648) rotate(-90.0429)">
+ <title>Simple Arrow</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ArrowType" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <g id="shadow14-39" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,-1.97305,0.344122)" class="st1">
+ <path d="M0 391.88 L10.18 387.38 L10.18 389.63 L16.71 389.63 L16.71 391.88 L16.71 394.13 L10.18 394.13 L10.18 396.38
+ L0 391.88 Z" class="st12"/>
+ </g>
+ <path d="M0 391.88 L10.18 387.38 L10.18 389.63 L16.71 389.63 L16.71 391.88 L16.71 394.13 L10.18 394.13 L10.18 396.38
+ L0 391.88 Z" class="st13"/>
+ </g>
+ <g id="shape15-43" v:mID="15" v:groupContext="shape" transform="translate(212.5,-271.46)">
+ <title>Sheet.15</title>
+ <desc>Hash function is used to create a flow table index</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="377.822" width="135" height="28.125"/>
+ <rect x="0" y="363.759" width="135" height="28.125" class="st7"/>
+ <text x="9.05" y="374.22" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Hash function is used to <tspan
+ x="7.39" dy="1.2em" class="st9">create a flow table index</tspan></text> </g>
+ <g id="shape58-47" v:mID="58" v:groupContext="shape" transform="translate(199,-221.397)">
+ <title>Rectangle.58</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow58-48" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape59-53" v:mID="59" v:groupContext="shape" transform="translate(232.75,-221.397)">
+ <title>Rectangle.59</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow59-54" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape60-59" v:mID="60" v:groupContext="shape" transform="translate(280,-221.397)">
+ <title>Rectangle.60</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow60-60" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape61-65" v:mID="61" v:groupContext="shape" transform="translate(313.75,-221.397)">
+ <title>Rectangle.61</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow61-66" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape62-71" v:mID="62" v:groupContext="shape" transform="translate(361,-221.397)">
+ <title>Rectangle.62</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow62-72" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape63-76" v:mID="63" v:groupContext="shape" transform="translate(394.75,-221.397)">
+ <title>Rectangle.63</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow63-77" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape64-81" v:mID="64" v:groupContext="shape" transform="translate(199,-198.897)">
+ <title>Rectangle.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow64-82" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape65-86" v:mID="65" v:groupContext="shape" transform="translate(232.75,-198.897)">
+ <title>Rectangle.65</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow65-87" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape66-91" v:mID="66" v:groupContext="shape" transform="translate(280,-198.897)">
+ <title>Rectangle.66</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow66-92" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape67-96" v:mID="67" v:groupContext="shape" transform="translate(313.75,-198.897)">
+ <title>Rectangle.67</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow67-97" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape68-101" v:mID="68" v:groupContext="shape" transform="translate(361,-198.897)">
+ <title>Rectangle.68</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow68-102" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape69-106" v:mID="69" v:groupContext="shape" transform="translate(394.75,-198.897)">
+ <title>Rectangle.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow69-107" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape70-111" v:mID="70" v:groupContext="shape" transform="translate(199,-162.897)">
+ <title>Rectangle.70</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow70-112" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape71-117" v:mID="71" v:groupContext="shape" transform="translate(232.75,-162.897)">
+ <title>Rectangle.71</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow71-118" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape72-123" v:mID="72" v:groupContext="shape" transform="translate(280,-162.897)">
+ <title>Rectangle.72</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow72-124" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape73-129" v:mID="73" v:groupContext="shape" transform="translate(313.75,-162.897)">
+ <title>Rectangle.73</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow73-130" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape74-135" v:mID="74" v:groupContext="shape" transform="translate(361,-162.897)">
+ <title>Rectangle.74</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow74-136" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape75-141" v:mID="75" v:groupContext="shape" transform="translate(394.75,-162.897)">
+ <title>Rectangle.75</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow75-142" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape76-147" v:mID="76" v:groupContext="shape" transform="translate(199,-126.397)">
+ <title>Rectangle.76</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow76-148" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape77-152" v:mID="77" v:groupContext="shape" transform="translate(232.75,-126.397)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow77-153" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape78-157" v:mID="78" v:groupContext="shape" transform="translate(280,-126.397)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow78-158" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape79-162" v:mID="79" v:groupContext="shape" transform="translate(313.75,-126.397)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow79-163" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape80-167" v:mID="80" v:groupContext="shape" transform="translate(361,-126.397)">
+ <title>Rectangle.80</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow80-168" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="385.58" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape81-173" v:mID="81" v:groupContext="shape" transform="translate(394.75,-126.397)">
+ <title>Rectangle.81</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow81-174" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="385.58" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape82-179" v:mID="82" v:groupContext="shape" transform="translate(196.75,-117.397)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow82-180" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="256.384" width="245.25" height="135.5" class="st15"/>
+ </g>
+ <rect x="0" y="256.384" width="245.25" height="135.5" class="st16"/>
+ </g>
+ <g id="shape83-184" v:mID="83" v:groupContext="shape" transform="translate(554.884,123.862) rotate(90)">
+ <title>Sheet.83</title>
+ <path d="M0 391.88 L99 391.88" class="st17"/>
+ </g>
+ <g id="shape84-187" v:mID="84" v:groupContext="shape" transform="translate(208,-248.397)">
+ <title>Sheet.84</title>
+ <desc>Load Balancing Flow Table</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="91.75" cy="386.259" width="183.5" height="11.25"/>
+ <rect x="0" y="380.634" width="183.5" height="11.25" class="st18"/>
+ <text x="26.14" y="389.86" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Load Balancing Flow Table</text> </g>
+ <g id="shape85-190" v:mID="85" v:groupContext="shape" transform="translate(190,-157.835)">
+ <title>Rectangle.85</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow85-191" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="363.759" width="261" height="28.125" class="st19"/>
+ </g>
+ <rect x="0" y="363.759" width="261" height="28.125" class="st20"/>
+ </g>
+ <g id="shape86-195" v:mID="86" v:groupContext="shape" transform="translate(163,-169.022)">
+ <title>Sheet.86</title>
+ <path d="M0 391.88 L18.76 391.88" class="st5"/>
+ </g>
+ <g id="shape87-200" v:mID="87" v:groupContext="shape" transform="translate(19,-198.107)">
+ <title>Sheet.87</title>
+ <desc>Hash value used to index Flow table</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="377.822" width="135" height="28.125"/>
+ <rect x="0" y="363.759" width="135" height="28.125" class="st7"/>
+ <text x="6.79" y="374.22" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Hash value used to index <tspan
+ x="42.16" dy="1.2em" class="st9">Flow table</tspan></text> </g>
+ <g id="shape88-204" v:mID="88" v:groupContext="shape" transform="translate(551.381,21.2928) rotate(87.9001)">
+ <title>Sheet.88</title>
+ <path d="M0 391.88 L20.86 391.88" class="st5"/>
+ </g>
+ <g id="shape89-209" v:mID="89" v:groupContext="shape" transform="translate(494.785,297.309) rotate(131.987)">
+ <title>Sheet.89</title>
+ <path d="M0 391.88 L30.84 391.88" class="st5"/>
+ </g>
+ <g id="shape90-214" v:mID="90" v:groupContext="shape" transform="translate(228.25,-92.5847)">
+ <title>Rectangle.90</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow90-215" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape91-220" v:mID="91" v:groupContext="shape" transform="translate(340.75,-92.5847)">
+ <title>Rectangle.91</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow91-221" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="group96-226" transform="translate(253,-51.4597)" v:mID="96" v:groupContext="group">
+ <title>Sheet.96</title>
+ <g id="shape97-227" v:mID="97" v:groupContext="shape" transform="translate(85.5,751.143) rotate(180)">
+ <title>Trapezoid</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow97-228" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,-0.345598,-1.97279)" class="st1">
+ <path d="M0 391.88 L85.5 391.88 L60.19 359.26 L25.31 359.26 L0 391.88 Z" class="st2"/>
+ </g>
+ <path d="M0 391.88 L85.5 391.88 L60.19 359.26 L25.31 359.26 L0 391.88 Z" class="st3"/>
+ </g>
+ <g id="shape98-232" v:mID="98" v:groupContext="shape" transform="translate(13.5,-6.525)">
+ <title>Sheet.98</title>
+ <desc>Match</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="381.689" width="54" height="20.3906"/>
+ <rect x="0" y="371.494" width="54" height="20.3906" class="st10"/>
+ <text x="10.98" y="385.29" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Match</text> </g>
+ </g>
+ <g id="shape99-235" v:mID="99" v:groupContext="shape" transform="translate(532.137,0.00916548) rotate(54.6508)">
+ <title>Sheet.99</title>
+ <path d="M0 391.88 L93.23 391.88" class="st5"/>
+ </g>
+ <g id="shape100-240" v:mID="100" v:groupContext="shape" transform="translate(683.134,224.487) rotate(90)">
+ <title>Sheet.100</title>
+ <path d="M0 391.88 L77.15 391.88" class="st5"/>
+ </g>
+ <g id="shape101-245" v:mID="101" v:groupContext="shape" transform="translate(692.213,476.024) rotate(129.078)">
+ <title>Sheet.101</title>
+ <path d="M0 391.88 L95.37 391.88" class="st5"/>
+ </g>
+ <g id="shape102-250" v:mID="102" v:groupContext="shape" transform="translate(293.5,-97.0847)">
+ <title>Rectangle.102</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow102-251" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape103-256" v:mID="103" v:groupContext="shape" transform="translate(169.75,-55.9597)">
+ <title>Rectangle.103</title>
+ <desc>Flow Key</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="382.884" width="54" height="18"/>
+ <g id="shadow103-257" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="54" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="54" height="18" class="st3"/>
+ <text x="8.87" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Flow Key</text> </g>
+ <g id="shape104-262" v:mID="104" v:groupContext="shape" transform="translate(226,-64.9597)">
+ <title>Sheet.104</title>
+ <path d="M0 391.88 L34.34 391.88" class="st5"/>
+ </g>
+ <g id="shape105-267" v:mID="105" v:groupContext="shape" transform="translate(54,-82.4597)">
+ <title>Sheet.105</title>
+ <desc>Retrieved keys are matched with input key</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="377.822" width="135" height="28.125"/>
+ <rect x="0" y="363.759" width="135" height="28.125" class="st7"/>
+ <text x="22.51" y="374.22" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Retrieved keys are <tspan
+ x="9.83" dy="1.2em" class="st9">matched with input key</tspan></text> </g>
+ <g id="shape106-271" v:mID="106" v:groupContext="shape" transform="translate(271,-23.9597)">
+ <title>Rectangle.106</title>
+ <desc>Action</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="382.884" width="54" height="18"/>
+ <g id="shadow106-272" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="54" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="54" height="18" class="st3"/>
+ <text x="8.67" y="387.08" class="st21" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action</text> </g>
+ <g id="shape111-277" v:mID="111" v:groupContext="shape" transform="translate(-94.8716,350.902) rotate(-90.0429)">
+ <title>Simple Arrow.111</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ArrowType" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <g id="shadow111-278" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,-1.97305,0.344122)" class="st1">
+ <path d="M0 391.88 L10.18 387.38 L10.18 389.63 L16.71 389.63 L16.71 391.88 L16.71 394.13 L10.18 394.13 L10.18 396.38
+ L0 391.88 Z" class="st12"/>
+ </g>
+ <path d="M0 391.88 L10.18 387.38 L10.18 389.63 L16.71 389.63 L16.71 391.88 L16.71 394.13 L10.18 394.13 L10.18 396.38
+ L0 391.88 Z" class="st13"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i4.svg b/doc/guides/prog_guide/img/efd_i4.svg
new file mode 100644
index 0000000..5be5ccd
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i4.svg
@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i4.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="2.78993in" height="1.78151in"
+ viewBox="0 0 200.875 128.269" xml:space="preserve" color-interpolation-filters="sRGB" class="st19">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:none;stroke:none;stroke-width:0.25}
+ .st2 {fill:#5b9bd5;font-family:Calibri;font-size:0.75em}
+ .st3 {font-size:1em}
+ .st4 {fill:#5b9bd5;font-family:Calibri;font-size:0.75em;font-weight:bold}
+ .st5 {fill:#deebf6;stroke:none;stroke-width:0.25}
+ .st6 {stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st7 {stroke:#5b9bd5;stroke-dasharray:0.75,1.5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st8 {fill:#ff0000;font-size:1em}
+ .st9 {baseline-shift:-28.8834%;font-size:0.577667em}
+ .st10 {fill:#ff0000;font-family:Calibri;font-size:0.75em}
+ .st11 {fill:#5b9bd5;font-size:1em}
+ .st12 {visibility:visible}
+ .st13 {fill:#5b9bd5;fill-opacity:0.25;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.25}
+ .st14 {fill:url(#grad0-73);stroke:#40709c;stroke-width:0.75}
+ .st15 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st16 {fill:#00fefe;font-size:1em}
+ .st17 {fill:#00b050}
+ .st18 {stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st19 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Patterns_And_Gradients">
+ <linearGradient id="grad0-73" x1="0" y1="0" x2="1" y2="0" gradientTransform="rotate(250 0.5 0.5)">
+ <stop offset="0" stop-color="#4f87bb" stop-opacity="1"/>
+ <stop offset="0.48" stop-color="#4f87bb" stop-opacity="1"/>
+ <stop offset="0.82" stop-color="#5b9bd5" stop-opacity="1"/>
+ </linearGradient>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(18.25,-59.3478)">
+ <title>Sheet.2</title>
+ <desc>Key 1 Key 2 ... Key 28</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="18" cy="121.519" width="36" height="13.5"/>
+ <rect x="0" y="114.769" width="36" height="13.5" class="st1"/>
+ <text x="8.09" y="108.02" class="st2" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1<v:newlineChar/><tspan
+ x="8.09" dy="1.2em" class="st3">Key </tspan>2<v:newlineChar/><tspan x="14.59" dy="1.2em" class="st3">...<v:newlineChar/></tspan><tspan
+ x="5.81" dy="1.2em" class="st3">Key </tspan>28</text> </g>
+ <g id="shape9-7" v:mID="9" v:groupContext="shape" transform="translate(52,-91.9728)">
+ <title>Sheet.9</title>
+ <desc>Target Value</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="17.4375" cy="122.644" width="34.88" height="11.25"/>
+ <rect x="0" y="117.019" width="34.875" height="11.25" class="st1"/>
+ <text x="5.43" y="119.94" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target <tspan x="6.77"
+ dy="1.2em" class="st3">Value</tspan></text> </g>
+ <g id="shape11-11" v:mID="11" v:groupContext="shape" transform="translate(52,-42.4728)">
+ <title>Sheet.11</title>
+ <desc>0 1 0</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="17.4375" cy="105.769" width="34.88" height="45"/>
+ <rect x="0" y="83.2689" width="34.875" height="45" class="st5"/>
+ <text x="15.16" y="92.27" class="st2" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0<v:newlineChar/><tspan
+ x="15.16" dy="1.2em" class="st3">1<v:newlineChar/><v:newlineChar/></tspan><tspan x="15.16" dy="2.4em"
+ class="st3">0</tspan></text> </g>
+ <g id="shape8-16" v:mID="8" v:groupContext="shape" transform="translate(180.269,21.6711) rotate(90)">
+ <title>Sheet.8</title>
+ <path d="M0 128.27 L69.75 128.27" class="st6"/>
+ </g>
+ <g id="shape10-19" v:mID="10" v:groupContext="shape" transform="translate(215.144,21.6711) rotate(90)">
+ <title>Sheet.10</title>
+ <path d="M0 128.27 L69.75 128.27" class="st6"/>
+ </g>
+ <g id="shape4-22" v:mID="4" v:groupContext="shape" transform="translate(22.75,-77.3478)">
+ <title>Sheet.4</title>
+ <path d="M0 128.27 L157.5 128.27" class="st7"/>
+ </g>
+ <g id="shape5-25" v:mID="5" v:groupContext="shape" transform="translate(23.875,-66.0978)">
+ <title>Sheet.5</title>
+ <path d="M0 128.27 L158.62 128.27" class="st7"/>
+ </g>
+ <g id="shape6-28" v:mID="6" v:groupContext="shape" transform="translate(22.75,-54.8478)">
+ <title>Sheet.6</title>
+ <path d="M0 128.27 L159.75 128.27" class="st7"/>
+ </g>
+ <g id="shape7-31" v:mID="7" v:groupContext="shape" transform="translate(22.75,-87.4728)">
+ <title>Sheet.7</title>
+ <path d="M0 128.27 L155.25 128.27" class="st6"/>
+ </g>
+ <g id="shape12-34" v:mID="12" v:groupContext="shape" transform="translate(91.9375,-42.4728)">
+ <title>Sheet.12</title>
+ <desc>0 0 0</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="8.4375" cy="105.769" width="16.88" height="45"/>
+ <rect x="0" y="83.2689" width="16.875" height="45" class="st1"/>
+ <text x="6.16" y="92.27" class="st2" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0<v:newlineChar/><tspan
+ x="6.16" dy="1.2em" class="st8">0<v:newlineChar/><v:newlineChar/></tspan><tspan x="6.16" dy="2.4em"
+ class="st3">0</tspan></text> </g>
+ <g id="shape26-39" v:mID="26" v:groupContext="shape" transform="translate(86.875,-88.5978)">
+ <title>Sheet.26</title>
+ <desc>H1(x)</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="14.0625" cy="122.644" width="28.13" height="11.25"/>
+ <rect x="0" y="117.019" width="28.125" height="11.25" class="st1"/>
+ <text x="5.03" y="125.34" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st9" v:baseFontSize="8">1</tspan><tspan dy="0.164em" class="st3">(</tspan>x)</text> </g>
+ <g id="shape27-44" v:mID="27" v:groupContext="shape" transform="translate(115,-42.4728)">
+ <title>Sheet.27</title>
+ <desc>1 1 0</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="8.4375" cy="105.769" width="16.88" height="45"/>
+ <rect x="0" y="83.2689" width="16.875" height="45" class="st1"/>
+ <text x="6.16" y="92.27" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1<v:newlineChar/><tspan
+ x="6.16" dy="1.2em" class="st11">1<v:newlineChar/><v:newlineChar/></tspan><tspan x="6.16" dy="2.4em"
+ class="st11">0</tspan></text> </g>
+ <g id="shape28-49" v:mID="28" v:groupContext="shape" transform="translate(109.938,-88.5978)">
+ <title>Sheet.28</title>
+ <desc>H2(x)</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="14.0625" cy="122.644" width="28.13" height="11.25"/>
+ <rect x="0" y="117.019" width="28.125" height="11.25" class="st1"/>
+ <text x="5.03" y="125.34" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st9" v:baseFontSize="8">2</tspan><tspan dy="0.164em" class="st3">(</tspan>x)</text> </g>
+ <g id="shape29-54" v:mID="29" v:groupContext="shape" transform="translate(155.5,-42.4728)">
+ <title>Sheet.29</title>
+ <desc>0 1 0</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="8.4375" cy="105.769" width="16.88" height="45"/>
+ <rect x="0" y="83.2689" width="16.875" height="45" class="st1"/>
+ <text x="6.16" y="92.27" class="st2" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0<v:newlineChar/><tspan
+ x="6.16" dy="1.2em" class="st3">1<v:newlineChar/><v:newlineChar/></tspan><tspan x="6.16" dy="2.4em"
+ class="st3">0</tspan></text> </g>
+ <g id="shape30-59" v:mID="30" v:groupContext="shape" transform="translate(150.438,-88.5978)">
+ <title>Sheet.30</title>
+ <desc>Hm(x)</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="14.0625" cy="122.644" width="28.13" height="11.25"/>
+ <rect x="0" y="117.019" width="28.125" height="11.25" class="st1"/>
+ <text x="4.24" y="125.34" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st9" v:baseFontSize="8">m</tspan><tspan dy="0.164em" class="st3">(</tspan>x)</text> </g>
+ <g id="shape31-64" v:mID="31" v:groupContext="shape" transform="translate(130.188,-89.7228)">
+ <title>Sheet.31</title>
+ <desc>…..</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="14.0625" cy="122.644" width="28.13" height="11.25"/>
+ <rect x="0" y="117.019" width="28.125" height="11.25" class="st1"/>
+ <text x="8.46" y="125.34" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>…..</text> </g>
+ <g id="shape32-67" v:mID="32" v:groupContext="shape" transform="translate(34,-23.3478)">
+ <title>Sheet.32</title>
+ <desc>Store m for this group of keys</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="66.375" cy="122.644" width="132.75" height="11.25"/>
+ <g id="shadow32-68" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st12">
+ <rect x="0" y="117.019" width="132.75" height="11.25" class="st13"/>
+ </g>
+ <rect x="0" y="117.019" width="132.75" height="11.25" class="st14"/>
+ <text x="6.32" y="125.64" class="st15" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Store <tspan
+ class="st16">m</tspan> for this group of keys</text> </g>
+ <g id="shape36-76" v:mID="36" v:groupContext="shape" transform="translate(159.381,-100.964)">
+ <title>Sheet.36</title>
+ <path d="M3.45 125.81 L6.87 119.34 L7.99 120.16 L3.87 128.27 L0 124.35 L0.86 123.13 L3.45 125.81 Z" class="st17"/>
+ </g>
+ <g id="group44-79" transform="translate(97.5625,-100.086)" v:mID="44" v:groupContext="group">
+ <title>Sheet.44</title>
+ <g id="shape42-80" v:mID="42" v:groupContext="shape" transform="translate(85.4972,28.6255) rotate(41.8011)">
+ <title>Sheet.42</title>
+ <path d="M0 128.27 L6.04 128.27" class="st18"/>
+ </g>
+ <g id="shape43-83" v:mID="43" v:groupContext="shape" transform="translate(-87.9035,34.8564) rotate(-43.2597)">
+ <title>Sheet.43</title>
+ <path d="M0 128.27 L5.87 128.27" class="st18"/>
+ </g>
+ </g>
+ <g id="group45-86" transform="translate(120.625,-100.086)" v:mID="45" v:groupContext="group">
+ <title>Sheet.45</title>
+ <g id="shape46-87" v:mID="46" v:groupContext="shape" transform="translate(85.4972,28.6255) rotate(41.8011)">
+ <title>Sheet.46</title>
+ <path d="M0 128.27 L6.04 128.27" class="st18"/>
+ </g>
+ <g id="shape47-90" v:mID="47" v:groupContext="shape" transform="translate(-87.9035,34.8564) rotate(-43.2597)">
+ <title>Sheet.47</title>
+ <path d="M0 128.27 L5.87 128.27" class="st18"/>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i5.svg b/doc/guides/prog_guide/img/efd_i5.svg
new file mode 100644
index 0000000..b6540ba
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i5.svg
@@ -0,0 +1,183 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i5.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="8.34375in" height="2.86443in"
+ viewBox="0 0 600.75 206.239" xml:space="preserve" color-interpolation-filters="sRGB" class="st14">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:1.5em}
+ .st5 {fill:#feffff;font-family:Calibri;font-size:1.16666em}
+ .st6 {marker-end:url(#mrkr5-36);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st7 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st8 {stroke:#5b9bd5;stroke-dasharray:1.5,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st9 {fill:none;stroke:none;stroke-width:0.25}
+ .st10 {fill:#5b9bd5;font-family:Calibri;font-size:1.5em;font-weight:bold}
+ .st11 {baseline-shift:-32.4951%;font-size:0.649902em}
+ .st12 {fill:#deebf6;stroke:#0070c0;stroke-width:1}
+ .st13 {fill:#5b9bd5;font-family:Calibri;font-size:1.5em}
+ .st14 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-36" class="st7" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(93.0294,-158.5)">
+ <title>Rectangle</title>
+ <desc>All Keys</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="216" cy="192.739" width="432" height="27"/>
+ <g id="shadow2-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="179.239" width="432" height="27" class="st2"/>
+ </g>
+ <rect x="0" y="179.239" width="432" height="27" class="st3"/>
+ <text x="187.88" y="198.14" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>All Keys</text> </g>
+ <g id="shape3-7" v:mID="3" v:groupContext="shape" transform="translate(21.0294,-77.5)">
+ <title>Rectangle.3</title>
+ <desc>Group 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="188.239" width="108" height="36"/>
+ <g id="shadow3-8" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="170.239" width="108" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="170.239" width="108" height="36" class="st3"/>
+ <text x="30.97" y="192.44" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group 1</text> </g>
+ <g id="shape4-13" v:mID="4" v:groupContext="shape" transform="translate(156.029,-77.5)">
+ <title>Rectangle.4</title>
+ <desc>Group 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="188.239" width="108" height="36"/>
+ <g id="shadow4-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="170.239" width="108" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="170.239" width="108" height="36" class="st3"/>
+ <text x="30.97" y="192.44" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group 2</text> </g>
+ <g id="shape5-19" v:mID="5" v:groupContext="shape" transform="translate(291.029,-77.5)">
+ <title>Rectangle.5</title>
+ <desc>Group 3</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="188.239" width="108" height="36"/>
+ <g id="shadow5-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="170.239" width="108" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="170.239" width="108" height="36" class="st3"/>
+ <text x="30.97" y="192.44" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group 3</text> </g>
+ <g id="shape6-25" v:mID="6" v:groupContext="shape" transform="translate(471.029,-77.5)">
+ <title>Rectangle.6</title>
+ <desc>Group X</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="188.239" width="108" height="36"/>
+ <g id="shadow6-26" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="170.239" width="108" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="170.239" width="108" height="36" class="st3"/>
+ <text x="30.88" y="192.44" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group X</text> </g>
+ <g id="shape7-31" v:mID="7" v:groupContext="shape" transform="translate(359.05,247.819) rotate(165.964)">
+ <title>Sheet.7</title>
+ <path d="M0 206.24 L178.5 206.24" class="st6"/>
+ </g>
+ <g id="shape8-37" v:mID="8" v:groupContext="shape" transform="translate(428.903,215.562) rotate(144.462)">
+ <title>Sheet.8</title>
+ <path d="M0 206.24 L70.39 206.24" class="st6"/>
+ </g>
+ <g id="shape9-42" v:mID="9" v:groupContext="shape" transform="translate(470.075,-81.0976) rotate(51.3402)">
+ <title>Sheet.9</title>
+ <path d="M0 206.24 L50.59 206.24" class="st6"/>
+ </g>
+ <g id="shape10-47" v:mID="10" v:groupContext="shape" transform="translate(364.228,-150.976) rotate(15.5241)">
+ <title>Sheet.10</title>
+ <path d="M0 206.24 L161.1 206.24" class="st6"/>
+ </g>
+ <g id="shape11-52" v:mID="11" v:groupContext="shape" transform="translate(408.029,-95.5)">
+ <title>Sheet.11</title>
+ <path d="M0 206.24 L45 206.24" class="st8"/>
+ </g>
+ <g id="shape12-55" v:mID="12" v:groupContext="shape" transform="translate(48.0294,-50.5)">
+ <title>Sheet.12</title>
+ <desc>H7</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="192.739" width="45" height="27"/>
+ <rect x="0" y="179.239" width="45" height="27" class="st9"/>
+ <text x="13.86" y="198.14" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan
+ dy="-0.284em" class="st11" v:baseFontSize="18">7</tspan></text> </g>
+ <g id="shape13-59" v:mID="13" v:groupContext="shape" transform="translate(192.029,-50.5)">
+ <title>Sheet.13</title>
+ <desc>H267</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="192.739" width="45" height="27"/>
+ <rect x="0" y="179.239" width="45" height="27" class="st9"/>
+ <text x="7.93" y="198.14" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st11" v:baseFontSize="18">267</tspan></text> </g>
+ <g id="shape14-63" v:mID="14" v:groupContext="shape" transform="translate(318.029,-50.5)">
+ <title>Sheet.14</title>
+ <desc>H46</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="192.739" width="45" height="27"/>
+ <rect x="0" y="179.239" width="45" height="27" class="st9"/>
+ <text x="10.89" y="198.14" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan
+ dy="-0.284em" class="st11" v:baseFontSize="18">46</tspan></text> </g>
+ <g id="shape15-67" v:mID="15" v:groupContext="shape" transform="translate(502.529,-50.5)">
+ <title>Sheet.15</title>
+ <desc>H132</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="192.739" width="45" height="27"/>
+ <rect x="0" y="179.239" width="45" height="27" class="st9"/>
+ <text x="7.93" y="198.14" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st11" v:baseFontSize="18">132</tspan></text> </g>
+ <g id="shape16-71" v:mID="16" v:groupContext="shape" transform="translate(111.029,-19)">
+ <title>Sheet.16</title>
+ <desc>Store hash function index for each group of keys</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="189" cy="192.739" width="378" height="27"/>
+ <rect x="0" y="179.239" width="378" height="27" class="st12"/>
+ <text x="12.27" y="198.14" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Store hash function index for each group of keys</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i6.svg b/doc/guides/prog_guide/img/efd_i6.svg
new file mode 100644
index 0000000..9aee30b
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i6.svg
@@ -0,0 +1,1254 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i6.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="8.2496in" height="5.89673in"
+ viewBox="0 0 593.971 424.565" xml:space="preserve" color-interpolation-filters="sRGB" class="st27">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st5 {fill:#feffff;font-family:Calibri;font-size:0.75em}
+ .st6 {fill:none;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st7 {fill:none;stroke:#2e75b5;stroke-width:2.25}
+ .st8 {fill:#305497;stroke:#2e75b5;stroke-width:1}
+ .st9 {fill:#feffff;font-family:Calibri;font-size:0.833336em;font-weight:bold}
+ .st10 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2)}
+ .st11 {fill:#5b9bd5}
+ .st12 {stroke:#c7c8c8;stroke-width:0.25}
+ .st13 {fill:#acccea;stroke:#c7c8c8;stroke-width:0.25}
+ .st14 {fill:#feffff;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st15 {fill:#ed7d31;stroke:#c7c8c8;stroke-width:0.25}
+ .st16 {fill:#deebf6;stroke:#c7c8c8;stroke-width:0.25}
+ .st17 {marker-end:url(#mrkr5-212);stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st18 {fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st19 {fill:none;stroke:#2e75b5;stroke-width:1}
+ .st20 {fill:#5b9bd5;font-family:Calibri;font-size:1.00001em}
+ .st21 {fill:none;stroke:none;stroke-width:0.25}
+ .st22 {font-size:1em}
+ .st23 {fill:#ffffff}
+ .st24 {stroke:#5b9bd5;stroke-dasharray:1.5,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st25 {marker-end:url(#mrkr5-444);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st26 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st27 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-212" class="st18" v:arrowType="5" v:arrowSize="2" v:setback="5.8" refX="-5.8" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ <marker id="mrkr5-444" class="st26" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(319.501,-335.688)">
+ <title>Rectangle.58</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow3-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape4-7" v:mID="4" v:groupContext="shape" transform="translate(353.251,-335.688)">
+ <title>Rectangle.59</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow4-8" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape5-13" v:mID="5" v:groupContext="shape" transform="translate(400.501,-335.688)">
+ <title>Rectangle.60</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow5-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape6-19" v:mID="6" v:groupContext="shape" transform="translate(434.251,-335.688)">
+ <title>Rectangle.61</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow6-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape7-25" v:mID="7" v:groupContext="shape" transform="translate(481.501,-335.688)">
+ <title>Rectangle.62</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow7-26" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape8-30" v:mID="8" v:groupContext="shape" transform="translate(515.251,-335.688)">
+ <title>Rectangle.63</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow8-31" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape9-35" v:mID="9" v:groupContext="shape" transform="translate(319.501,-313.188)">
+ <title>Rectangle.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow9-36" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape10-40" v:mID="10" v:groupContext="shape" transform="translate(353.251,-313.188)">
+ <title>Rectangle.65</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow10-41" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape11-45" v:mID="11" v:groupContext="shape" transform="translate(400.501,-313.188)">
+ <title>Rectangle.66</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow11-46" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape12-50" v:mID="12" v:groupContext="shape" transform="translate(434.251,-313.188)">
+ <title>Rectangle.67</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow12-51" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape13-55" v:mID="13" v:groupContext="shape" transform="translate(481.501,-313.188)">
+ <title>Rectangle.68</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow13-56" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape14-60" v:mID="14" v:groupContext="shape" transform="translate(515.251,-313.188)">
+ <title>Rectangle.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow14-61" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape15-65" v:mID="15" v:groupContext="shape" transform="translate(319.501,-277.188)">
+ <title>Rectangle.70</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow15-66" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape16-71" v:mID="16" v:groupContext="shape" transform="translate(353.251,-277.188)">
+ <title>Rectangle.71</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow16-72" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape17-77" v:mID="17" v:groupContext="shape" transform="translate(400.501,-277.188)">
+ <title>Rectangle.72</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow17-78" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape18-83" v:mID="18" v:groupContext="shape" transform="translate(434.251,-277.188)">
+ <title>Rectangle.73</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow18-84" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape19-89" v:mID="19" v:groupContext="shape" transform="translate(481.501,-277.188)">
+ <title>Rectangle.74</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow19-90" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape20-95" v:mID="20" v:groupContext="shape" transform="translate(515.251,-277.188)">
+ <title>Rectangle.75</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow20-96" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape21-101" v:mID="21" v:groupContext="shape" transform="translate(319.501,-240.687)">
+ <title>Rectangle.76</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow21-102" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape22-106" v:mID="22" v:groupContext="shape" transform="translate(353.251,-240.687)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow22-107" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape23-111" v:mID="23" v:groupContext="shape" transform="translate(400.501,-240.687)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow23-112" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape24-116" v:mID="24" v:groupContext="shape" transform="translate(434.251,-240.687)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow24-117" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape25-121" v:mID="25" v:groupContext="shape" transform="translate(481.501,-240.687)">
+ <title>Rectangle.80</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow25-122" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape26-127" v:mID="26" v:groupContext="shape" transform="translate(515.251,-240.687)">
+ <title>Rectangle.81</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow26-128" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape27-133" v:mID="27" v:groupContext="shape" transform="translate(317.251,-231.687)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow27-134" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st6"/>
+ </g>
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st7"/>
+ </g>
+ <g id="shape28-138" v:mID="28" v:groupContext="shape" transform="translate(328.501,-362.688)">
+ <title>Sheet.28</title>
+ <desc>Local Table for N Specific Flows Serviced at Node 1</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="110.423" cy="418.94" width="220.85" height="11.25"/>
+ <rect x="0" y="413.315" width="220.846" height="11.25" class="st8"/>
+ <text x="5.77" y="421.94" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Local Table for N Specific Flows Serviced at Node 1</text> </g>
+ <g id="group34-141" transform="translate(66.0294,-165.569)" v:mID="34" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Device)"/>
+ <v:cp v:nameU="SubShapeType" v:lbl="SubShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Load balancer)"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Load balancer</title>
+ <g id="shape35-142" v:mID="35" v:groupContext="shape" transform="translate(0,-7.33146)">
+ <title>Sheet.35</title>
+ <g id="shadow35-143" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23 Z" class="st10"/>
+ <path d="M0 377.86 L72 377.86" class="st6"/>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23" class="st6"/>
+ </g>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23 Z" class="st11"/>
+ <path d="M0 377.86 L72 377.86" class="st12"/>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23" class="st12"/>
+ </g>
+ <g id="shape36-152" v:mID="36" v:groupContext="shape" transform="translate(8.03054,-12.9324)">
+ <title>Sheet.36</title>
+ <g id="shadow36-153" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M45.34 421.81 L41.2 422.66 L44.12 424.56 L49.68 423.16 L48.75 417.51 L45.8 415.59 L46.69 419.68 L36.97
+ 413.34 L35.6 415.45 L45.34 421.81 ZM50.83 405.36 L39.2 405.36 L39.2 407.88 L50.8 407.88 L47.82 410.83
+ L51.34 410.83 L55.21 406.61 L51.32 402.39 L47.83 402.39 L50.83 405.36 ZM46.49 392.01 L36.75 398.37
+ L38.13 400.48 L47.84 394.14 L46.96 398.23 L49.91 396.31 L50.84 390.66 L45.28 389.26 L42.36 391.16
+ L46.49 392.01 ZM27.71 397.16 C22.66 397.16 18.58 401.25 18.58 406.29 C18.58 411.33 22.66 415.42
+ 27.71 415.42 C32.75 415.42 36.84 411.33 36.84 406.29 C36.84 401.25 32.75 397.16 27.71 397.16 ZM27.71
+ 400.04 C31.15 400.04 33.96 402.84 33.96 406.29 C33.96 409.74 31.15 412.54 27.71 412.54 C24.26 412.54
+ 21.46 409.74 21.46 406.29 C21.46 402.84 24.26 400.04 27.71 400.04 ZM11.64 405.04 L0 405.04 L0 407.56
+ L11.6 407.56 L8.62 410.51 L12.14 410.51 L16.01 406.29 L12.12 402.07 L8.64 402.07 L11.64 405.04 Z"
+ class="st2"/>
+ </g>
+ <path d="M45.34 421.81 L41.2 422.66 L44.12 424.56 L49.68 423.16 L48.75 417.51 L45.8 415.59 L46.69 419.68 L36.97 413.34
+ L35.6 415.45 L45.34 421.81 ZM50.83 405.36 L39.2 405.36 L39.2 407.88 L50.8 407.88 L47.82 410.83 L51.34
+ 410.83 L55.21 406.61 L51.32 402.39 L47.83 402.39 L50.83 405.36 ZM46.49 392.01 L36.75 398.37 L38.13 400.48
+ L47.84 394.14 L46.96 398.23 L49.91 396.31 L50.84 390.66 L45.28 389.26 L42.36 391.16 L46.49 392.01 ZM27.71
+ 397.16 C22.66 397.16 18.58 401.25 18.58 406.29 C18.58 411.33 22.66 415.42 27.71 415.42 C32.75 415.42
+ 36.84 411.33 36.84 406.29 C36.84 401.25 32.75 397.16 27.71 397.16 ZM27.71 400.04 C31.15 400.04 33.96
+ 402.84 33.96 406.29 C33.96 409.74 31.15 412.54 27.71 412.54 C24.26 412.54 21.46 409.74 21.46 406.29
+ C21.46 402.84 24.26 400.04 27.71 400.04 ZM11.64 405.04 L0 405.04 L0 407.56 L11.6 407.56 L8.62 410.51
+ L12.14 410.51 L16.01 406.29 L12.12 402.07 L8.64 402.07 L11.64 405.04 Z" class="st13"/>
+ </g>
+ </g>
+ <g id="shape37-157" v:mID="37" v:groupContext="shape" transform="translate(21.0294,-45.4375)">
+ <title>Rectangle</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow37-158" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="336.433" width="135" height="88.1315" class="st2"/>
+ </g>
+ <rect x="0" y="336.433" width="135" height="88.1315" class="st3"/>
+ </g>
+ <g id="shape38-162" v:mID="38" v:groupContext="shape" transform="translate(34.693,-126.438)">
+ <title>Sheet.38</title>
+ <desc>EFD Table</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="49.3364" cy="415.565" width="98.68" height="18"/>
+ <rect x="0" y="406.565" width="98.6728" height="18" class="st8"/>
+ <text x="24.87" y="419.17" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>EFD Table</text> </g>
+ <g id="shape39-165" v:mID="39" v:groupContext="shape" transform="translate(30.0294,-99.4375)">
+ <title>Rectangle.39</title>
+ <desc>Group_id</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="26.9182" cy="415.565" width="53.84" height="18"/>
+ <g id="shadow39-166" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st15"/>
+ <text x="7.87" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group_id</text> </g>
+ <g id="shape40-171" v:mID="40" v:groupContext="shape" transform="translate(93.193,-99.4375)">
+ <title>Rectangle.40</title>
+ <desc>Hash index</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="26.9182" cy="415.565" width="53.84" height="18"/>
+ <g id="shadow40-172" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st15"/>
+ <text x="4.64" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Hash index</text> </g>
+ <g id="shape41-177" v:mID="41" v:groupContext="shape" transform="translate(30.193,-82.4275)">
+ <title>Rectangle.41</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow41-178" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape42-182" v:mID="42" v:groupContext="shape" transform="translate(30.193,-66.8125)">
+ <title>Rectangle.42</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow42-183" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape43-187" v:mID="43" v:groupContext="shape" transform="translate(30.1112,-52.1875)">
+ <title>Rectangle.43</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow43-188" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape44-192" v:mID="44" v:groupContext="shape" transform="translate(93.0294,-81.4375)">
+ <title>Rectangle.44</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow44-193" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape45-197" v:mID="45" v:groupContext="shape" transform="translate(93.193,-66.8125)">
+ <title>Rectangle.45</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow45-198" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape46-202" v:mID="46" v:groupContext="shape" transform="translate(93.193,-52.1875)">
+ <title>Rectangle.46</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow46-203" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape47-207" v:mID="47" v:groupContext="shape" transform="translate(374.924,544.022) rotate(135)">
+ <title>Sheet.47</title>
+ <path d="M-0 417.75 A40.674 18.0151 -156.2 0 0 40.24 422.15 L40.49 421.89" class="st17"/>
+ </g>
+ <g id="shape48-213" v:mID="48" v:groupContext="shape" transform="translate(21.0294,-19)">
+ <title>Sheet.48</title>
+ <desc>Supports X*N Flows</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="415.565" width="135" height="18"/>
+ <rect x="0" y="406.565" width="135" height="18" class="st19"/>
+ <text x="19.05" y="419.17" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Supports X*N Flows</text> </g>
+ <g id="shape49-216" v:mID="49" v:groupContext="shape" transform="translate(48.0294,-229.938)">
+ <title>Sheet.49</title>
+ <desc>Frontend Server or Load Balancer</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="400.94" width="108" height="47.25"/>
+ <rect x="0" y="377.315" width="108" height="47.25" class="st21"/>
+ <text x="14.56" y="397.34" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Frontend Server<v:newlineChar/><tspan
+ x="13.16" dy="1.2em" class="st22">or Load Balancer </tspan> </text> </g>
+ <g id="group51-220" transform="translate(223.876,-310.938)" v:mID="51" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server</title>
+ <g id="shape52-221" v:mID="52" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.52</title>
+ <g id="shadow52-222" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape53-226" v:mID="53" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.53</title>
+ <g id="shadow53-227" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape54-231" v:mID="54" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.54</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow54-232" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape59-239" v:mID="59" v:groupContext="shape" transform="translate(277.876,-373.938)">
+ <title>Sheet.59</title>
+ <path d="M-0 424.56 A111.108 53.2538 42.31 0 1 93.83 421.21 L94.14 421.41" class="st17"/>
+ </g>
+ <g id="shape60-244" v:mID="60" v:groupContext="shape" transform="translate(205.876,-283.938)">
+ <title>Sheet.60</title>
+ <desc>Backend Server 1</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.93" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server 1</text> </g>
+ <g id="group61-247" transform="translate(223.876,-207.438)" v:mID="61" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server.61</title>
+ <g id="shape62-248" v:mID="62" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.62</title>
+ <g id="shadow62-249" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape63-253" v:mID="63" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.63</title>
+ <g id="shadow63-254" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape64-258" v:mID="64" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow64-259" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape65-266" v:mID="65" v:groupContext="shape" transform="translate(205.876,-180.437)">
+ <title>Sheet.65</title>
+ <desc>Backend Server 2</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.93" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server 2</text> </g>
+ <g id="group66-269" transform="translate(219.029,-58.9375)" v:mID="66" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server.66</title>
+ <g id="shape67-270" v:mID="67" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.67</title>
+ <g id="shadow67-271" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape68-275" v:mID="68" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.68</title>
+ <g id="shadow68-276" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape69-280" v:mID="69" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow69-281" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape70-288" v:mID="70" v:groupContext="shape" transform="translate(201.029,-26.056)">
+ <title>Sheet.70</title>
+ <desc>Backend Server X</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.86" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server X</text> </g>
+ <g id="shape71-291" v:mID="71" v:groupContext="shape" transform="translate(684.44,239.627) rotate(90)">
+ <title>Sheet.71</title>
+ <path d="M0 424.56 L45 424.56" class="st24"/>
+ </g>
+ <g id="shape72-294" v:mID="72" v:groupContext="shape" transform="translate(6.85967,-22.443) rotate(-38.1076)">
+ <title>Sheet.72</title>
+ <path d="M-0 424.56 A96.1331 44.4001 55.03 0 1 68.24 420.56 L68.51 420.79" class="st17"/>
+ </g>
+ <g id="shape73-299" v:mID="73" v:groupContext="shape" transform="translate(328.501,-135.937)">
+ <title>Rectangle.73</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow73-300" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape74-305" v:mID="74" v:groupContext="shape" transform="translate(362.251,-135.937)">
+ <title>Rectangle.74</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow74-306" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape75-311" v:mID="75" v:groupContext="shape" transform="translate(409.501,-135.937)">
+ <title>Rectangle.75</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow75-312" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape76-317" v:mID="76" v:groupContext="shape" transform="translate(443.251,-135.937)">
+ <title>Rectangle.76</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow76-318" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape77-323" v:mID="77" v:groupContext="shape" transform="translate(490.501,-135.937)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow77-324" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape78-328" v:mID="78" v:groupContext="shape" transform="translate(524.251,-135.937)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow78-329" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape79-333" v:mID="79" v:groupContext="shape" transform="translate(328.501,-113.437)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow79-334" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape80-338" v:mID="80" v:groupContext="shape" transform="translate(362.251,-113.437)">
+ <title>Rectangle.80</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow80-339" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape81-343" v:mID="81" v:groupContext="shape" transform="translate(409.501,-113.437)">
+ <title>Rectangle.81</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow81-344" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape82-348" v:mID="82" v:groupContext="shape" transform="translate(443.251,-113.437)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow82-349" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape83-353" v:mID="83" v:groupContext="shape" transform="translate(490.501,-113.437)">
+ <title>Rectangle.83</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow83-354" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape84-358" v:mID="84" v:groupContext="shape" transform="translate(524.251,-113.437)">
+ <title>Rectangle.84</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow84-359" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape85-363" v:mID="85" v:groupContext="shape" transform="translate(328.501,-77.4375)">
+ <title>Rectangle.85</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow85-364" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape86-369" v:mID="86" v:groupContext="shape" transform="translate(362.251,-77.4375)">
+ <title>Rectangle.86</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow86-370" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape87-375" v:mID="87" v:groupContext="shape" transform="translate(409.501,-77.4375)">
+ <title>Rectangle.87</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow87-376" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape88-381" v:mID="88" v:groupContext="shape" transform="translate(443.251,-77.4375)">
+ <title>Rectangle.88</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow88-382" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape89-387" v:mID="89" v:groupContext="shape" transform="translate(490.501,-77.4375)">
+ <title>Rectangle.89</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow89-388" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape90-393" v:mID="90" v:groupContext="shape" transform="translate(524.251,-77.4375)">
+ <title>Rectangle.90</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow90-394" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape91-399" v:mID="91" v:groupContext="shape" transform="translate(328.501,-40.9375)">
+ <title>Rectangle.91</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow91-400" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape92-404" v:mID="92" v:groupContext="shape" transform="translate(362.251,-40.9375)">
+ <title>Rectangle.92</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow92-405" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape93-409" v:mID="93" v:groupContext="shape" transform="translate(409.501,-40.9375)">
+ <title>Rectangle.93</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow93-410" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape94-414" v:mID="94" v:groupContext="shape" transform="translate(443.251,-40.9375)">
+ <title>Rectangle.94</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow94-415" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape95-419" v:mID="95" v:groupContext="shape" transform="translate(490.501,-40.9375)">
+ <title>Rectangle.95</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow95-420" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape96-425" v:mID="96" v:groupContext="shape" transform="translate(524.251,-40.9375)">
+ <title>Rectangle.96</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow96-426" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape97-431" v:mID="97" v:groupContext="shape" transform="translate(326.251,-31.9375)">
+ <title>Rectangle.97</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow97-432" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st6"/>
+ </g>
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st7"/>
+ </g>
+ <g id="shape98-436" v:mID="98" v:groupContext="shape" transform="translate(337.501,-162.938)">
+ <title>Sheet.98</title>
+ <desc>Local Table for N Specific Flows Serviced at Node X</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="110.423" cy="418.94" width="220.85" height="11.25"/>
+ <rect x="0" y="413.315" width="220.846" height="11.25" class="st8"/>
+ <text x="5.55" y="421.94" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Local Table for N Specific Flows Serviced at Node X</text> </g>
+ <g id="shape99-439" v:mID="99" v:groupContext="shape" transform="translate(-204.342,-29.4449) rotate(-53.7462)">
+ <title>Sheet.99</title>
+ <path d="M0 424.56 L160.37 424.56" class="st25"/>
+ </g>
+ <g id="shape100-445" v:mID="100" v:groupContext="shape" transform="translate(-37.6568,-164.882) rotate(-24.444)">
+ <title>Sheet.100</title>
+ <path d="M0 424.56 L101.71 424.56" class="st25"/>
+ </g>
+ <g id="shape101-450" v:mID="101" v:groupContext="shape" transform="translate(464.049,-50.8578) rotate(50.099)">
+ <title>Sheet.101</title>
+ <path d="M0 424.56 L139.8 424.56" class="st25"/>
+ </g>
+ <g id="shape102-455" v:mID="102" v:groupContext="shape" transform="translate(372.376,-207.438)">
+ <title>Sheet.102</title>
+ <desc>Supports N Flows</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="415.565" width="135" height="18"/>
+ <rect x="0" y="406.565" width="135" height="18" class="st19"/>
+ <text x="25.15" y="419.17" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Supports N Flows</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i7.svg b/doc/guides/prog_guide/img/efd_i7.svg
new file mode 100644
index 0000000..98f8000
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i7.svg
@@ -0,0 +1,790 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i8.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="10.6168in" height="4.81965in"
+ viewBox="0 0 764.409 347.015" xml:space="preserve" color-interpolation-filters="sRGB" class="st27">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st4 {fill:#000000;font-family:Intel Clear;font-size:0.998566em}
+ .st5 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st6 {stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st7 {stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st8 {stroke:#ca8f02;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st9 {stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st10 {fill:#c00000;font-family:Intel Clear;font-size:0.828804em;font-weight:bold}
+ .st11 {fill:#7f6d00;font-family:Intel Clear;font-size:0.828804em;font-weight:bold}
+ .st12 {fill:#00b050;stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st13 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st14 {fill:#00b050;stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st15 {fill:#ca8f02;stroke:#ca8f02;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st16 {fill:#004280;font-family:Intel Clear;font-size:0.828804em}
+ .st17 {fill:#ffffff;font-family:Intel Clear;font-size:0.998566em}
+ .st18 {fill:#ffffff;font-family:Intel Clear;font-size:1.49785em}
+ .st19 {visibility:visible}
+ .st20 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st21 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st22 {fill:#feffff;font-family:Symbol;font-size:1.16666em}
+ .st23 {font-size:1em}
+ .st24 {font-family:Calibri;font-size:1em}
+ .st25 {fill:none;stroke:none;stroke-width:0.25}
+ .st26 {fill:#ffffff;font-family:Calibri;font-size:1.00001em}
+ .st27 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(27.7836,-307.505)">
+ <title>Sheet.3</title>
+ <path d="M0 329.94 C-0 328.06 1.54 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.06 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.54 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape4-3" v:mID="4" v:groupContext="shape" transform="translate(27.7836,-307.505)">
+ <title>Sheet.4</title>
+ <path d="M0 329.94 C-0 328.06 1.54 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.06 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.54 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape5-5" v:mID="5" v:groupContext="shape" transform="translate(50.1544,-309.121)">
+ <title>Sheet.5</title>
+ <desc>Key1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1</text> </g>
+ <g id="shape6-9" v:mID="6" v:groupContext="shape" transform="translate(43.6909,-286.954)">
+ <title>Sheet.6</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape7-11" v:mID="7" v:groupContext="shape" transform="translate(27.7836,-266.044)">
+ <title>Sheet.7</title>
+ <path d="M0 330.04 C0 328.16 1.52 326.64 3.41 326.64 L68.51 326.64 C70.4 326.64 71.91 328.16 71.91 330.04 L71.91 343.62
+ C71.91 345.49 70.4 347.02 68.51 347.02 L3.41 347.02 C1.52 347.02 0 345.49 0 343.62 L0 330.04 Z"
+ class="st1"/>
+ </g>
+ <g id="shape8-13" v:mID="8" v:groupContext="shape" transform="translate(27.7836,-266.044)">
+ <title>Sheet.8</title>
+ <path d="M0 330.04 C0 328.16 1.52 326.64 3.41 326.64 L68.51 326.64 C70.4 326.64 71.91 328.16 71.91 330.04 L71.91 343.62
+ C71.91 345.49 70.4 347.02 68.51 347.02 L3.41 347.02 C1.52 347.02 0 345.49 0 343.62 L0 330.04 Z"
+ class="st2"/>
+ </g>
+ <g id="shape9-15" v:mID="9" v:groupContext="shape" transform="translate(50.7572,-267.602)">
+ <title>Sheet.9</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape10-19" v:mID="10" v:groupContext="shape" transform="translate(19.0195,-225.183)">
+ <title>Sheet.10</title>
+ <path d="M0 330.74 C0 328.94 1.46 327.48 3.26 327.48 L87.15 327.48 C88.95 327.48 90.4 328.94 90.4 330.74 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.26 347.02 C1.46 347.02 0 345.56 0 343.76 L0 330.74 Z"
+ class="st1"/>
+ </g>
+ <g id="shape11-21" v:mID="11" v:groupContext="shape" transform="translate(19.0195,-225.183)">
+ <title>Sheet.11</title>
+ <path d="M0 330.74 C0 328.94 1.46 327.48 3.26 327.48 L87.15 327.48 C88.95 327.48 90.4 328.94 90.4 330.74 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.26 347.02 C1.46 347.02 0 345.56 0 343.76 L0 330.74 Z"
+ class="st2"/>
+ </g>
+ <g id="shape12-23" v:mID="12" v:groupContext="shape" transform="translate(28.0373,-226.287)">
+ <title>Sheet.12</title>
+ <desc>0x0102ABCD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.3615" cy="339.824" width="86.73" height="14.3829"/>
+ <path d="M86.72 332.63 L0 332.63 L0 347.02 L86.72 347.02 L86.72 332.63" class="st3"/>
+ <text x="7.12" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102ABCD</text> </g>
+ <g id="shape13-27" v:mID="13" v:groupContext="shape" transform="translate(43.6909,-244.775)">
+ <title>Sheet.13</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape14-29" v:mID="14" v:groupContext="shape" transform="translate(40.7496,-210.444)">
+ <title>Sheet.14</title>
+ <path d="M26.29 334.91 C26.29 338.26 25.84 340.96 25.29 340.96 L14.16 340.96 C13.6 340.96 13.15 343.67 13.15 347.02 C13.15
+ 343.67 12.7 340.96 12.14 340.96 L1.01 340.96 C0.46 340.96 0 338.26 0 334.91" class="st6"/>
+ </g>
+ <g id="shape15-32" v:mID="15" v:groupContext="shape" transform="translate(125.629,-307.625)">
+ <title>Sheet.15</title>
+ <path d="M0 330.04 C0 328.16 1.52 326.64 3.41 326.64 L68.63 326.64 C70.51 326.64 72.03 328.16 72.03 330.04 L72.03 343.62
+ C72.03 345.49 70.51 347.02 68.63 347.02 L3.41 347.02 C1.52 347.02 0 345.49 0 343.62 L0 330.04 Z"
+ class="st1"/>
+ </g>
+ <g id="shape16-34" v:mID="16" v:groupContext="shape" transform="translate(125.629,-307.625)">
+ <title>Sheet.16</title>
+ <path d="M0 330.04 C0 328.16 1.52 326.64 3.41 326.64 L68.63 326.64 C70.51 326.64 72.03 328.16 72.03 330.04 L72.03 343.62
+ C72.03 345.49 70.51 347.02 68.63 347.02 L3.41 347.02 C1.52 347.02 0 345.49 0 343.62 L0 330.04 Z"
+ class="st2"/>
+ </g>
+ <g id="shape17-36" v:mID="17" v:groupContext="shape" transform="translate(148.034,-309.155)">
+ <title>Sheet.17</title>
+ <desc>Key2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key2</text> </g>
+ <g id="shape18-40" v:mID="18" v:groupContext="shape" transform="translate(141.536,-286.954)">
+ <title>Sheet.18</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape19-42" v:mID="19" v:groupContext="shape" transform="translate(125.629,-266.044)">
+ <title>Sheet.19</title>
+ <path d="M0 329.94 C0 328.06 1.54 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.06 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.54 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st1"/>
+ </g>
+ <g id="shape20-44" v:mID="20" v:groupContext="shape" transform="translate(125.629,-266.044)">
+ <title>Sheet.20</title>
+ <path d="M0 329.94 C0 328.06 1.54 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.06 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.54 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st2"/>
+ </g>
+ <g id="shape21-46" v:mID="21" v:groupContext="shape" transform="translate(148.636,-267.636)">
+ <title>Sheet.21</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape22-50" v:mID="22" v:groupContext="shape" transform="translate(116.865,-225.183)">
+ <title>Sheet.22</title>
+ <path d="M0 330.74 C0 328.94 1.46 327.48 3.26 327.48 L87.15 327.48 C88.95 327.48 90.4 328.94 90.4 330.74 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.26 347.02 C1.46 347.02 0 345.56 0 343.76 L0 330.74 Z"
+ class="st1"/>
+ </g>
+ <g id="shape23-52" v:mID="23" v:groupContext="shape" transform="translate(116.865,-225.183)">
+ <title>Sheet.23</title>
+ <path d="M0 330.74 C0 328.94 1.46 327.48 3.26 327.48 L87.15 327.48 C88.95 327.48 90.4 328.94 90.4 330.74 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.26 347.02 C1.46 347.02 0 345.56 0 343.76 L0 330.74 Z"
+ class="st2"/>
+ </g>
+ <g id="shape24-54" v:mID="24" v:groupContext="shape" transform="translate(125.917,-226.322)">
+ <title>Sheet.24</title>
+ <desc>0x0103CDAB</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.3615" cy="339.824" width="86.73" height="14.3829"/>
+ <path d="M86.72 332.63 L0 332.63 L0 347.02 L86.72 347.02 L86.72 332.63" class="st3"/>
+ <text x="7.12" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0103CDAB</text> </g>
+ <g id="shape25-58" v:mID="25" v:groupContext="shape" transform="translate(141.536,-244.775)">
+ <title>Sheet.25</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape26-60" v:mID="26" v:groupContext="shape" transform="translate(138.595,-210.444)">
+ <title>Sheet.26</title>
+ <path d="M26.29 334.91 C26.29 338.26 25.84 340.96 25.29 340.96 L14.16 340.96 C13.6 340.96 13.15 343.67 13.15 347.02 C13.15
+ 343.67 12.7 340.96 12.14 340.96 L1.01 340.96 C0.46 340.96 0 338.26 0 334.91" class="st7"/>
+ </g>
+ <g id="shape27-63" v:mID="27" v:groupContext="shape" transform="translate(221.793,-307.625)">
+ <title>Sheet.27</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.64 326.64 C70.52 326.64 72.03 328.17 72.03 330.04 L72.03 343.63
+ C72.03 345.5 70.52 347.02 68.64 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st1"/>
+ </g>
+ <g id="shape28-65" v:mID="28" v:groupContext="shape" transform="translate(221.793,-307.625)">
+ <title>Sheet.28</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.64 326.64 C70.52 326.64 72.03 328.17 72.03 330.04 L72.03 343.63
+ C72.03 345.5 70.52 347.02 68.64 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st2"/>
+ </g>
+ <g id="shape29-67" v:mID="29" v:groupContext="shape" transform="translate(244.237,-309.155)">
+ <title>Sheet.29</title>
+ <desc>Key3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3</text> </g>
+ <g id="shape30-71" v:mID="30" v:groupContext="shape" transform="translate(237.701,-286.954)">
+ <title>Sheet.30</title>
+ <path d="M0 336.65 L9.84 336.65 L9.84 326.28 L29.53 326.28 L29.53 336.65 L39.38 336.65 L19.69 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape31-73" v:mID="31" v:groupContext="shape" transform="translate(221.793,-266.044)">
+ <title>Sheet.31</title>
+ <path d="M0 329.94 C-0 328.07 1.55 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.07 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st1"/>
+ </g>
+ <g id="shape32-75" v:mID="32" v:groupContext="shape" transform="translate(221.793,-266.044)">
+ <title>Sheet.32</title>
+ <path d="M0 329.94 C-0 328.07 1.55 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.07 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st2"/>
+ </g>
+ <g id="shape33-77" v:mID="33" v:groupContext="shape" transform="translate(244.84,-267.636)">
+ <title>Sheet.33</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape34-81" v:mID="34" v:groupContext="shape" transform="translate(213.029,-225.183)">
+ <title>Sheet.34</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.27 327.48 C89.07 327.48 90.52 328.95 90.52 330.75 L90.52 343.76
+ C90.52 345.56 89.07 347.02 87.27 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape35-83" v:mID="35" v:groupContext="shape" transform="translate(213.029,-225.183)">
+ <title>Sheet.35</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.27 327.48 C89.07 327.48 90.52 328.95 90.52 330.75 L90.52 343.76
+ C90.52 345.56 89.07 347.02 87.27 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape36-85" v:mID="36" v:groupContext="shape" transform="translate(222.002,-226.322)">
+ <title>Sheet.36</title>
+ <desc>0x0102BAAD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.4787" cy="339.824" width="86.96" height="14.3829"/>
+ <path d="M86.96 332.63 L0 332.63 L0 347.02 L86.96 347.02 L86.96 332.63" class="st3"/>
+ <text x="7.13" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102BAAD</text> </g>
+ <g id="shape37-89" v:mID="37" v:groupContext="shape" transform="translate(237.701,-244.775)">
+ <title>Sheet.37</title>
+ <path d="M0 336.71 L9.84 336.71 L9.84 326.4 L29.53 326.4 L29.53 336.71 L39.38 336.71 L19.69 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape38-91" v:mID="38" v:groupContext="shape" transform="translate(234.759,-210.444)">
+ <title>Sheet.38</title>
+ <path d="M26.41 334.91 C26.41 338.26 25.96 340.96 25.41 340.96 L14.22 340.96 C13.66 340.96 13.21 343.67 13.21 347.02
+ C13.21 343.67 12.76 340.96 12.2 340.96 L1.01 340.96 C0.46 340.96 0 338.26 0 334.91" class="st6"/>
+ </g>
+ <g id="shape39-94" v:mID="39" v:groupContext="shape" transform="translate(319.759,-307.625)">
+ <title>Sheet.39</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.52 326.64 C70.4 326.64 71.91 328.17 71.91 330.04 L71.91 343.63
+ C71.91 345.5 70.4 347.02 68.52 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st1"/>
+ </g>
+ <g id="shape40-96" v:mID="40" v:groupContext="shape" transform="translate(319.759,-307.625)">
+ <title>Sheet.40</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.52 326.64 C70.4 326.64 71.91 328.17 71.91 330.04 L71.91 343.63
+ C71.91 345.5 70.4 347.02 68.52 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st2"/>
+ </g>
+ <g id="shape41-98" v:mID="41" v:groupContext="shape" transform="translate(342.125,-309.155)">
+ <title>Sheet.41</title>
+ <desc>Key4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4</text> </g>
+ <g id="shape42-102" v:mID="42" v:groupContext="shape" transform="translate(335.666,-286.954)">
+ <title>Sheet.42</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape43-104" v:mID="43" v:groupContext="shape" transform="translate(319.759,-266.044)">
+ <title>Sheet.43</title>
+ <path d="M0 329.94 C0 328.07 1.55 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.07 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape44-106" v:mID="44" v:groupContext="shape" transform="translate(319.759,-266.044)">
+ <title>Sheet.44</title>
+ <path d="M0 329.94 C0 328.07 1.55 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.07 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape45-108" v:mID="45" v:groupContext="shape" transform="translate(342.728,-267.636)">
+ <title>Sheet.45</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape46-112" v:mID="46" v:groupContext="shape" transform="translate(310.995,-225.183)">
+ <title>Sheet.46</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.15 327.48 C88.95 327.48 90.4 328.95 90.4 330.75 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape47-114" v:mID="47" v:groupContext="shape" transform="translate(310.995,-225.183)">
+ <title>Sheet.47</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.15 327.48 C88.95 327.48 90.4 328.95 90.4 330.75 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape48-116" v:mID="48" v:groupContext="shape" transform="translate(321.689,-226.322)">
+ <title>Sheet.48</title>
+ <desc>0x0104BEEF</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="41.4183" cy="339.824" width="82.84" height="14.3829"/>
+ <path d="M82.84 332.63 L0 332.63 L0 347.02 L82.84 347.02 L82.84 332.63" class="st3"/>
+ <text x="6.87" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0104BEEF</text> </g>
+ <g id="shape49-120" v:mID="49" v:groupContext="shape" transform="translate(335.666,-244.775)">
+ <title>Sheet.49</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape50-122" v:mID="50" v:groupContext="shape" transform="translate(332.725,-210.444)">
+ <title>Sheet.50</title>
+ <path d="M26.29 334.91 C26.29 338.27 25.84 340.96 25.29 340.96 L14.17 340.96 C13.61 340.96 13.15 343.67 13.15 347.02
+ C13.15 343.67 12.7 340.96 12.14 340.96 L1.02 340.96 C0.47 340.96 0 338.27 0 334.91" class="st6"/>
+ </g>
+ <g id="shape51-125" v:mID="51" v:groupContext="shape" transform="translate(416.884,-307.625)">
+ <title>Sheet.51</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.52 326.64 C70.4 326.64 71.91 328.17 71.91 330.04 L71.91 343.63
+ C71.91 345.5 70.4 347.02 68.52 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st1"/>
+ </g>
+ <g id="shape52-127" v:mID="52" v:groupContext="shape" transform="translate(416.884,-307.625)">
+ <title>Sheet.52</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.52 326.64 C70.4 326.64 71.91 328.17 71.91 330.04 L71.91 343.63
+ C71.91 345.5 70.4 347.02 68.52 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st2"/>
+ </g>
+ <g id="shape53-129" v:mID="53" v:groupContext="shape" transform="translate(439.255,-309.155)">
+ <title>Sheet.53</title>
+ <desc>Key5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key5</text> </g>
+ <g id="shape54-133" v:mID="54" v:groupContext="shape" transform="translate(432.791,-286.954)">
+ <title>Sheet.54</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape55-135" v:mID="55" v:groupContext="shape" transform="translate(416.884,-266.044)">
+ <title>Sheet.55</title>
+ <path d="M0 329.94 C0 328.07 1.55 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.07 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape56-137" v:mID="56" v:groupContext="shape" transform="translate(416.884,-266.044)">
+ <title>Sheet.56</title>
+ <path d="M0 329.94 C0 328.07 1.55 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.07 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape57-139" v:mID="57" v:groupContext="shape" transform="translate(439.858,-267.636)">
+ <title>Sheet.57</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape58-143" v:mID="58" v:groupContext="shape" transform="translate(408.12,-225.183)">
+ <title>Sheet.58</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.15 327.48 C88.95 327.48 90.4 328.95 90.4 330.75 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape59-145" v:mID="59" v:groupContext="shape" transform="translate(408.12,-225.183)">
+ <title>Sheet.59</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.15 327.48 C88.95 327.48 90.4 328.95 90.4 330.75 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape60-147" v:mID="60" v:groupContext="shape" transform="translate(416.778,-226.322)">
+ <title>Sheet.60</title>
+ <desc>0x0103DABD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.7817" cy="339.824" width="87.57" height="14.3829"/>
+ <path d="M87.56 332.63 L0 332.63 L0 347.02 L87.56 347.02 L87.56 332.63" class="st3"/>
+ <text x="7.17" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0103DABD</text> </g>
+ <g id="shape61-151" v:mID="61" v:groupContext="shape" transform="translate(432.791,-244.775)">
+ <title>Sheet.61</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape62-153" v:mID="62" v:groupContext="shape" transform="translate(429.85,-210.444)">
+ <title>Sheet.62</title>
+ <path d="M26.29 334.91 C26.29 338.27 25.84 340.96 25.29 340.96 L14.17 340.96 C13.61 340.96 13.15 343.67 13.15 347.02
+ C13.15 343.67 12.7 340.96 12.14 340.96 L1.02 340.96 C0.47 340.96 0 338.27 0 334.91" class="st7"/>
+ </g>
+ <g id="shape63-156" v:mID="63" v:groupContext="shape" transform="translate(514.489,-307.625)">
+ <title>Sheet.63</title>
+ <path d="M0 330.06 C-0 328.17 1.53 326.64 3.42 326.64 L68.64 326.64 C70.53 326.64 72.03 328.17 72.03 330.06 L72.03 343.63
+ C72.03 345.52 70.53 347.02 68.64 347.02 L3.42 347.02 C1.53 347.02 0 345.52 0 343.63 L0 330.06 Z"
+ class="st1"/>
+ </g>
+ <g id="shape64-158" v:mID="64" v:groupContext="shape" transform="translate(514.489,-307.625)">
+ <title>Sheet.64</title>
+ <path d="M0 330.06 C-0 328.17 1.53 326.64 3.42 326.64 L68.64 326.64 C70.53 326.64 72.03 328.17 72.03 330.06 L72.03 343.63
+ C72.03 345.52 70.53 347.02 68.64 347.02 L3.42 347.02 C1.53 347.02 0 345.52 0 343.63 L0 330.06 Z"
+ class="st2"/>
+ </g>
+ <g id="shape65-160" v:mID="65" v:groupContext="shape" transform="translate(536.883,-309.19)">
+ <title>Sheet.65</title>
+ <desc>Key6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key6</text> </g>
+ <g id="shape66-164" v:mID="66" v:groupContext="shape" transform="translate(530.396,-287.074)">
+ <title>Sheet.66</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape67-166" v:mID="67" v:groupContext="shape" transform="translate(514.489,-266.044)">
+ <title>Sheet.67</title>
+ <path d="M0 329.94 C0 328.08 1.56 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.08 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.56 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st1"/>
+ </g>
+ <g id="shape68-168" v:mID="68" v:groupContext="shape" transform="translate(514.489,-266.044)">
+ <title>Sheet.68</title>
+ <path d="M0 329.94 C0 328.08 1.56 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.08 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.56 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st2"/>
+ </g>
+ <g id="shape69-170" v:mID="69" v:groupContext="shape" transform="translate(537.486,-267.671)">
+ <title>Sheet.69</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape70-174" v:mID="70" v:groupContext="shape" transform="translate(505.725,-225.183)">
+ <title>Sheet.70</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.16 327.48 C88.96 327.48 90.4 328.95 90.4 330.75 L90.4 343.78
+ C90.4 345.58 88.96 347.02 87.16 347.02 L3.27 347.02 C1.47 347.02 0 345.58 0 343.78 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape71-176" v:mID="71" v:groupContext="shape" transform="translate(505.725,-225.183)">
+ <title>Sheet.71</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.16 327.48 C88.96 327.48 90.4 328.95 90.4 330.75 L90.4 343.78
+ C90.4 345.58 88.96 347.02 87.16 347.02 L3.27 347.02 C1.47 347.02 0 345.58 0 343.78 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape72-178" v:mID="72" v:groupContext="shape" transform="translate(514.766,-226.356)">
+ <title>Sheet.72</title>
+ <desc>0x0102ADCB</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.3615" cy="339.824" width="86.73" height="14.3829"/>
+ <path d="M86.72 332.63 L0 332.63 L0 347.02 L86.72 347.02 L86.72 332.63" class="st3"/>
+ <text x="7.12" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102ADCB</text> </g>
+ <g id="shape73-182" v:mID="73" v:groupContext="shape" transform="translate(530.396,-244.775)">
+ <title>Sheet.73</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape74-184" v:mID="74" v:groupContext="shape" transform="translate(527.455,-210.564)">
+ <title>Sheet.74</title>
+ <path d="M26.29 335.03 C26.29 338.36 25.87 341.02 25.3 341.02 L14.17 341.02 C13.6 341.02 13.15 343.72 13.15 347.02 C13.15
+ 343.72 12.73 341.02 12.16 341.02 L1.02 341.02 C0.45 341.02 0 338.36 0 335.03" class="st6"/>
+ </g>
+ <g id="shape75-187" v:mID="75" v:groupContext="shape" transform="translate(610.653,-307.505)">
+ <title>Sheet.75</title>
+ <path d="M0 329.94 C0 328.08 1.56 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.08 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.56 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st1"/>
+ </g>
+ <g id="shape76-189" v:mID="76" v:groupContext="shape" transform="translate(610.653,-307.505)">
+ <title>Sheet.76</title>
+ <path d="M0 329.94 C0 328.08 1.56 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.08 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.56 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st2"/>
+ </g>
+ <g id="shape77-191" v:mID="77" v:groupContext="shape" transform="translate(633.086,-309.121)">
+ <title>Sheet.77</title>
+ <desc>Key7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7</text> </g>
+ <g id="shape78-195" v:mID="78" v:groupContext="shape" transform="translate(626.561,-286.954)">
+ <title>Sheet.78</title>
+ <path d="M0 336.65 L9.84 336.65 L9.84 326.28 L29.53 326.28 L29.53 336.65 L39.38 336.65 L19.69 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape79-197" v:mID="79" v:groupContext="shape" transform="translate(610.653,-266.044)">
+ <title>Sheet.79</title>
+ <path d="M0 330.06 C-0 328.17 1.53 326.64 3.42 326.64 L68.64 326.64 C70.53 326.64 72.03 328.17 72.03 330.06 L72.03 343.63
+ C72.03 345.52 70.53 347.02 68.64 347.02 L3.42 347.02 C1.53 347.02 0 345.52 0 343.63 L0 330.06 Z"
+ class="st1"/>
+ </g>
+ <g id="shape80-199" v:mID="80" v:groupContext="shape" transform="translate(610.653,-266.044)">
+ <title>Sheet.80</title>
+ <path d="M0 330.06 C-0 328.17 1.53 326.64 3.42 326.64 L68.64 326.64 C70.53 326.64 72.03 328.17 72.03 330.06 L72.03 343.63
+ C72.03 345.52 70.53 347.02 68.64 347.02 L3.42 347.02 C1.53 347.02 0 345.52 0 343.63 L0 330.06 Z"
+ class="st2"/>
+ </g>
+ <g id="shape81-201" v:mID="81" v:groupContext="shape" transform="translate(633.689,-267.602)">
+ <title>Sheet.81</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape82-205" v:mID="82" v:groupContext="shape" transform="translate(601.889,-225.183)">
+ <title>Sheet.82</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.28 327.48 C89.08 327.48 90.52 328.95 90.52 330.75 L90.52 343.78
+ C90.52 345.58 89.08 347.02 87.28 347.02 L3.27 347.02 C1.47 347.02 0 345.58 0 343.78 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape83-207" v:mID="83" v:groupContext="shape" transform="translate(601.889,-225.183)">
+ <title>Sheet.83</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.28 327.48 C89.08 327.48 90.52 328.95 90.52 330.75 L90.52 343.78
+ C90.52 345.58 89.08 347.02 87.28 347.02 L3.27 347.02 C1.47 347.02 0 345.58 0 343.78 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape84-209" v:mID="84" v:groupContext="shape" transform="translate(610.969,-226.287)">
+ <title>Sheet.84</title>
+ <desc>0x0104DBCA</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.3615" cy="339.824" width="86.73" height="14.3829"/>
+ <path d="M86.72 332.63 L0 332.63 L0 347.02 L86.72 347.02 L86.72 332.63" class="st3"/>
+ <text x="7.12" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0104DBCA</text> </g>
+ <g id="shape85-213" v:mID="85" v:groupContext="shape" transform="translate(626.561,-244.775)">
+ <title>Sheet.85</title>
+ <path d="M0 336.71 L9.84 336.71 L9.84 326.4 L29.53 326.4 L29.53 336.71 L39.38 336.71 L19.69 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape86-215" v:mID="86" v:groupContext="shape" transform="translate(623.619,-210.444)">
+ <title>Sheet.86</title>
+ <path d="M26.41 334.91 C26.41 338.27 25.96 340.96 25.42 340.96 L14.23 340.96 C13.69 340.96 13.21 343.69 13.21 347.02
+ C13.21 343.69 12.76 340.96 12.22 340.96 L1.02 340.96 C0.48 340.96 0 338.27 0 334.91" class="st8"/>
+ </g>
+ <g id="shape87-218" v:mID="87" v:groupContext="shape" transform="translate(242.323,-81.6288)">
+ <title>Sheet.87</title>
+ <path d="M0 281.23 L0 347.02 L41.18 347.02 L41.18 281.23 L0 281.23 L0 281.23 Z" class="st1"/>
+ </g>
+ <g id="shape88-220" v:mID="88" v:groupContext="shape" transform="translate(247.009,-81.6288)">
+ <title>Sheet.88</title>
+ <path d="M0 281.23 L41.18 281.23 L41.18 347.02 L0 347.02 L0 281.23" class="st9"/>
+ </g>
+ <g id="shape89-223" v:mID="89" v:groupContext="shape" transform="translate(245.254,-132.398)">
+ <title>Sheet.89</title>
+ <desc>0x0102</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.3211" cy="341.046" width="42.65" height="11.9384"/>
+ <path d="M42.64 335.08 L0 335.08 L0 347.02 L42.64 347.02 L42.64 335.08" class="st3"/>
+ <text x="4" y="344.03" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102</text> </g>
+ <g id="shape90-227" v:mID="90" v:groupContext="shape" transform="translate(245.015,-82.7016)">
+ <title>Sheet.90</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.79425" cy="341.046" width="9.59" height="11.9384"/>
+ <path d="M9.59 335.08 L0 335.08 L0 347.02 L9.59 347.02 L9.59 335.08" class="st3"/>
+ <text x="1.84" y="344.03" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape91-231" v:mID="91" v:groupContext="shape" transform="translate(336.326,-81.6288)">
+ <title>Sheet.91</title>
+ <path d="M0 281.23 L0 347.02 L41.18 347.02 L41.18 281.23 L0 281.23 L0 281.23 Z" class="st1"/>
+ </g>
+ <g id="shape92-233" v:mID="92" v:groupContext="shape" transform="translate(339.598,-81.6288)">
+ <title>Sheet.92</title>
+ <path d="M0 281.23 L41.18 281.23 L41.18 347.02 L0 347.02 L0 281.23" class="st9"/>
+ </g>
+ <g id="shape93-236" v:mID="93" v:groupContext="shape" transform="translate(339.264,-132.398)">
+ <title>Sheet.93</title>
+ <desc>0x0103</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.3211" cy="341.046" width="42.65" height="11.9384"/>
+ <path d="M42.64 335.08 L0 335.08 L0 347.02 L42.64 347.02 L42.64 335.08" class="st3"/>
+ <text x="4" y="344.03" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0103</text> </g>
+ <g id="shape94-240" v:mID="94" v:groupContext="shape" transform="translate(339.024,-82.7016)">
+ <title>Sheet.94</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.79425" cy="341.046" width="9.59" height="11.9384"/>
+ <path d="M9.59 335.08 L0 335.08 L0 347.02 L9.59 347.02 L9.59 335.08" class="st3"/>
+ <text x="1.84" y="344.03" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape95-244" v:mID="95" v:groupContext="shape" transform="translate(438.598,-81.5089)">
+ <title>Sheet.95</title>
+ <path d="M0 281.23 L0 347.02 L41.18 347.02 L41.18 281.23 L0 281.23 L0 281.23 Z" class="st1"/>
+ </g>
+ <g id="shape96-246" v:mID="96" v:groupContext="shape" transform="translate(438.598,-81.5089)">
+ <title>Sheet.96</title>
+ <path d="M0 281.23 L41.18 281.23 L41.18 347.02 L0 347.02 L0 281.23" class="st9"/>
+ </g>
+ <g id="shape97-249" v:mID="97" v:groupContext="shape" transform="translate(437.81,-132.27)">
+ <title>Sheet.97</title>
+ <desc>0x0104</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.3211" cy="341.046" width="42.65" height="11.9384"/>
+ <path d="M42.64 335.08 L0 335.08 L0 347.02 L42.64 347.02 L42.64 335.08" class="st3"/>
+ <text x="4" y="344.03" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0104</text> </g>
+ <g id="shape98-253" v:mID="98" v:groupContext="shape" transform="translate(437.57,-82.5735)">
+ <title>Sheet.98</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.79425" cy="341.046" width="9.59" height="11.9384"/>
+ <path d="M9.59 335.08 L0 335.08 L0 347.02 L9.59 347.02 L9.59 335.08" class="st3"/>
+ <text x="1.84" y="344.03" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape99-257" v:mID="99" v:groupContext="shape" transform="translate(53.5505,-147.924)">
+ <title>Sheet.99</title>
+ <path d="M0.59 283.52 L206.27 343.39 L205.7 345.34 L0 285.48 L0.59 283.52 L0.59 283.52 ZM205.85 341.14 L210.88 345.79
+ L204.14 347.02 L205.85 341.14 L205.85 341.14 Z" class="st12"/>
+ </g>
+ <g id="shape100-259" v:mID="100" v:groupContext="shape" transform="translate(151.516,-147.924)">
+ <title>Sheet.100</title>
+ <path d="M0.59 283.52 L202.41 343.41 L201.83 345.35 L0 285.48 L0.59 283.52 L0.59 283.52 ZM202.01 341.16 L207.01 345.83
+ L200.27 347.02 L202.01 341.16 L202.01 341.16 Z" class="st13"/>
+ </g>
+ <g id="shape101-261" v:mID="101" v:groupContext="shape" transform="translate(246.975,-147.37)">
+ <title>Sheet.101</title>
+ <path d="M2 283.72 L15.77 341.83 L13.79 342.3 L0 284.18 L2 283.72 L2 283.72 ZM17.53 340.36 L15.97 347.02 L11.57 341.77
+ L17.53 340.36 L17.53 340.36 Z" class="st12"/>
+ </g>
+ <g id="shape102-263" v:mID="102" v:groupContext="shape" transform="translate(262.972,-147.37)">
+ <title>Sheet.102</title>
+ <path d="M82.31 283.13 L3.45 343.12 L4.68 344.74 L83.54 284.76 L82.31 283.13 L82.31 283.13 ZM3.02 340.89 L0 347.02 L6.74
+ 345.74 L3.02 340.89 L3.02 340.89 Z" class="st12"/>
+ </g>
+ <g id="shape103-265" v:mID="103" v:groupContext="shape" transform="translate(358.537,-149.107)">
+ <title>Sheet.103</title>
+ <path d="M83.92 284.85 L3.53 343.2 L4.73 344.84 L85.12 286.5 L83.92 284.85 L83.92 284.85 ZM3.15 340.95 L0 347.02 L6.75
+ 345.89 L3.15 340.95 L3.15 340.95 Z" class="st13"/>
+ </g>
+ <g id="shape104-267" v:mID="104" v:groupContext="shape" transform="translate(264.413,-147.534)">
+ <title>Sheet.104</title>
+ <path d="M275.95 283 L4.77 343.27 L5.22 345.25 L276.37 285 L275.95 283 L275.95 283 ZM5.31 341.05 L0 345.37 L6.66 347.02
+ L5.31 341.05 L5.31 341.05 Z" class="st14"/>
+ </g>
+ <g id="shape105-269" v:mID="105" v:groupContext="shape" transform="translate(456.982,-148.103)">
+ <title>Sheet.105</title>
+ <path d="M179.48 283.72 L4.5 343.48 L5.16 345.43 L180.14 285.66 L179.48 283.72 L179.48 283.72 ZM4.8 341.23 L0 346.12
+ L6.81 347.02 L4.8 341.23 L4.8 341.23 Z" class="st15"/>
+ </g>
+ <g id="shape106-271" v:mID="106" v:groupContext="shape" transform="translate(335.628,-18)">
+ <title>Sheet.106</title>
+ <path d="M0 309.64 C0 305.52 2.99 302.16 6.65 302.16 L14.2 302.16 L8.01 284.85 L35.48 302.16 L78.47 302.16 C82.15 302.16
+ 85.12 305.52 85.12 309.64 L85.12 309.64 L85.12 320.85 L85.12 339.54 C85.12 343.68 82.15 347.02 78.47 347.02
+ L35.48 347.02 L14.2 347.02 L14.2 347.02 L6.65 347.02 C2.99 347.02 0 343.68 0 339.54 L0 320.85 L0 309.64
+ L0 309.64 Z" class="st5"/>
+ </g>
+ <g id="shape109-273" v:mID="109" v:groupContext="shape" transform="translate(157.564,-62.4234)">
+ <title>Sheet.109</title>
+ <path d="M16.21 347.02 C11.74 347.02 8.1 346.42 8.1 345.67 L8.1 303.49 C8.1 302.75 4.49 302.14 0 302.14 C4.49 302.14
+ 8.1 301.54 8.1 300.79 L8.1 258.61 C8.1 257.88 11.74 257.26 16.21 257.26" class="st7"/>
+ </g>
+ <g id="shape110-276" v:mID="110" v:groupContext="shape" transform="translate(113.844,-100.157)">
+ <title>Sheet.110</title>
+ <desc>Groups</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="20.2175" cy="341.046" width="40.44" height="11.9384"/>
+ <path d="M40.44 335.08 L0 335.08 L0 347.02 L40.44 347.02 L40.44 335.08" class="st3"/>
+ <text x="3.85" y="344.03" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Groups</text> </g>
+ <g id="shape111-280" v:mID="111" v:groupContext="shape" transform="translate(196.718,-76.2186)">
+ <title>Sheet.111</title>
+ <path d="M0 331.97 C0 330.32 2.27 328.96 5.04 328.96 L37.61 328.96 L60.43 284.85 L53.72 328.96 L59.43 328.96 C62.22 328.96
+ 64.47 330.32 64.47 331.97 L64.47 331.97 L64.47 336.48 L64.47 344.01 C64.47 345.67 62.22 347.02 59.43 347.02
+ L53.72 347.02 L37.61 347.02 L37.61 347.02 L5.04 347.02 C2.27 347.02 0 345.67 0 344.01 L0 336.48 L0 331.97
+ L0 331.97 Z" class="st5"/>
+ </g>
+ <g id="shape112-282" v:mID="112" v:groupContext="shape" transform="translate(196.65,-80.2991)">
+ <title>Sheet.112</title>
+ <desc>group id</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="27.7691" cy="339.824" width="55.54" height="14.3829"/>
+ <path d="M55.54 332.63 L0 332.63 L0 347.02 L55.54 347.02 L55.54 332.63" class="st3"/>
+ <text x="5.09" y="343.42" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>group id</text> </g>
+ <g id="shape114-286" v:mID="114" v:groupContext="shape" transform="translate(506.433,-128.007)">
+ <title>Sheet.114</title>
+ <desc>-</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="6.63728" cy="336.229" width="13.28" height="21.5726"/>
+ <path d="M13.27 325.44 L0 325.44 L0 347.02 L13.27 347.02 L13.27 325.44" class="st3"/>
+ <text x="3.06" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>-</text> </g>
+ <g id="shape115-290" v:mID="115" v:groupContext="shape" transform="translate(529.004,-128.007)">
+ <title>Sheet.115</title>
+ <desc>Keys separated into</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="97.1729" cy="336.229" width="194.35" height="21.5726"/>
+ <path d="M194.35 325.44 L0 325.44 L0 347.02 L194.35 347.02 L194.35 325.44" class="st3"/>
+ <text x="17.06" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Keys separated into </text> </g>
+ <g id="shape116-294" v:mID="116" v:groupContext="shape" transform="translate(529.004,-106.438)">
+ <title>Sheet.116</title>
+ <desc>groups based on</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="83.1587" cy="336.229" width="166.32" height="21.5726"/>
+ <path d="M166.32 325.44 L0 325.44 L0 347.02 L166.32 347.02 L166.32 325.44" class="st3"/>
+ <text x="15.23" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>groups based on </text> </g>
+ <g id="shape117-298" v:mID="117" v:groupContext="shape" transform="translate(529.004,-84.869)">
+ <title>Sheet.117</title>
+ <desc>some bits from hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="97.731" cy="336.229" width="195.47" height="21.5726"/>
+ <path d="M195.46 325.44 L0 325.44 L0 347.02 L195.46 347.02 L195.46 325.44" class="st3"/>
+ <text x="14.94" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>some bits from hash</text> </g>
+ <g id="shape118-302" v:mID="118" v:groupContext="shape" transform="translate(506.433,-63.2999)">
+ <title>Sheet.118</title>
+ <desc>-</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="6.63728" cy="336.229" width="13.28" height="21.5726"/>
+ <path d="M13.27 325.44 L0 325.44 L0 347.02 L13.27 347.02 L13.27 325.44" class="st3"/>
+ <text x="3.06" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>-</text> </g>
+ <g id="shape119-306" v:mID="119" v:groupContext="shape" transform="translate(529.004,-63.2999)">
+ <title>Sheet.119</title>
+ <desc>Groups contain a</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="84.2539" cy="336.229" width="168.51" height="21.5726"/>
+ <path d="M168.51 325.44 L0 325.44 L0 347.02 L168.51 347.02 L168.51 325.44" class="st3"/>
+ <text x="15.38" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Groups contain a </text> </g>
+ <g id="shape120-310" v:mID="120" v:groupContext="shape" transform="translate(529.004,-41.7308)">
+ <title>Sheet.120</title>
+ <desc>small number of</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="81.4635" cy="336.229" width="162.93" height="21.5726"/>
+ <path d="M162.93 325.44 L0 325.44 L0 347.02 L162.93 347.02 L162.93 325.44" class="st3"/>
+ <text x="15.01" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>small number of </text> </g>
+ <g id="shape121-314" v:mID="121" v:groupContext="shape" transform="translate(529.004,-20.1617)">
+ <title>Sheet.121</title>
+ <desc>keys (<28)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.4481" cy="336.229" width="100.9" height="21.5726"/>
+ <path d="M100.9 325.44 L0 325.44 L0 347.02 L100.9 347.02 L100.9 325.44" class="st3"/>
+ <text x="8.77" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>keys (<28)</text> </g>
+ <g id="shape122-318" v:mID="122" v:groupContext="shape" transform="translate(19.1996,-146.276)">
+ <title>Sheet.122</title>
+ <path d="M0 310.17 C-0 306.1 3.62 302.8 8.07 302.8 L14.46 302.8 L29.68 282.28 L36.14 302.8 L78.65 302.8 C83.11 302.8
+ 86.72 306.1 86.72 310.17 L86.72 310.17 L86.72 321.22 L86.72 339.65 C86.72 343.72 83.11 347.02 78.65 347.02
+ L36.14 347.02 L14.46 347.02 L14.46 347.02 L8.07 347.02 C3.62 347.02 0 343.72 0 339.65 L0 321.22 L0 310.17
+ L0 310.17 Z" class="st5"/>
+ </g>
+ <g id="shape123-320" v:mID="123" v:groupContext="shape" transform="translate(41.9777,-174.053)">
+ <title>Sheet.123</title>
+ <desc>Group</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="22.8289" cy="339.824" width="45.66" height="14.3829"/>
+ <path d="M45.66 332.63 L0 332.63 L0 347.02 L45.66 347.02 L45.66 332.63" class="st3"/>
+ <text x="5.9" y="343.42" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group </text> </g>
+ <g id="shape124-324" v:mID="124" v:groupContext="shape" transform="translate(34.4142,-159.674)">
+ <title>Sheet.124</title>
+ <desc>Identifier</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="31.5173" cy="339.824" width="63.04" height="14.3829"/>
+ <path d="M63.03 332.63 L0 332.63 L0 347.02 L63.03 347.02 L63.03 332.63" class="st3"/>
+ <text x="7.04" y="343.42" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Identifier </text> </g>
+ <g id="shape125-328" v:mID="125" v:groupContext="shape" transform="translate(28.7716,-145.295)">
+ <title>Sheet.125</title>
+ <desc>(simplified)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="36.2165" cy="339.824" width="72.44" height="14.3829"/>
+ <path d="M72.43 332.63 L0 332.63 L0 347.02 L72.43 347.02 L72.43 332.63" class="st3"/>
+ <text x="6.19" y="343.42" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(simplified)</text> </g>
+ <g id="shape127-332" v:mID="127" v:groupContext="shape" transform="translate(517.688,-71.2991)">
+ <title>Sheet.127</title>
+ <desc>Keys separated into groups based on some bits from hash. Grou...</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="112.5" cy="302.139" width="225" height="89.7513"/>
+ <g id="shadow127-333" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st19">
+ <rect x="0" y="257.264" width="225" height="89.7513" class="st20"/>
+ </g>
+ <rect x="0" y="257.264" width="225" height="89.7513" class="st21"/>
+ <text x="4" y="281.09" class="st22" v:langID="1033"><v:paragraph v:indentFirst="-18" v:indentLeft="18" v:bullet="1"/><v:tabList/><tspan
+ class="st23" v:isBullet="true">·</tspan> <tspan class="st24">Keys separated into groups based </tspan><tspan
+ x="22" dy="1.204em" class="st24">on some bits from hash</tspan><tspan class="st24">.<v:newlineChar/></tspan><tspan
+ x="4" dy="1.211em" class="st23" v:isBullet="true">·</tspan> <tspan class="st24">Groups contain a small number of </tspan><tspan
+ x="22" dy="1.204em" class="st24">keys </tspan><tspan class="st24">(</tspan><tspan class="st24"><</tspan><tspan
+ class="st24">28</tspan><tspan class="st24">)</tspan></text> </g>
+ <g id="shape129-349" v:mID="129" v:groupContext="shape" transform="translate(336.326,-26.2991)">
+ <title>Sheet.129</title>
+ <desc>Total # of keys in group so far</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="39.6784" cy="333.515" width="79.36" height="27"/>
+ <rect x="0" y="320.015" width="79.3567" height="27" class="st25"/>
+ <text x="4.5" y="329.92" class="st26" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Total # of keys <tspan
+ x="4.39" dy="1.2em" class="st23">in group so far</tspan></text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i8.svg b/doc/guides/prog_guide/img/efd_i8.svg
new file mode 100644
index 0000000..d0fd463
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i8.svg
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i9.svg Page-2 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="4.98372in" height="2.08442in"
+ viewBox="0 0 358.828 150.078" xml:space="preserve" color-interpolation-filters="sRGB" class="st8">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st4 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st5 {fill:#000000;font-family:Intel Clear;font-size:0.998566em}
+ .st6 {fill:#c00000;font-family:Intel Clear;font-size:0.828804em;font-weight:bold}
+ .st7 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st8 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="4" v:index="2" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-2</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape4-1" v:mID="4" v:groupContext="shape" transform="translate(206.306,-19.0195)">
+ <title>Sheet.4</title>
+ <path d="M0 38.04 L0 150.08 L133.5 150.08 L133.5 38.04 L0 38.04 L0 38.04 Z" class="st1"/>
+ </g>
+ <g id="shape5-3" v:mID="5" v:groupContext="shape" transform="translate(206.306,-19.0195)">
+ <title>Sheet.5</title>
+ <path d="M0 38.04 L133.5 38.04 L133.5 150.08 L0 150.08 L0 38.04" class="st2"/>
+ </g>
+ <g id="shape6-6" v:mID="6" v:groupContext="shape" transform="translate(215.55,-70.7853)">
+ <title>Sheet.6</title>
+ <path d="M0 121.92 C0 118.82 2.54 116.29 5.64 116.29 L110.69 116.29 C113.81 116.29 116.33 118.82 116.33 121.92 L116.33
+ 144.45 C116.33 147.56 113.81 150.08 110.69 150.08 L5.64 150.08 C2.54 150.08 0 147.56 0 144.45 L0 121.92
+ Z" class="st1"/>
+ </g>
+ <g id="shape7-8" v:mID="7" v:groupContext="shape" transform="translate(215.55,-70.7853)">
+ <title>Sheet.7</title>
+ <path d="M0 121.92 C0 118.82 2.54 116.29 5.64 116.29 L110.69 116.29 C113.81 116.29 116.33 118.82 116.33 121.92 L116.33
+ 144.45 C116.33 147.56 113.81 150.08 110.69 150.08 L5.64 150.08 C2.54 150.08 0 147.56 0 144.45 L0 121.92
+ Z" class="st3"/>
+ </g>
+ <g id="shape8-10" v:mID="8" v:groupContext="shape" transform="translate(242.756,-86.1914)">
+ <title>Sheet.8</title>
+ <desc>hash_index</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="36.9951" cy="142.887" width="74" height="14.3829"/>
+ <path d="M73.99 135.7 L0 135.7 L0 150.08 L73.99 150.08 L73.99 135.7" class="st4"/>
+ <text x="6.29" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash_index</text> </g>
+ <g id="shape9-14" v:mID="9" v:groupContext="shape" transform="translate(229.67,-71.812)">
+ <title>Sheet.9</title>
+ <desc>(integer, 16 bits)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="52.0635" cy="142.887" width="104.13" height="14.3829"/>
+ <path d="M104.13 135.7 L0 135.7 L0 150.08 L104.13 150.08 L104.13 135.7" class="st4"/>
+ <text x="8.25" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(integer, 16 bits)</text> </g>
+ <g id="shape10-18" v:mID="10" v:groupContext="shape" transform="translate(215.55,-27.1678)">
+ <title>Sheet.10</title>
+ <path d="M0 121.92 C0 118.82 2.54 116.29 5.64 116.29 L110.69 116.29 C113.81 116.29 116.33 118.82 116.33 121.92 L116.33
+ 144.45 C116.33 147.56 113.81 150.08 110.69 150.08 L5.64 150.08 C2.54 150.08 0 147.56 0 144.45 L0 121.92
+ Z" class="st1"/>
+ </g>
+ <g id="shape11-20" v:mID="11" v:groupContext="shape" transform="translate(215.55,-27.1678)">
+ <title>Sheet.11</title>
+ <path d="M0 121.92 C0 118.82 2.54 116.29 5.64 116.29 L110.69 116.29 C113.81 116.29 116.33 118.82 116.33 121.92 L116.33
+ 144.45 C116.33 147.56 113.81 150.08 110.69 150.08 L5.64 150.08 C2.54 150.08 0 147.56 0 144.45 L0 121.92
+ Z" class="st3"/>
+ </g>
+ <g id="shape12-22" v:mID="12" v:groupContext="shape" transform="translate(237.836,-42.6033)">
+ <title>Sheet.12</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="42.693" cy="142.887" width="85.39" height="14.3829"/>
+ <path d="M85.39 135.7 L0 135.7 L0 150.08 L85.39 150.08 L85.39 135.7" class="st4"/>
+ <text x="7.03" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape13-26" v:mID="13" v:groupContext="shape" transform="translate(251.643,-28.2239)">
+ <title>Sheet.13</title>
+ <desc>(16 bits)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="26.9562" cy="142.887" width="53.92" height="14.3829"/>
+ <path d="M53.91 135.7 L0 135.7 L0 150.08 L53.91 150.08 L53.91 135.7" class="st4"/>
+ <text x="4.98" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(16 bits)</text> </g>
+ <g id="shape14-30" v:mID="14" v:groupContext="shape" transform="translate(213.473,-114.303)">
+ <title>Sheet.14</title>
+ <desc>Group ID: 0x0102</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="47.976" cy="144.109" width="95.96" height="11.9384"/>
+ <path d="M95.95 138.14 L0 138.14 L0 150.08 L95.95 150.08 L95.95 138.14" class="st4"/>
+ <text x="7.47" y="147.09" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group ID: 0x0102</text> </g>
+ <g id="shape15-34" v:mID="15" v:groupContext="shape" transform="translate(19.0195,-99.4242)">
+ <title>Sheet.15</title>
+ <path d="M0 129.31 C0 127.02 1.87 125.15 4.16 125.15 L109.18 125.15 C111.47 125.15 113.33 127.02 113.33 129.31 L113.33
+ 145.93 C113.33 148.22 111.47 150.08 109.18 150.08 L4.16 150.08 C1.87 150.08 0 148.22 0 145.93 L0 129.31
+ Z" class="st1"/>
+ </g>
+ <g id="shape16-36" v:mID="16" v:groupContext="shape" transform="translate(19.0195,-99.4242)">
+ <title>Sheet.16</title>
+ <path d="M0 129.31 C0 127.02 1.87 125.15 4.16 125.15 L109.18 125.15 C111.47 125.15 113.33 127.02 113.33 129.31 L113.33
+ 145.93 C113.33 148.22 111.47 150.08 109.18 150.08 L4.16 150.08 C1.87 150.08 0 148.22 0 145.93 L0 129.31
+ Z" class="st3"/>
+ </g>
+ <g id="shape17-38" v:mID="17" v:groupContext="shape" transform="translate(33.9485,-103.285)">
+ <title>Sheet.17</title>
+ <desc>Key1: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="49.6862" cy="142.887" width="99.38" height="14.3829"/>
+ <path d="M99.37 135.7 L0 135.7 L0 150.08 L99.37 150.08 L99.37 135.7" class="st4"/>
+ <text x="7.94" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1: Value = 0</text> </g>
+ <g id="shape18-42" v:mID="18" v:groupContext="shape" transform="translate(19.0195,-74.6198)">
+ <title>Sheet.18</title>
+ <path d="M0 129.31 C0 127.02 1.87 125.15 4.16 125.15 L109.18 125.15 C111.47 125.15 113.33 127.02 113.33 129.31 L113.33
+ 145.93 C113.33 148.22 111.47 150.08 109.18 150.08 L4.16 150.08 C1.87 150.08 0 148.22 0 145.93 L0 129.31
+ Z" class="st1"/>
+ </g>
+ <g id="shape19-44" v:mID="19" v:groupContext="shape" transform="translate(19.0195,-74.6198)">
+ <title>Sheet.19</title>
+ <path d="M0 129.31 C0 127.02 1.87 125.15 4.16 125.15 L109.18 125.15 C111.47 125.15 113.33 127.02 113.33 129.31 L113.33
+ 145.93 C113.33 148.22 111.47 150.08 109.18 150.08 L4.16 150.08 C1.87 150.08 0 148.22 0 145.93 L0 129.31
+ Z" class="st3"/>
+ </g>
+ <g id="shape20-46" v:mID="20" v:groupContext="shape" transform="translate(33.9485,-78.4626)">
+ <title>Sheet.20</title>
+ <desc>Key3: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="49.6862" cy="142.887" width="99.38" height="14.3829"/>
+ <path d="M99.37 135.7 L0 135.7 L0 150.08 L99.37 150.08 L99.37 135.7" class="st4"/>
+ <text x="7.94" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3: Value = 1</text> </g>
+ <g id="shape21-50" v:mID="21" v:groupContext="shape" transform="translate(19.0195,-49.5757)">
+ <title>Sheet.21</title>
+ <path d="M0 129.21 C0 126.91 1.88 125.03 4.19 125.03 L109.15 125.03 C111.46 125.03 113.33 126.91 113.33 129.21 L113.33
+ 145.91 C113.33 148.21 111.46 150.08 109.15 150.08 L4.19 150.08 C1.88 150.08 0 148.21 0 145.91 L0 129.21
+ Z" class="st1"/>
+ </g>
+ <g id="shape22-52" v:mID="22" v:groupContext="shape" transform="translate(19.0195,-49.5757)">
+ <title>Sheet.22</title>
+ <path d="M0 129.21 C0 126.91 1.88 125.03 4.19 125.03 L109.15 125.03 C111.46 125.03 113.33 126.91 113.33 129.21 L113.33
+ 145.91 C113.33 148.21 111.46 150.08 109.15 150.08 L4.19 150.08 C1.88 150.08 0 148.21 0 145.91 L0 129.21
+ Z" class="st3"/>
+ </g>
+ <g id="shape23-54" v:mID="23" v:groupContext="shape" transform="translate(33.9485,-53.4903)">
+ <title>Sheet.23</title>
+ <desc>Key4: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="49.6862" cy="142.887" width="99.38" height="14.3829"/>
+ <path d="M99.37 135.7 L0 135.7 L0 150.08 L99.37 150.08 L99.37 135.7" class="st4"/>
+ <text x="7.94" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4: Value = 0</text> </g>
+ <g id="shape24-58" v:mID="24" v:groupContext="shape" transform="translate(19.0195,-25.0109)">
+ <title>Sheet.24</title>
+ <path d="M0 129.21 C0 126.91 1.88 125.03 4.19 125.03 L109.15 125.03 C111.46 125.03 113.33 126.91 113.33 129.21 L113.33
+ 145.91 C113.33 148.21 111.46 150.08 109.15 150.08 L4.19 150.08 C1.88 150.08 0 148.21 0 145.91 L0 129.21
+ Z" class="st1"/>
+ </g>
+ <g id="shape25-60" v:mID="25" v:groupContext="shape" transform="translate(19.0195,-25.0109)">
+ <title>Sheet.25</title>
+ <path d="M0 129.21 C0 126.91 1.88 125.03 4.19 125.03 L109.15 125.03 C111.46 125.03 113.33 126.91 113.33 129.21 L113.33
+ 145.91 C113.33 148.21 111.46 150.08 109.15 150.08 L4.19 150.08 C1.88 150.08 0 148.21 0 145.91 L0 129.21
+ Z" class="st3"/>
+ </g>
+ <g id="shape26-62" v:mID="26" v:groupContext="shape" transform="translate(33.9485,-28.927)">
+ <title>Sheet.26</title>
+ <desc>Key7: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="49.6862" cy="142.887" width="99.38" height="14.3829"/>
+ <path d="M99.37 135.7 L0 135.7 L0 150.08 L99.37 150.08 L99.37 135.7" class="st4"/>
+ <text x="7.94" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7: Value = 1</text> </g>
+ <g id="shape27-66" v:mID="27" v:groupContext="shape" transform="translate(141.536,-51.5529)">
+ <title>Sheet.27</title>
+ <path d="M0 115.39 L22.75 115.39 L22.75 103.82 L45.5 126.95 L22.75 150.08 L22.75 138.51 L0 138.51 L0 115.39 Z"
+ class="st7"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i9.svg b/doc/guides/prog_guide/img/efd_i9.svg
new file mode 100644
index 0000000..b2e385d
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i9.svg
@@ -0,0 +1,390 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i10.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="9.8125in" height="3.76365in"
+ viewBox="0 0 706.5 270.983" xml:space="preserve" color-interpolation-filters="sRGB" class="st9">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st4 {fill:#000000;font-family:Arial;font-size:0.998566em}
+ .st5 {fill:#000000;font-family:Arial;font-size:0.918686em}
+ .st6 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st7 {fill:#ffffff;font-family:Arial;font-size:0.998566em}
+ .st8 {fill:#ffffff;font-family:Arial;font-size:1.49785em}
+ .st9 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape68-1" v:mID="68" v:groupContext="shape" transform="translate(196.523,-158.978)">
+ <title>Sheet.68</title>
+ <path d="M0 250.22 C0 247.95 1.89 246.06 4.17 246.06 L317.25 246.06 C319.53 246.06 321.39 247.95 321.39 250.22 L321.39
+ 266.85 C321.39 269.13 319.53 270.98 317.25 270.98 L4.17 270.98 C1.89 270.98 0 269.13 0 266.85 L0 250.22
+ Z" class="st1"/>
+ </g>
+ <g id="shape69-3" v:mID="69" v:groupContext="shape" transform="translate(196.523,-158.978)">
+ <title>Sheet.69</title>
+ <path d="M0 250.22 C0 247.95 1.89 246.06 4.17 246.06 L317.25 246.06 C319.53 246.06 321.39 247.95 321.39 250.22 L321.39
+ 266.85 C321.39 269.13 319.53 270.98 317.25 270.98 L4.17 270.98 C1.89 270.98 0 269.13 0 266.85 L0 250.22
+ Z" class="st2"/>
+ </g>
+ <g id="shape70-5" v:mID="70" v:groupContext="shape" transform="translate(186.139,-162.437)">
+ <title>Sheet.70</title>
+ <desc>(hash(key, seed1) + hash_index *</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="106.671" cy="263.792" width="213.35" height="14.3829"/>
+ <path d="M213.34 256.6 L0 256.6 L0 270.98 L213.34 270.98 L213.34 256.6" class="st3"/>
+ <text x="17.24" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(hash(key, seed1) + hash_index * </text> </g>
+ <g id="shape71-9" v:mID="71" v:groupContext="shape" transform="translate(381.48,-162.845)">
+ <title>Sheet.71</title>
+ <desc>hash(key</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="27.4843" cy="264.367" width="54.97" height="13.2327"/>
+ <path d="M54.97 257.75 L0 257.75 L0 270.98 L54.97 270.98 L54.97 257.75" class="st3"/>
+ <text x="5.12" y="267.67" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash(key</text> </g>
+ <g id="shape72-13" v:mID="72" v:groupContext="shape" transform="translate(424.755,-162.437)">
+ <title>Sheet.72</title>
+ <desc>, seed2)) % 16</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="46.7254" cy="263.792" width="93.46" height="14.3829"/>
+ <path d="M93.45 256.6 L0 256.6 L0 270.98 L93.45 270.98 L93.45 256.6" class="st3"/>
+ <text x="7.76" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>, seed2)) % 16</text> </g>
+ <g id="shape73-17" v:mID="73" v:groupContext="shape" transform="translate(524.094,-148.373)">
+ <title>Sheet.73</title>
+ <path d="M0 236.29 L22.75 236.29 L22.75 224.73 L45.5 247.86 L22.75 270.98 L22.75 259.42 L0 259.42 L0 236.29 Z"
+ class="st6"/>
+ </g>
+ <g id="shape74-19" v:mID="74" v:groupContext="shape" transform="translate(574.148,-217.574)">
+ <title>Sheet.74</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st1"/>
+ </g>
+ <g id="shape75-21" v:mID="75" v:groupContext="shape" transform="translate(574.148,-217.574)">
+ <title>Sheet.75</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st2"/>
+ </g>
+ <g id="shape76-23" v:mID="76" v:groupContext="shape" transform="translate(584.296,-231.499)">
+ <title>Sheet.76</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="40.993" cy="263.792" width="81.99" height="14.3829"/>
+ <path d="M81.99 256.6 L0 256.6 L0 270.98 L81.99 270.98 L81.99 256.6" class="st3"/>
+ <text x="7.01" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape77-27" v:mID="77" v:groupContext="shape" transform="translate(655.369,-231.499)">
+ <title>Sheet.77</title>
+ <desc>bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1076" cy="263.792" width="22.22" height="14.3829"/>
+ <path d="M22.22 256.6 L0 256.6 L0 270.98 L22.22 270.98 L22.22 256.6" class="st3"/>
+ <text x="4.78" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit </text> </g>
+ <g id="shape78-31" v:mID="78" v:groupContext="shape" transform="translate(588.858,-217.12)">
+ <title>Sheet.78</title>
+ <desc>index for key1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="44.8113" cy="263.792" width="89.63" height="14.3829"/>
+ <path d="M89.62 256.6 L0 256.6 L0 270.98 L89.62 270.98 L89.62 256.6" class="st3"/>
+ <text x="7.51" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>index for key1</text> </g>
+ <g id="shape79-35" v:mID="79" v:groupContext="shape" transform="translate(573.548,-178.869)">
+ <title>Sheet.79</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st1"/>
+ </g>
+ <g id="shape80-37" v:mID="80" v:groupContext="shape" transform="translate(573.548,-178.869)">
+ <title>Sheet.80</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st2"/>
+ </g>
+ <g id="shape81-39" v:mID="81" v:groupContext="shape" transform="translate(584.296,-192.768)">
+ <title>Sheet.81</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="40.993" cy="263.792" width="81.99" height="14.3829"/>
+ <path d="M81.99 256.6 L0 256.6 L0 270.98 L81.99 270.98 L81.99 256.6" class="st3"/>
+ <text x="7.01" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape82-43" v:mID="82" v:groupContext="shape" transform="translate(655.369,-192.768)">
+ <title>Sheet.82</title>
+ <desc>bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1076" cy="263.792" width="22.22" height="14.3829"/>
+ <path d="M22.22 256.6 L0 256.6 L0 270.98 L22.22 270.98 L22.22 256.6" class="st3"/>
+ <text x="4.78" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit </text> </g>
+ <g id="shape83-47" v:mID="83" v:groupContext="shape" transform="translate(588.858,-178.388)">
+ <title>Sheet.83</title>
+ <desc>index for key3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="44.8113" cy="263.792" width="89.63" height="14.3829"/>
+ <path d="M89.62 256.6 L0 256.6 L0 270.98 L89.62 270.98 L89.62 256.6" class="st3"/>
+ <text x="7.51" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>index for key3</text> </g>
+ <g id="shape84-51" v:mID="84" v:groupContext="shape" transform="translate(574.148,-139.326)">
+ <title>Sheet.84</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st1"/>
+ </g>
+ <g id="shape85-53" v:mID="85" v:groupContext="shape" transform="translate(574.148,-139.326)">
+ <title>Sheet.85</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st2"/>
+ </g>
+ <g id="shape86-55" v:mID="86" v:groupContext="shape" transform="translate(584.296,-153.227)">
+ <title>Sheet.86</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="40.993" cy="263.792" width="81.99" height="14.3829"/>
+ <path d="M81.99 256.6 L0 256.6 L0 270.98 L81.99 270.98 L81.99 256.6" class="st3"/>
+ <text x="7.01" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape87-59" v:mID="87" v:groupContext="shape" transform="translate(655.369,-153.227)">
+ <title>Sheet.87</title>
+ <desc>bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1076" cy="263.792" width="22.22" height="14.3829"/>
+ <path d="M22.22 256.6 L0 256.6 L0 270.98 L22.22 270.98 L22.22 256.6" class="st3"/>
+ <text x="4.78" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit </text> </g>
+ <g id="shape88-63" v:mID="88" v:groupContext="shape" transform="translate(588.858,-138.848)">
+ <title>Sheet.88</title>
+ <desc>index for key4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="44.8113" cy="263.792" width="89.63" height="14.3829"/>
+ <path d="M89.62 256.6 L0 256.6 L0 270.98 L89.62 270.98 L89.62 256.6" class="st3"/>
+ <text x="7.51" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>index for key4</text> </g>
+ <g id="shape89-67" v:mID="89" v:groupContext="shape" transform="translate(574.148,-100.622)">
+ <title>Sheet.89</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st1"/>
+ </g>
+ <g id="shape90-69" v:mID="90" v:groupContext="shape" transform="translate(574.148,-100.622)">
+ <title>Sheet.90</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st2"/>
+ </g>
+ <g id="shape91-71" v:mID="91" v:groupContext="shape" transform="translate(584.296,-114.496)">
+ <title>Sheet.91</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="40.993" cy="263.792" width="81.99" height="14.3829"/>
+ <path d="M81.99 256.6 L0 256.6 L0 270.98 L81.99 270.98 L81.99 256.6" class="st3"/>
+ <text x="7.01" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape92-75" v:mID="92" v:groupContext="shape" transform="translate(655.369,-114.496)">
+ <title>Sheet.92</title>
+ <desc>bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1076" cy="263.792" width="22.22" height="14.3829"/>
+ <path d="M22.22 256.6 L0 256.6 L0 270.98 L22.22 270.98 L22.22 256.6" class="st3"/>
+ <text x="4.78" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit </text> </g>
+ <g id="shape93-79" v:mID="93" v:groupContext="shape" transform="translate(588.858,-100.117)">
+ <title>Sheet.93</title>
+ <desc>index for key7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="44.8113" cy="263.792" width="89.63" height="14.3829"/>
+ <path d="M89.62 256.6 L0 256.6 L0 270.98 L89.62 270.98 L89.62 256.6" class="st3"/>
+ <text x="7.51" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>index for key7</text> </g>
+ <g id="shape94-83" v:mID="94" v:groupContext="shape" transform="translate(205.227,-191.137)">
+ <title>Sheet.94</title>
+ <path d="M0 217.76 C0 213 3.87 209.14 8.64 209.14 L14.53 209.14 L14.53 209.14 L36.32 209.14 L78.52 209.14 C83.3 209.14
+ 87.16 213 87.16 217.76 L87.16 239.33 L87.16 239.33 L87.16 252.27 L87.16 252.27 C87.16 257.05 83.3 260.9
+ 78.52 260.9 L36.32 260.9 L18.46 270.98 L14.53 260.9 L8.64 260.9 C3.87 260.9 0 257.05 0 252.27 L0 239.33
+ L0 239.33 L0 217.76 Z" class="st6"/>
+ </g>
+ <g id="shape95-85" v:mID="95" v:groupContext="shape" transform="translate(214.98,-225.215)">
+ <title>Sheet.95</title>
+ <desc>CRC32 (32</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="38.2947" cy="263.792" width="76.59" height="14.3829"/>
+ <path d="M76.59 256.6 L0 256.6 L0 270.98 L76.59 270.98 L76.59 256.6" class="st3"/>
+ <text x="8.33" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>CRC32 (32 </text> </g>
+ <g id="shape96-89" v:mID="96" v:groupContext="shape" transform="translate(222.123,-210.835)">
+ <title>Sheet.96</title>
+ <desc>bit output)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="32.5584" cy="263.792" width="65.12" height="14.3829"/>
+ <path d="M65.12 256.6 L0 256.6 L0 270.98 L65.12 270.98 L65.12 256.6" class="st3"/>
+ <text x="5.91" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit output)</text> </g>
+ <g id="shape97-93" v:mID="97" v:groupContext="shape" transform="translate(305.473,-188.366)">
+ <title>Sheet.97</title>
+ <path d="M0 226.84 C0 223.28 2.9 220.39 6.47 220.39 L21.37 220.39 L21.37 220.39 L53.42 220.39 L121.77 220.39 C125.34
+ 220.39 128.22 223.28 128.22 226.84 L128.22 242.97 L128.22 242.97 L128.22 252.65 L128.22 252.65 C128.22 256.21
+ 125.34 259.09 121.77 259.09 L53.42 259.09 L38.73 270.98 L21.37 259.09 L6.47 259.09 C2.9 259.09 0 256.21
+ 0 252.65 L0 242.97 L0 242.97 L0 226.84 Z" class="st6"/>
+ </g>
+ <g id="shape98-95" v:mID="98" v:groupContext="shape" transform="translate(318.48,-217.733)">
+ <title>Sheet.98</title>
+ <desc>Goal: Find a valid</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="57.4478" cy="263.792" width="114.9" height="14.3829"/>
+ <path d="M114.9 256.6 L0 256.6 L0 270.98 L114.9 270.98 L114.9 256.6" class="st3"/>
+ <text x="10.82" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Goal: Find a valid </text> </g>
+ <g id="shape99-99" v:mID="99" v:groupContext="shape" transform="translate(339.077,-203.354)">
+ <title>Sheet.99</title>
+ <desc>hash_index</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="37.1611" cy="263.792" width="74.33" height="14.3829"/>
+ <path d="M74.32 256.6 L0 256.6 L0 270.98 L74.32 270.98 L74.32 256.6" class="st3"/>
+ <text x="6.51" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash_index</text> </g>
+ <g id="shape100-103" v:mID="100" v:groupContext="shape" transform="translate(438.135,-185.939)">
+ <title>Sheet.100</title>
+ <path d="M0 217.36 C0 213.8 2.91 210.89 6.48 210.89 L21.37 210.89 L21.37 210.89 L53.42 210.89 L121.77 210.89 C125.34
+ 210.89 128.22 213.8 128.22 217.36 L128.22 233.48 L128.22 233.48 L128.22 243.15 L128.22 243.15 C128.22 246.72
+ 125.34 249.59 121.77 249.59 L53.42 249.59 L54.75 270.98 L21.37 249.59 L6.48 249.59 C2.91 249.59 0 246.72
+ 0 243.15 L0 233.48 L0 233.48 L0 217.36 Z" class="st6"/>
+ </g>
+ <g id="shape101-105" v:mID="101" v:groupContext="shape" transform="translate(448.763,-224.802)">
+ <title>Sheet.101</title>
+ <desc>Lookup Table has</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.6085" cy="263.792" width="117.22" height="14.3829"/>
+ <path d="M117.22 256.6 L0 256.6 L0 270.98 L117.22 270.98 L117.22 256.6" class="st3"/>
+ <text x="10.98" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Lookup Table has </text> </g>
+ <g id="shape102-109" v:mID="102" v:groupContext="shape" transform="translate(484.549,-210.423)">
+ <title>Sheet.102</title>
+ <desc>16 bits</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="22.2166" cy="263.792" width="44.44" height="14.3829"/>
+ <path d="M44.43 256.6 L0 256.6 L0 270.98 L44.43 270.98 L44.43 256.6" class="st3"/>
+ <text x="4.56" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>16 bits</text> </g>
+ <g id="shape103-113" v:mID="103" v:groupContext="shape" transform="translate(369.583,-90.8555)">
+ <title>Sheet.103</title>
+ <path d="M0 227.76 C0 222.98 3.89 219.1 8.67 219.1 L14.53 219.1 L34.47 205.09 L36.32 219.1 L78.5 219.1 C83.29 219.1 87.16
+ 222.98 87.16 227.76 L87.16 227.76 L87.16 240.73 L87.16 262.34 C87.16 267.12 83.29 270.98 78.5 270.98 L36.32
+ 270.98 L14.53 270.98 L14.53 270.98 L8.67 270.98 C3.89 270.98 0 267.12 0 262.34 L0 240.73 L0 227.76 L0 227.76
+ Z" class="st6"/>
+ </g>
+ <g id="shape104-115" v:mID="104" v:groupContext="shape" transform="translate(383.264,-114.932)">
+ <title>Sheet.104</title>
+ <desc>CRC32 (32</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="38.2947" cy="263.792" width="76.59" height="14.3829"/>
+ <path d="M76.59 256.6 L0 256.6 L0 270.98 L76.59 270.98 L76.59 256.6" class="st3"/>
+ <text x="8.33" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>CRC32 (32 </text> </g>
+ <g id="shape105-119" v:mID="105" v:groupContext="shape" transform="translate(386.505,-100.553)">
+ <title>Sheet.105</title>
+ <desc>bit output)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="32.5584" cy="263.792" width="65.12" height="14.3829"/>
+ <path d="M65.12 256.6 L0 256.6 L0 270.98 L65.12 270.98 L65.12 256.6" class="st3"/>
+ <text x="5.91" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit output)</text> </g>
+ <g id="shape106-123" v:mID="106" v:groupContext="shape" transform="translate(313.397,-18)">
+ <title>Sheet.106</title>
+ <path d="M0 226.35 C0 221.43 4.02 217.42 8.94 217.42 L347.02 217.42 C351.97 217.42 355.96 221.43 355.96 226.35 L355.96
+ 262.06 C355.96 267 351.97 270.98 347.02 270.98 L8.94 270.98 C4.02 270.98 0 267 0 262.06 L0 226.35 Z"
+ class="st6"/>
+ </g>
+ <g id="shape107-125" v:mID="107" v:groupContext="shape" transform="translate(313.98,-41.963)">
+ <title>Sheet.107</title>
+ <desc>Goal is to find a hash_index that produces</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="177.75" cy="260.197" width="355.5" height="21.5726"/>
+ <path d="M355.5 249.41 L0 249.41 L0 270.98 L355.5 270.98 L355.5 249.41" class="st3"/>
+ <text x="9.88" y="265.59" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Goal is to find a hash_index that produces </text> </g>
+ <g id="shape108-129" v:mID="108" v:groupContext="shape" transform="translate(318.48,-20.3939)">
+ <title>Sheet.108</title>
+ <desc>a lookup_table with no contradictions</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="175.5" cy="260.197" width="351" height="21.5726"/>
+ <path d="M351 249.41 L0 249.41 L0 270.98 L351 270.98 L351 249.41" class="st3"/>
+ <text x="28.12" y="265.59" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>a lookup_table with no contradictions</text> </g>
+ <g id="shape109-133" v:mID="109" v:groupContext="shape" transform="translate(18,-196.244)">
+ <title>Sheet.109</title>
+ <path d="M0 250.22 C0 247.92 1.87 246.06 4.16 246.06 L109.18 246.06 C111.47 246.06 113.33 247.92 113.33 250.22 L113.33
+ 266.83 C113.33 269.13 111.47 270.98 109.18 270.98 L4.16 270.98 C1.87 270.98 0 269.13 0 266.83 L0 250.22
+ Z" class="st1"/>
+ </g>
+ <g id="shape110-135" v:mID="110" v:groupContext="shape" transform="translate(29.8201,-196.244)">
+ <title>Sheet.110</title>
+ <path d="M0 250.22 C-0 247.92 1.67 246.06 3.73 246.06 L97.79 246.06 C99.85 246.06 101.51 247.92 101.51 250.22 L101.51
+ 266.83 C101.51 269.13 99.85 270.98 97.79 270.98 L3.73 270.98 C1.67 270.98 0 269.13 0 266.83 L0 250.22 Z"
+ class="st2"/>
+ </g>
+ <g id="shape111-137" v:mID="111" v:groupContext="shape" transform="translate(32.5663,-199.746)">
+ <title>Sheet.111</title>
+ <desc>Key1: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.7562" cy="263.792" width="101.52" height="14.3829"/>
+ <path d="M101.51 256.6 L0 256.6 L0 270.98 L101.51 270.98 L101.51 256.6" class="st3"/>
+ <text x="8.29" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1: Value = 0</text> </g>
+ <g id="shape112-141" v:mID="112" v:groupContext="shape" transform="translate(18,-171.44)">
+ <title>Sheet.112</title>
+ <path d="M0 250.22 C0 247.92 1.87 246.06 4.16 246.06 L109.18 246.06 C111.47 246.06 113.33 247.92 113.33 250.22 L113.33
+ 266.83 C113.33 269.13 111.47 270.98 109.18 270.98 L4.16 270.98 C1.87 270.98 0 269.13 0 266.83 L0 250.22
+ Z" class="st1"/>
+ </g>
+ <g id="shape113-143" v:mID="113" v:groupContext="shape" transform="translate(29.8201,-171.44)">
+ <title>Sheet.113</title>
+ <path d="M0 250.22 C0 247.92 1.67 246.06 3.73 246.06 L97.79 246.06 C99.85 246.06 101.51 247.92 101.51 250.22 L101.51
+ 266.83 C101.51 269.13 99.85 270.98 97.79 270.98 L3.73 270.98 C1.67 270.98 0 269.13 0 266.83 L0 250.22 Z"
+ class="st2"/>
+ </g>
+ <g id="shape114-145" v:mID="114" v:groupContext="shape" transform="translate(32.5663,-174.923)">
+ <title>Sheet.114</title>
+ <desc>Key3: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.7562" cy="263.792" width="101.52" height="14.3829"/>
+ <path d="M101.51 256.6 L0 256.6 L0 270.98 L101.51 270.98 L101.51 256.6" class="st3"/>
+ <text x="8.29" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3: Value = 1</text> </g>
+ <g id="shape115-149" v:mID="115" v:groupContext="shape" transform="translate(18,-146.396)">
+ <title>Sheet.115</title>
+ <path d="M0 250.12 C0 247.81 1.88 245.94 4.19 245.94 L109.15 245.94 C111.46 245.94 113.33 247.81 113.33 250.12 L113.33
+ 266.81 C113.33 269.12 111.46 270.98 109.15 270.98 L4.19 270.98 C1.88 270.98 0 269.12 0 266.81 L0 250.12
+ Z" class="st1"/>
+ </g>
+ <g id="shape116-151" v:mID="116" v:groupContext="shape" transform="translate(29.8201,-146.396)">
+ <title>Sheet.116</title>
+ <path d="M0 250.12 C0 247.81 1.68 245.94 3.75 245.94 L97.77 245.94 C99.84 245.94 101.51 247.81 101.51 250.12 L101.51
+ 266.81 C101.51 269.12 99.84 270.98 97.77 270.98 L3.75 270.98 C1.68 270.98 0 269.12 0 266.81 L0 250.12 Z"
+ class="st2"/>
+ </g>
+ <g id="shape117-153" v:mID="117" v:groupContext="shape" transform="translate(32.5663,-149.951)">
+ <title>Sheet.117</title>
+ <desc>Key4: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.7562" cy="263.792" width="101.52" height="14.3829"/>
+ <path d="M101.51 256.6 L0 256.6 L0 270.98 L101.51 270.98 L101.51 256.6" class="st3"/>
+ <text x="8.29" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4: Value = 0</text> </g>
+ <g id="shape118-157" v:mID="118" v:groupContext="shape" transform="translate(18,-121.831)">
+ <title>Sheet.118</title>
+ <path d="M0 250.12 C0 247.81 1.88 245.94 4.19 245.94 L109.15 245.94 C111.46 245.94 113.33 247.81 113.33 250.12 L113.33
+ 266.81 C113.33 269.12 111.46 270.98 109.15 270.98 L4.19 270.98 C1.88 270.98 0 269.12 0 266.81 L0 250.12
+ Z" class="st1"/>
+ </g>
+ <g id="shape119-159" v:mID="119" v:groupContext="shape" transform="translate(29.8201,-121.831)">
+ <title>Sheet.119</title>
+ <path d="M0 250.12 C0 247.81 1.68 245.94 3.75 245.94 L97.77 245.94 C99.84 245.94 101.51 247.81 101.51 250.12 L101.51
+ 266.81 C101.51 269.12 99.84 270.98 97.77 270.98 L3.75 270.98 C1.68 270.98 0 269.12 0 266.81 L0 250.12 Z"
+ class="st2"/>
+ </g>
+ <g id="shape120-161" v:mID="120" v:groupContext="shape" transform="translate(32.5663,-125.388)">
+ <title>Sheet.120</title>
+ <desc>Key7: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.7562" cy="263.792" width="101.52" height="14.3829"/>
+ <path d="M101.51 256.6 L0 256.6 L0 270.98 L101.51 270.98 L101.51 256.6" class="st3"/>
+ <text x="8.29" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7: Value = 1</text> </g>
+ <g id="shape121-165" v:mID="121" v:groupContext="shape" transform="translate(140.517,-148.373)">
+ <title>Sheet.121</title>
+ <path d="M0 236.29 L22.75 236.29 L22.75 224.73 L45.5 247.86 L22.75 270.98 L22.75 259.42 L0 259.42 L0 236.29 Z"
+ class="st6"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/index.rst b/doc/guides/prog_guide/index.rst
index ed7f770..7f825cb 100644
--- a/doc/guides/prog_guide/index.rst
+++ b/doc/guides/prog_guide/index.rst
@@ -47,6 +47,7 @@ Programmer's Guide
link_bonding_poll_mode_drv_lib
timer_lib
hash_lib
+ efd_lib
lpm_lib
lpm6_lib
packet_distrib_lib
@@ -167,6 +168,28 @@ Programmer's Guide
:numref:`figure_figure39` :ref:`figure_figure39`
+:numref:`figure_efd1` :ref:`figure_efd1`
+
+:numref:`figure_efd2` :ref:`figure_efd2`
+
+:numref:`figure_efd3` :ref:`figure_efd3`
+
+:numref:`figure_efd4` :ref:`figure_efd4`
+
+:numref:`figure_efd5` :ref:`figure_efd5`
+
+:numref:`figure_efd6` :ref:`figure_efd6`
+
+:numref:`figure_efd7` :ref:`figure_efd7`
+
+:numref:`figure_efd8` :ref:`figure_efd8`
+
+:numref:`figure_efd9` :ref:`figure_efd9`
+
+:numref:`figure_efd10` :ref:`figure_efd10`
+
+:numref:`figure_efd11` :ref:`figure_efd11`
+
**Tables**
diff --git a/doc/guides/rel_notes/release_17_02.rst b/doc/guides/rel_notes/release_17_02.rst
index 5023038..4c94513 100644
--- a/doc/guides/rel_notes/release_17_02.rst
+++ b/doc/guides/rel_notes/release_17_02.rst
@@ -64,6 +64,9 @@ New Features
is much smaller than a hash-based flow table and therefore, it can better fit for
CPU cache, being able to scale to millions of flow keys.
+ See the :ref:`Elastic Flow Distributor Library <Efd_Library>` documentation in
+ the Programmers Guide document, for more information.
+
Resolved Issues
---------------
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v5 5/5] doc: add flow distributor guide
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 0/5] Elastic Flow Distributor Pablo de Lara
` (3 preceding siblings ...)
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 4/5] doc: add EFD library section in Programmers guide Pablo de Lara
@ 2017-01-16 9:43 ` Pablo de Lara
2017-01-16 15:08 ` [dpdk-dev] [PATCH v5 0/5] Elastic Flow Distributor Thomas Monjalon
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 " Pablo de Lara
6 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-16 9:43 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Sameh Gobriel
Signed-off-by: Sameh Gobriel <sameh.gobriel@intel.com>
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 1 +
doc/guides/sample_app_ug/flow_distributor.rst | 494 ++++++++
doc/guides/sample_app_ug/img/flow_distributor.svg | 1254 +++++++++++++++++++++
doc/guides/sample_app_ug/index.rst | 1 +
4 files changed, 1750 insertions(+)
create mode 100644 doc/guides/sample_app_ug/flow_distributor.rst
create mode 100644 doc/guides/sample_app_ug/img/flow_distributor.svg
diff --git a/MAINTAINERS b/MAINTAINERS
index 66e9466..0d3b247 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -535,6 +535,7 @@ F: lib/librte_efd/
F: doc/guides/prog_guide/efd_lib.rst
F: app/test/test_efd*
F: examples/flow_distributor/
+F: doc/guides/sample_app_ug/flow_distributor.rst
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
diff --git a/doc/guides/sample_app_ug/flow_distributor.rst b/doc/guides/sample_app_ug/flow_distributor.rst
new file mode 100644
index 0000000..a8cd5f4
--- /dev/null
+++ b/doc/guides/sample_app_ug/flow_distributor.rst
@@ -0,0 +1,494 @@
+.. BSD LICENSE
+ Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of Intel Corporation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Flow Distributor Sample Application
+===================================
+
+This sample application demonstrates the use of EFD library as a flow-level
+load balancer, for more information about the EFD Library please refer to the
+DPDK programmer's guide.
+
+This sample application is a variant of the
+:ref:`client-server sample application <multi_process_app>`
+where a specific target node is specified for every and each flow
+(not in a round-robin fashion as the original load balancing sample application).
+
+Overview
+--------
+
+The architecture of the EFD flow-based load balancer sample application is
+presented in the following figure.
+
+.. _figure_efd_sample_app_overview:
+
+.. figure:: img/flow_distributor.*
+
+ Using EFD as a Flow-Level Load Balancer
+
+As shown in :numref:`_figure_efd_sample_app_overview`,
+the sample application consists of a front-end node (distributor)
+using the EFD library to create a load-balancing table for flows,
+for each flow a target backend worker node is specified. The EFD table does not
+store the flow key (unlike a regular hash table), and hence, it can
+individually load-balance millions of flows (number of targets * maximum number
+of flows fit in a flow table per target) while still fitting in CPU cache.
+
+It should be noted that although they are referred to as nodes, the frontend
+distributor and worker nodes are processes running on the same platform.
+
+Front-end Distributor
+~~~~~~~~~~~~~~~~~~~~~
+
+Upon initializing, the frontend distributor node (process) creates a flow
+distributor table (based on the EFD library) which is populated with flow
+information and its intended target node.
+
+The sample application assigns a specific target node_id (process) for each of
+the IP destination addresses as follows:
+
+.. code-block:: c
+
+ node_id = i % num_nodes; /* Target node id is generated */
+ ip_dst = rte_cpu_to_be_32(i); /* Specific ip destination address is
+ assigned to this target node */
+
+then the pair of <key,target> is inserted into the flow distribution table.
+
+The main loop of the the distributor node receives a burst of packets, then for
+each packet, a flow key (IP destination address) is extracted. The flow
+distributor table is looked up and the target node id is returned. Packets are
+then enqueued to the specified target node id.
+
+It should be noted that flow distributor table is not a membership test table.
+I.e. if the key has already been inserted the target node id will be correct,
+but for new keys the flow distributor table will return a value (which can be
+valid).
+
+Backend Worker Nodes
+~~~~~~~~~~~~~~~~~~~~
+
+Upon initializing, the worker node (process) creates a flow table (a regular
+hash table that stores the key default size 1M flows) which is populated with
+only the flow information that is serviced at this node. This flow key is
+essential to point out new keys that have not been inserted before.
+
+The worker node's main loop is simply receiving packets then doing a hash table
+lookup. If a match occurs then statistics are updated for flows serviced by
+this node. If no match is found in the local hash table then this indicates
+that this is a new flow, which is dropped.
+
+
+Compiling the Application
+-------------------------
+
+The sequence of steps used to build the application is:
+
+#. Export the required environment variables:
+
+ .. code-block:: console
+
+ export RTE_SDK=/path/to/rte_sdk
+ export RTE_TARGET=x86_64-native-linuxapp-gcc
+
+#. Build the application executable file:
+
+ .. code-block:: console
+
+ cd ${RTE_SDK}/examples/flow_distributor/
+ make
+
+ For more details on how to build the DPDK libraries and sample
+ applications,
+ please refer to the *DPDK Getting Started Guide.*
+
+
+Running the Application
+-----------------------
+
+The application has two binaries to be run: the front-end distributor
+and the back-end node.
+
+The frontend distributor (distributor) has the following command line options::
+
+ ./distributor [EAL options] -- -p PORTMASK -n NUM_NODES -f NUM_FLOWS
+
+Where,
+
+* ``-p PORTMASK:`` Hexadecimal bitmask of ports to configure
+* ``-n NUM_NODES:`` Number of back-end nodes that will be used
+* ``-f NUM_FLOWS:`` Number of flows to be added in the EFD table (1 million, by default)
+
+The back-end node (node) has the following command line options::
+
+ ./node [EAL options] -- -n NODE_ID
+
+Where,
+
+* ``-n NODE_ID:`` Node ID, which cannot be equal or higher than NUM_MODES
+
+
+First, the distributor app must be launched, with the number of nodes that will be run.
+Once it has been started, the node instances can be run, with different NODE_ID.
+These instances have to be run as secondary processes, with ``--proc-type=secondary``
+in the EAL options, which will attach to the primary process memory, and therefore,
+they can access the queues created by the primary process to distribute packets.
+
+To successfully run the application, the command line used to start the
+application has to be in sync with the traffic flows configured on the traffic
+generator side.
+
+For examples of application command lines and traffic generator flows, please
+refer to the DPDK Test Report. For more details on how to set up and run the
+sample applications provided with DPDK package, please refer to the
+:ref:`DPDK Getting Started Guide for Linux <linux_gsg>` and
+:ref:`DPDK Getting Started Guide for FreeBSD <freebsd_gsg>`.
+
+
+Explanation
+-----------
+
+As described in previous sections, there are two processes in this example.
+
+The first process, the front-end distributor, creates and populates the EFD table,
+which is used to distribute packets to nodes, which the number of flows
+specified in the command line (1 million, by default).
+
+
+.. code-block:: c
+
+ static void
+ create_flow_distributor_table(void)
+ {
+ uint8_t socket_id = rte_socket_id();
+
+ /* create table */
+ efd_table = rte_efd_create("flow table", num_flows * 2, sizeof(uint32_t),
+ 1 << socket_id, socket_id);
+
+ if (efd_table == NULL)
+ rte_exit(EXIT_FAILURE, "Problem creating the flow table\n");
+ }
+
+ static void
+ populate_flow_distributor_table(void)
+ {
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint8_t socket_id = rte_socket_id();
+ uint64_t node_id;
+
+ /* Add flows in table */
+ for (i = 0; i < num_flows; i++) {
+ node_id = i % num_nodes;
+
+ ip_dst = rte_cpu_to_be_32(i);
+ ret = rte_efd_update(efd_table, socket_id,
+ (void *)&ip_dst, (efd_value_t)node_id);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u in "
+ "flow distributor table\n", i);
+ }
+
+ printf("EFD table: Adding 0x%x keys\n", num_flows);
+ }
+
+After initialization, packets are received from the enabled ports, and the IPv4
+address from the packets is used as a key to look up in the EFD table,
+which tells the node where the packet has to be distributed.
+
+.. code-block:: c
+
+ static void
+ process_packets(uint32_t port_num __rte_unused, struct rte_mbuf *pkts[],
+ uint16_t rx_count, unsigned int socket_id)
+ {
+ uint16_t i;
+ uint8_t node;
+ efd_value_t data[EFD_BURST_MAX];
+ const void *key_ptrs[EFD_BURST_MAX];
+
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[EFD_BURST_MAX];
+
+ for (i = 0; i < rx_count; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(pkts[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = (void *)&ipv4_dst_ip[i];
+ }
+
+ rte_efd_lookup_bulk(efd_table, socket_id, rx_count,
+ (const void **) key_ptrs, data);
+ for (i = 0; i < rx_count; i++) {
+ node = (uint8_t) ((uintptr_t)data[i]);
+
+ if (node >= num_nodes) {
+ /*
+ * Node is out of range, which means that
+ * flow has not been inserted
+ */
+ flow_dist_stats.drop++;
+ rte_pktmbuf_free(pkts[i]);
+ } else {
+ flow_dist_stats.distributed++;
+ enqueue_rx_packet(node, pkts[i]);
+ }
+ }
+
+ for (i = 0; i < num_nodes; i++)
+ flush_rx_queue(i);
+ }
+
+The burst of packets received is enqueued in temporary buffers (per node),
+and enqueued in the shared ring between the distributor and the node.
+After this, a new burst of packets is received and this process is
+repeated infinitely.
+
+.. code-block:: c
+
+ static void
+ flush_rx_queue(uint16_t node)
+ {
+ uint16_t j;
+ struct node *cl;
+
+ if (cl_rx_buf[node].count == 0)
+ return;
+
+ cl = &nodes[node];
+ if (rte_ring_enqueue_bulk(cl->rx_q, (void **)cl_rx_buf[node].buffer,
+ cl_rx_buf[node].count) != 0){
+ for (j = 0; j < cl_rx_buf[node].count; j++)
+ rte_pktmbuf_free(cl_rx_buf[node].buffer[j]);
+ cl->stats.rx_drop += cl_rx_buf[node].count;
+ } else
+ cl->stats.rx += cl_rx_buf[node].count;
+
+ cl_rx_buf[node].count = 0;
+ }
+
+The second process, the back-end node, receives the packets from the shared
+ring with the distributor and send them out, if they belong to the node.
+
+At initialization, it attaches to the distributor process memory, to have
+access to the shared ring, parameters and statistics.
+
+.. code-block:: c
+
+ rx_ring = rte_ring_lookup(get_rx_queue_name(node_id));
+ if (rx_ring == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get RX ring - "
+ "is distributor process running?\n");
+
+ mp = rte_mempool_lookup(PKTMBUF_POOL_NAME);
+ if (mp == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get mempool for mbufs\n");
+
+ mz = rte_memzone_lookup(MZ_SHARED_INFO);
+ if (mz == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get port info structure\n");
+ info = mz->addr;
+ tx_stats = &(info->tx_stats[node_id]);
+ filter_stats = &(info->filter_stats[node_id]);
+
+Then, the hash table that contains the flows that will be handled
+by the node is created and populated.
+
+.. code-block:: c
+
+ static struct rte_hash *
+ create_hash_table(const struct shared_info *info)
+ {
+ uint32_t num_flows_node = info->num_flows / info->num_nodes;
+ char name[RTE_HASH_NAMESIZE];
+ struct rte_hash *h;
+
+ /* create table */
+ struct rte_hash_parameters hash_params = {
+ .entries = num_flows_node * 2, /* table load = 50% */
+ .key_len = sizeof(uint32_t), /* Store IPv4 dest IP address */
+ .socket_id = rte_socket_id(),
+ .hash_func_init_val = 0,
+ };
+
+ snprintf(name, sizeof(name), "hash_table_%d", node_id);
+ hash_params.name = name;
+ h = rte_hash_create(&hash_params);
+
+ if (h == NULL)
+ rte_exit(EXIT_FAILURE,
+ "Problem creating the hash table for node %d\n",
+ node_id);
+ return h;
+ }
+
+ static void
+ populate_hash_table(const struct rte_hash *h, const struct shared_info *info)
+ {
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint32_t num_flows_node = 0;
+ uint64_t target_node;
+
+ /* Add flows in table */
+ for (i = 0; i < info->num_flows; i++) {
+ target_node = i % info->num_nodes;
+ if (target_node != node_id)
+ continue;
+
+ ip_dst = rte_cpu_to_be_32(i);
+
+ ret = rte_hash_add_key(h, (void *) &ip_dst);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u "
+ "in hash table\n", i);
+ else
+ num_flows_node++;
+
+ }
+
+ printf("Hash table: Adding 0x%x keys\n", num_flows_node);
+ }
+
+After initialization, packets are dequeued from the shared ring
+(from the distributor) and, like in the distributor process,
+the IPv4 address from the packets is used as a key to look up in the hash table.
+If there is a hit, packet is stored in a buffer, to be eventually transmitted
+in one of the enabled ports. If key is not there, packet is dropped, since the
+flow is not handled by the node.
+
+.. code-block:: c
+
+ static inline void
+ handle_packets(struct rte_hash *h, struct rte_mbuf **bufs, uint16_t num_packets)
+ {
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[PKT_READ_SIZE];
+ const void *key_ptrs[PKT_READ_SIZE];
+ unsigned int i;
+ int32_t positions[PKT_READ_SIZE] = {0};
+
+ for (i = 0; i < num_packets; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(bufs[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = &ipv4_dst_ip[i];
+ }
+ /* Check if packets belongs to any flows handled by this node */
+ rte_hash_lookup_bulk(h, key_ptrs, num_packets, positions);
+
+ for (i = 0; i < num_packets; i++) {
+ if (likely(positions[i] >= 0)) {
+ filter_stats->passed++;
+ transmit_packet(bufs[i]);
+ } else {
+ filter_stats->drop++;
+ /* Drop packet, as flow is not handled by this node */
+ rte_pktmbuf_free(bufs[i]);
+ }
+ }
+ }
+
+Finally, note that both processes updates statistics, such as transmitted, received
+and dropped packets, which are shown and refreshed by the distributor app.
+
+.. code-block:: c
+
+ static void
+ do_stats_display(void)
+ {
+ unsigned int i, j;
+ const char clr[] = {27, '[', '2', 'J', '\0'};
+ const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
+ uint64_t port_tx[RTE_MAX_ETHPORTS], port_tx_drop[RTE_MAX_ETHPORTS];
+ uint64_t node_tx[MAX_NODES], node_tx_drop[MAX_NODES];
+
+ /* to get TX stats, we need to do some summing calculations */
+ memset(port_tx, 0, sizeof(port_tx));
+ memset(port_tx_drop, 0, sizeof(port_tx_drop));
+ memset(node_tx, 0, sizeof(node_tx));
+ memset(node_tx_drop, 0, sizeof(node_tx_drop));
+
+ for (i = 0; i < num_nodes; i++) {
+ const struct tx_stats *tx = &info->tx_stats[i];
+
+ for (j = 0; j < info->num_ports; j++) {
+ const uint64_t tx_val = tx->tx[info->id[j]];
+ const uint64_t drop_val = tx->tx_drop[info->id[j]];
+
+ port_tx[j] += tx_val;
+ port_tx_drop[j] += drop_val;
+ node_tx[i] += tx_val;
+ node_tx_drop[i] += drop_val;
+ }
+ }
+
+ /* Clear screen and move to top left */
+ printf("%s%s", clr, topLeft);
+
+ printf("PORTS\n");
+ printf("-----\n");
+ for (i = 0; i < info->num_ports; i++)
+ printf("Port %u: '%s'\t", (unsigned int)info->id[i],
+ get_printable_mac_addr(info->id[i]));
+ printf("\n\n");
+ for (i = 0; i < info->num_ports; i++) {
+ printf("Port %u - rx: %9"PRIu64"\t"
+ "tx: %9"PRIu64"\n",
+ (unsigned int)info->id[i], info->rx_stats.rx[i],
+ port_tx[i]);
+ }
+
+ printf("\nFLOW DISTRIBUTOR\n");
+ printf("-----\n");
+ printf("distributed: %9"PRIu64", drop: %9"PRIu64"\n",
+ flow_dist_stats.distributed, flow_dist_stats.drop);
+
+ printf("\nNODES\n");
+ printf("-------\n");
+ for (i = 0; i < num_nodes; i++) {
+ const unsigned long long rx = nodes[i].stats.rx;
+ const unsigned long long rx_drop = nodes[i].stats.rx_drop;
+ const struct filter_stats *filter = &info->filter_stats[i];
+
+ printf("Node %2u - rx: %9llu, rx_drop: %9llu\n"
+ " tx: %9"PRIu64", tx_drop: %9"PRIu64"\n"
+ " filter_passed: %9"PRIu64", "
+ "filter_drop: %9"PRIu64"\n",
+ i, rx, rx_drop, node_tx[i], node_tx_drop[i],
+ filter->passed, filter->drop);
+ }
+
+ printf("\n");
+ }
diff --git a/doc/guides/sample_app_ug/img/flow_distributor.svg b/doc/guides/sample_app_ug/img/flow_distributor.svg
new file mode 100644
index 0000000..9aee30b
--- /dev/null
+++ b/doc/guides/sample_app_ug/img/flow_distributor.svg
@@ -0,0 +1,1254 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i6.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="8.2496in" height="5.89673in"
+ viewBox="0 0 593.971 424.565" xml:space="preserve" color-interpolation-filters="sRGB" class="st27">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st5 {fill:#feffff;font-family:Calibri;font-size:0.75em}
+ .st6 {fill:none;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st7 {fill:none;stroke:#2e75b5;stroke-width:2.25}
+ .st8 {fill:#305497;stroke:#2e75b5;stroke-width:1}
+ .st9 {fill:#feffff;font-family:Calibri;font-size:0.833336em;font-weight:bold}
+ .st10 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2)}
+ .st11 {fill:#5b9bd5}
+ .st12 {stroke:#c7c8c8;stroke-width:0.25}
+ .st13 {fill:#acccea;stroke:#c7c8c8;stroke-width:0.25}
+ .st14 {fill:#feffff;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st15 {fill:#ed7d31;stroke:#c7c8c8;stroke-width:0.25}
+ .st16 {fill:#deebf6;stroke:#c7c8c8;stroke-width:0.25}
+ .st17 {marker-end:url(#mrkr5-212);stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st18 {fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st19 {fill:none;stroke:#2e75b5;stroke-width:1}
+ .st20 {fill:#5b9bd5;font-family:Calibri;font-size:1.00001em}
+ .st21 {fill:none;stroke:none;stroke-width:0.25}
+ .st22 {font-size:1em}
+ .st23 {fill:#ffffff}
+ .st24 {stroke:#5b9bd5;stroke-dasharray:1.5,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st25 {marker-end:url(#mrkr5-444);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st26 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st27 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-212" class="st18" v:arrowType="5" v:arrowSize="2" v:setback="5.8" refX="-5.8" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ <marker id="mrkr5-444" class="st26" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(319.501,-335.688)">
+ <title>Rectangle.58</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow3-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape4-7" v:mID="4" v:groupContext="shape" transform="translate(353.251,-335.688)">
+ <title>Rectangle.59</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow4-8" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape5-13" v:mID="5" v:groupContext="shape" transform="translate(400.501,-335.688)">
+ <title>Rectangle.60</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow5-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape6-19" v:mID="6" v:groupContext="shape" transform="translate(434.251,-335.688)">
+ <title>Rectangle.61</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow6-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape7-25" v:mID="7" v:groupContext="shape" transform="translate(481.501,-335.688)">
+ <title>Rectangle.62</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow7-26" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape8-30" v:mID="8" v:groupContext="shape" transform="translate(515.251,-335.688)">
+ <title>Rectangle.63</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow8-31" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape9-35" v:mID="9" v:groupContext="shape" transform="translate(319.501,-313.188)">
+ <title>Rectangle.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow9-36" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape10-40" v:mID="10" v:groupContext="shape" transform="translate(353.251,-313.188)">
+ <title>Rectangle.65</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow10-41" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape11-45" v:mID="11" v:groupContext="shape" transform="translate(400.501,-313.188)">
+ <title>Rectangle.66</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow11-46" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape12-50" v:mID="12" v:groupContext="shape" transform="translate(434.251,-313.188)">
+ <title>Rectangle.67</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow12-51" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape13-55" v:mID="13" v:groupContext="shape" transform="translate(481.501,-313.188)">
+ <title>Rectangle.68</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow13-56" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape14-60" v:mID="14" v:groupContext="shape" transform="translate(515.251,-313.188)">
+ <title>Rectangle.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow14-61" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape15-65" v:mID="15" v:groupContext="shape" transform="translate(319.501,-277.188)">
+ <title>Rectangle.70</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow15-66" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape16-71" v:mID="16" v:groupContext="shape" transform="translate(353.251,-277.188)">
+ <title>Rectangle.71</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow16-72" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape17-77" v:mID="17" v:groupContext="shape" transform="translate(400.501,-277.188)">
+ <title>Rectangle.72</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow17-78" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape18-83" v:mID="18" v:groupContext="shape" transform="translate(434.251,-277.188)">
+ <title>Rectangle.73</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow18-84" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape19-89" v:mID="19" v:groupContext="shape" transform="translate(481.501,-277.188)">
+ <title>Rectangle.74</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow19-90" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape20-95" v:mID="20" v:groupContext="shape" transform="translate(515.251,-277.188)">
+ <title>Rectangle.75</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow20-96" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape21-101" v:mID="21" v:groupContext="shape" transform="translate(319.501,-240.687)">
+ <title>Rectangle.76</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow21-102" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape22-106" v:mID="22" v:groupContext="shape" transform="translate(353.251,-240.687)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow22-107" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape23-111" v:mID="23" v:groupContext="shape" transform="translate(400.501,-240.687)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow23-112" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape24-116" v:mID="24" v:groupContext="shape" transform="translate(434.251,-240.687)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow24-117" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape25-121" v:mID="25" v:groupContext="shape" transform="translate(481.501,-240.687)">
+ <title>Rectangle.80</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow25-122" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape26-127" v:mID="26" v:groupContext="shape" transform="translate(515.251,-240.687)">
+ <title>Rectangle.81</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow26-128" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape27-133" v:mID="27" v:groupContext="shape" transform="translate(317.251,-231.687)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow27-134" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st6"/>
+ </g>
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st7"/>
+ </g>
+ <g id="shape28-138" v:mID="28" v:groupContext="shape" transform="translate(328.501,-362.688)">
+ <title>Sheet.28</title>
+ <desc>Local Table for N Specific Flows Serviced at Node 1</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="110.423" cy="418.94" width="220.85" height="11.25"/>
+ <rect x="0" y="413.315" width="220.846" height="11.25" class="st8"/>
+ <text x="5.77" y="421.94" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Local Table for N Specific Flows Serviced at Node 1</text> </g>
+ <g id="group34-141" transform="translate(66.0294,-165.569)" v:mID="34" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Device)"/>
+ <v:cp v:nameU="SubShapeType" v:lbl="SubShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Load balancer)"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Load balancer</title>
+ <g id="shape35-142" v:mID="35" v:groupContext="shape" transform="translate(0,-7.33146)">
+ <title>Sheet.35</title>
+ <g id="shadow35-143" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23 Z" class="st10"/>
+ <path d="M0 377.86 L72 377.86" class="st6"/>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23" class="st6"/>
+ </g>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23 Z" class="st11"/>
+ <path d="M0 377.86 L72 377.86" class="st12"/>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23" class="st12"/>
+ </g>
+ <g id="shape36-152" v:mID="36" v:groupContext="shape" transform="translate(8.03054,-12.9324)">
+ <title>Sheet.36</title>
+ <g id="shadow36-153" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M45.34 421.81 L41.2 422.66 L44.12 424.56 L49.68 423.16 L48.75 417.51 L45.8 415.59 L46.69 419.68 L36.97
+ 413.34 L35.6 415.45 L45.34 421.81 ZM50.83 405.36 L39.2 405.36 L39.2 407.88 L50.8 407.88 L47.82 410.83
+ L51.34 410.83 L55.21 406.61 L51.32 402.39 L47.83 402.39 L50.83 405.36 ZM46.49 392.01 L36.75 398.37
+ L38.13 400.48 L47.84 394.14 L46.96 398.23 L49.91 396.31 L50.84 390.66 L45.28 389.26 L42.36 391.16
+ L46.49 392.01 ZM27.71 397.16 C22.66 397.16 18.58 401.25 18.58 406.29 C18.58 411.33 22.66 415.42
+ 27.71 415.42 C32.75 415.42 36.84 411.33 36.84 406.29 C36.84 401.25 32.75 397.16 27.71 397.16 ZM27.71
+ 400.04 C31.15 400.04 33.96 402.84 33.96 406.29 C33.96 409.74 31.15 412.54 27.71 412.54 C24.26 412.54
+ 21.46 409.74 21.46 406.29 C21.46 402.84 24.26 400.04 27.71 400.04 ZM11.64 405.04 L0 405.04 L0 407.56
+ L11.6 407.56 L8.62 410.51 L12.14 410.51 L16.01 406.29 L12.12 402.07 L8.64 402.07 L11.64 405.04 Z"
+ class="st2"/>
+ </g>
+ <path d="M45.34 421.81 L41.2 422.66 L44.12 424.56 L49.68 423.16 L48.75 417.51 L45.8 415.59 L46.69 419.68 L36.97 413.34
+ L35.6 415.45 L45.34 421.81 ZM50.83 405.36 L39.2 405.36 L39.2 407.88 L50.8 407.88 L47.82 410.83 L51.34
+ 410.83 L55.21 406.61 L51.32 402.39 L47.83 402.39 L50.83 405.36 ZM46.49 392.01 L36.75 398.37 L38.13 400.48
+ L47.84 394.14 L46.96 398.23 L49.91 396.31 L50.84 390.66 L45.28 389.26 L42.36 391.16 L46.49 392.01 ZM27.71
+ 397.16 C22.66 397.16 18.58 401.25 18.58 406.29 C18.58 411.33 22.66 415.42 27.71 415.42 C32.75 415.42
+ 36.84 411.33 36.84 406.29 C36.84 401.25 32.75 397.16 27.71 397.16 ZM27.71 400.04 C31.15 400.04 33.96
+ 402.84 33.96 406.29 C33.96 409.74 31.15 412.54 27.71 412.54 C24.26 412.54 21.46 409.74 21.46 406.29
+ C21.46 402.84 24.26 400.04 27.71 400.04 ZM11.64 405.04 L0 405.04 L0 407.56 L11.6 407.56 L8.62 410.51
+ L12.14 410.51 L16.01 406.29 L12.12 402.07 L8.64 402.07 L11.64 405.04 Z" class="st13"/>
+ </g>
+ </g>
+ <g id="shape37-157" v:mID="37" v:groupContext="shape" transform="translate(21.0294,-45.4375)">
+ <title>Rectangle</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow37-158" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="336.433" width="135" height="88.1315" class="st2"/>
+ </g>
+ <rect x="0" y="336.433" width="135" height="88.1315" class="st3"/>
+ </g>
+ <g id="shape38-162" v:mID="38" v:groupContext="shape" transform="translate(34.693,-126.438)">
+ <title>Sheet.38</title>
+ <desc>EFD Table</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="49.3364" cy="415.565" width="98.68" height="18"/>
+ <rect x="0" y="406.565" width="98.6728" height="18" class="st8"/>
+ <text x="24.87" y="419.17" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>EFD Table</text> </g>
+ <g id="shape39-165" v:mID="39" v:groupContext="shape" transform="translate(30.0294,-99.4375)">
+ <title>Rectangle.39</title>
+ <desc>Group_id</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="26.9182" cy="415.565" width="53.84" height="18"/>
+ <g id="shadow39-166" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st15"/>
+ <text x="7.87" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group_id</text> </g>
+ <g id="shape40-171" v:mID="40" v:groupContext="shape" transform="translate(93.193,-99.4375)">
+ <title>Rectangle.40</title>
+ <desc>Hash index</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="26.9182" cy="415.565" width="53.84" height="18"/>
+ <g id="shadow40-172" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st15"/>
+ <text x="4.64" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Hash index</text> </g>
+ <g id="shape41-177" v:mID="41" v:groupContext="shape" transform="translate(30.193,-82.4275)">
+ <title>Rectangle.41</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow41-178" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape42-182" v:mID="42" v:groupContext="shape" transform="translate(30.193,-66.8125)">
+ <title>Rectangle.42</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow42-183" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape43-187" v:mID="43" v:groupContext="shape" transform="translate(30.1112,-52.1875)">
+ <title>Rectangle.43</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow43-188" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape44-192" v:mID="44" v:groupContext="shape" transform="translate(93.0294,-81.4375)">
+ <title>Rectangle.44</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow44-193" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape45-197" v:mID="45" v:groupContext="shape" transform="translate(93.193,-66.8125)">
+ <title>Rectangle.45</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow45-198" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape46-202" v:mID="46" v:groupContext="shape" transform="translate(93.193,-52.1875)">
+ <title>Rectangle.46</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow46-203" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape47-207" v:mID="47" v:groupContext="shape" transform="translate(374.924,544.022) rotate(135)">
+ <title>Sheet.47</title>
+ <path d="M-0 417.75 A40.674 18.0151 -156.2 0 0 40.24 422.15 L40.49 421.89" class="st17"/>
+ </g>
+ <g id="shape48-213" v:mID="48" v:groupContext="shape" transform="translate(21.0294,-19)">
+ <title>Sheet.48</title>
+ <desc>Supports X*N Flows</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="415.565" width="135" height="18"/>
+ <rect x="0" y="406.565" width="135" height="18" class="st19"/>
+ <text x="19.05" y="419.17" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Supports X*N Flows</text> </g>
+ <g id="shape49-216" v:mID="49" v:groupContext="shape" transform="translate(48.0294,-229.938)">
+ <title>Sheet.49</title>
+ <desc>Frontend Server or Load Balancer</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="400.94" width="108" height="47.25"/>
+ <rect x="0" y="377.315" width="108" height="47.25" class="st21"/>
+ <text x="14.56" y="397.34" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Frontend Server<v:newlineChar/><tspan
+ x="13.16" dy="1.2em" class="st22">or Load Balancer </tspan> </text> </g>
+ <g id="group51-220" transform="translate(223.876,-310.938)" v:mID="51" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server</title>
+ <g id="shape52-221" v:mID="52" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.52</title>
+ <g id="shadow52-222" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape53-226" v:mID="53" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.53</title>
+ <g id="shadow53-227" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape54-231" v:mID="54" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.54</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow54-232" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape59-239" v:mID="59" v:groupContext="shape" transform="translate(277.876,-373.938)">
+ <title>Sheet.59</title>
+ <path d="M-0 424.56 A111.108 53.2538 42.31 0 1 93.83 421.21 L94.14 421.41" class="st17"/>
+ </g>
+ <g id="shape60-244" v:mID="60" v:groupContext="shape" transform="translate(205.876,-283.938)">
+ <title>Sheet.60</title>
+ <desc>Backend Server 1</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.93" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server 1</text> </g>
+ <g id="group61-247" transform="translate(223.876,-207.438)" v:mID="61" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server.61</title>
+ <g id="shape62-248" v:mID="62" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.62</title>
+ <g id="shadow62-249" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape63-253" v:mID="63" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.63</title>
+ <g id="shadow63-254" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape64-258" v:mID="64" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow64-259" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape65-266" v:mID="65" v:groupContext="shape" transform="translate(205.876,-180.437)">
+ <title>Sheet.65</title>
+ <desc>Backend Server 2</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.93" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server 2</text> </g>
+ <g id="group66-269" transform="translate(219.029,-58.9375)" v:mID="66" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server.66</title>
+ <g id="shape67-270" v:mID="67" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.67</title>
+ <g id="shadow67-271" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape68-275" v:mID="68" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.68</title>
+ <g id="shadow68-276" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape69-280" v:mID="69" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow69-281" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape70-288" v:mID="70" v:groupContext="shape" transform="translate(201.029,-26.056)">
+ <title>Sheet.70</title>
+ <desc>Backend Server X</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.86" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server X</text> </g>
+ <g id="shape71-291" v:mID="71" v:groupContext="shape" transform="translate(684.44,239.627) rotate(90)">
+ <title>Sheet.71</title>
+ <path d="M0 424.56 L45 424.56" class="st24"/>
+ </g>
+ <g id="shape72-294" v:mID="72" v:groupContext="shape" transform="translate(6.85967,-22.443) rotate(-38.1076)">
+ <title>Sheet.72</title>
+ <path d="M-0 424.56 A96.1331 44.4001 55.03 0 1 68.24 420.56 L68.51 420.79" class="st17"/>
+ </g>
+ <g id="shape73-299" v:mID="73" v:groupContext="shape" transform="translate(328.501,-135.937)">
+ <title>Rectangle.73</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow73-300" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape74-305" v:mID="74" v:groupContext="shape" transform="translate(362.251,-135.937)">
+ <title>Rectangle.74</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow74-306" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape75-311" v:mID="75" v:groupContext="shape" transform="translate(409.501,-135.937)">
+ <title>Rectangle.75</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow75-312" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape76-317" v:mID="76" v:groupContext="shape" transform="translate(443.251,-135.937)">
+ <title>Rectangle.76</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow76-318" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape77-323" v:mID="77" v:groupContext="shape" transform="translate(490.501,-135.937)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow77-324" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape78-328" v:mID="78" v:groupContext="shape" transform="translate(524.251,-135.937)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow78-329" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape79-333" v:mID="79" v:groupContext="shape" transform="translate(328.501,-113.437)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow79-334" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape80-338" v:mID="80" v:groupContext="shape" transform="translate(362.251,-113.437)">
+ <title>Rectangle.80</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow80-339" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape81-343" v:mID="81" v:groupContext="shape" transform="translate(409.501,-113.437)">
+ <title>Rectangle.81</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow81-344" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape82-348" v:mID="82" v:groupContext="shape" transform="translate(443.251,-113.437)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow82-349" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape83-353" v:mID="83" v:groupContext="shape" transform="translate(490.501,-113.437)">
+ <title>Rectangle.83</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow83-354" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape84-358" v:mID="84" v:groupContext="shape" transform="translate(524.251,-113.437)">
+ <title>Rectangle.84</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow84-359" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape85-363" v:mID="85" v:groupContext="shape" transform="translate(328.501,-77.4375)">
+ <title>Rectangle.85</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow85-364" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape86-369" v:mID="86" v:groupContext="shape" transform="translate(362.251,-77.4375)">
+ <title>Rectangle.86</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow86-370" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape87-375" v:mID="87" v:groupContext="shape" transform="translate(409.501,-77.4375)">
+ <title>Rectangle.87</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow87-376" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape88-381" v:mID="88" v:groupContext="shape" transform="translate(443.251,-77.4375)">
+ <title>Rectangle.88</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow88-382" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape89-387" v:mID="89" v:groupContext="shape" transform="translate(490.501,-77.4375)">
+ <title>Rectangle.89</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow89-388" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape90-393" v:mID="90" v:groupContext="shape" transform="translate(524.251,-77.4375)">
+ <title>Rectangle.90</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow90-394" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape91-399" v:mID="91" v:groupContext="shape" transform="translate(328.501,-40.9375)">
+ <title>Rectangle.91</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow91-400" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape92-404" v:mID="92" v:groupContext="shape" transform="translate(362.251,-40.9375)">
+ <title>Rectangle.92</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow92-405" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape93-409" v:mID="93" v:groupContext="shape" transform="translate(409.501,-40.9375)">
+ <title>Rectangle.93</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow93-410" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape94-414" v:mID="94" v:groupContext="shape" transform="translate(443.251,-40.9375)">
+ <title>Rectangle.94</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow94-415" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape95-419" v:mID="95" v:groupContext="shape" transform="translate(490.501,-40.9375)">
+ <title>Rectangle.95</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow95-420" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape96-425" v:mID="96" v:groupContext="shape" transform="translate(524.251,-40.9375)">
+ <title>Rectangle.96</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow96-426" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape97-431" v:mID="97" v:groupContext="shape" transform="translate(326.251,-31.9375)">
+ <title>Rectangle.97</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow97-432" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st6"/>
+ </g>
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st7"/>
+ </g>
+ <g id="shape98-436" v:mID="98" v:groupContext="shape" transform="translate(337.501,-162.938)">
+ <title>Sheet.98</title>
+ <desc>Local Table for N Specific Flows Serviced at Node X</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="110.423" cy="418.94" width="220.85" height="11.25"/>
+ <rect x="0" y="413.315" width="220.846" height="11.25" class="st8"/>
+ <text x="5.55" y="421.94" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Local Table for N Specific Flows Serviced at Node X</text> </g>
+ <g id="shape99-439" v:mID="99" v:groupContext="shape" transform="translate(-204.342,-29.4449) rotate(-53.7462)">
+ <title>Sheet.99</title>
+ <path d="M0 424.56 L160.37 424.56" class="st25"/>
+ </g>
+ <g id="shape100-445" v:mID="100" v:groupContext="shape" transform="translate(-37.6568,-164.882) rotate(-24.444)">
+ <title>Sheet.100</title>
+ <path d="M0 424.56 L101.71 424.56" class="st25"/>
+ </g>
+ <g id="shape101-450" v:mID="101" v:groupContext="shape" transform="translate(464.049,-50.8578) rotate(50.099)">
+ <title>Sheet.101</title>
+ <path d="M0 424.56 L139.8 424.56" class="st25"/>
+ </g>
+ <g id="shape102-455" v:mID="102" v:groupContext="shape" transform="translate(372.376,-207.438)">
+ <title>Sheet.102</title>
+ <desc>Supports N Flows</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="415.565" width="135" height="18"/>
+ <rect x="0" y="406.565" width="135" height="18" class="st19"/>
+ <text x="25.15" y="419.17" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Supports N Flows</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/sample_app_ug/index.rst b/doc/guides/sample_app_ug/index.rst
index 775e2f7..260f6a5 100644
--- a/doc/guides/sample_app_ug/index.rst
+++ b/doc/guides/sample_app_ug/index.rst
@@ -57,6 +57,7 @@ Sample Applications User Guides
l3_forward_virtual
link_status_intr
load_balancer
+ flow_distributor
multi_process
qos_metering
qos_scheduler
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [dpdk-dev] [PATCH v5 0/5] Elastic Flow Distributor
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 0/5] Elastic Flow Distributor Pablo de Lara
` (4 preceding siblings ...)
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 5/5] doc: add flow distributor guide Pablo de Lara
@ 2017-01-16 15:08 ` Thomas Monjalon
2017-01-17 8:34 ` De Lara Guarch, Pablo
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 " Pablo de Lara
6 siblings, 1 reply; 63+ messages in thread
From: Thomas Monjalon @ 2017-01-16 15:08 UTC (permalink / raw)
To: Pablo de Lara; +Cc: dev
Unfortunately, it does not build:
rte_efd.o: In function `rte_efd_create':
lib/librte_efd/rte_efd.c:(.text+0xed): undefined reference to `log2'
lib/librte_efd/rte_efd.c:(.text+0x51b): undefined reference to `rte_ring_create'
rte_efd.o: In function `rte_efd_free':
lib/librte_efd/rte_efd.c:(.text+0x739): undefined reference to `rte_ring_free'
Configuration tested: x86_64-native-linuxapp-clang+shared+next+debug
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [dpdk-dev] [PATCH v4 4/5] doc: add EFD library section in Programmers guide
2017-01-16 4:15 ` Jerin Jacob
@ 2017-01-16 15:33 ` De Lara Guarch, Pablo
0 siblings, 0 replies; 63+ messages in thread
From: De Lara Guarch, Pablo @ 2017-01-16 15:33 UTC (permalink / raw)
To: Jerin Jacob; +Cc: dev, Gobriel, Sameh
Hi Jerin,
> -----Original Message-----
> From: Jerin Jacob [mailto:jerin.jacob@caviumnetworks.com]
> Sent: Monday, January 16, 2017 4:15 AM
> To: De Lara Guarch, Pablo
> Cc: dev@dpdk.org; Gobriel, Sameh
> Subject: Re: [dpdk-dev] [PATCH v4 4/5] doc: add EFD library section in
> Programmers guide
>
> On Sun, Jan 15, 2017 at 12:04:34PM +0000, Pablo de Lara wrote:
> > Signed-off-by: Sameh Gobriel <sameh.gobriel@intel.com>
> > Acked-by: Christian Maciocco <christian.maciocco@intel.com>
>
> Looks like trailing white space error on git am. Please check it
>
> ➜ [dpdk] $ pwclient git-am 19363
> Applying patch #19363 using 'git am'
> Description: [dpdk-dev,v4,4/5] doc: add EFD library section in
> Programmers guide
> Applying: doc: add EFD library section in Programmers guide
> /export/upstream/dpdk/.git/rebase-apply/patch:342: trailing whitespace.
> ``rte_efd_delete()`` can be used. The function returns zero upon success
> warning: 1 line adds whitespace errors.
Thanks, I saw it earlier and I sent a new version fixing that.
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [dpdk-dev] [PATCH v4 1/5] efd: new Elastic Flow Distributor library
2017-01-16 4:25 ` Jerin Jacob
@ 2017-01-16 15:34 ` De Lara Guarch, Pablo
0 siblings, 0 replies; 63+ messages in thread
From: De Lara Guarch, Pablo @ 2017-01-16 15:34 UTC (permalink / raw)
To: Jerin Jacob; +Cc: dev, Marohn, Byron, Edupuganti, Saikrishna
> -----Original Message-----
> From: Jerin Jacob [mailto:jerin.jacob@caviumnetworks.com]
> Sent: Monday, January 16, 2017 4:26 AM
> To: De Lara Guarch, Pablo
> Cc: dev@dpdk.org; Marohn, Byron; Edupuganti, Saikrishna
> Subject: Re: [dpdk-dev] [PATCH v4 1/5] efd: new Elastic Flow Distributor
> library
>
> On Sun, Jan 15, 2017 at 12:04:31PM +0000, Pablo de Lara wrote:
> > Elastic Flow Distributor (EFD) is a distributor library that uses
> > perfect hashing to determine a target/value for a given incoming flow key.
> > It has the following advantages:
> >
> > - First, because it uses perfect hashing, it does not store
> > the key itself and hence lookup performance is not dependent
> > on the key size.
> >
> > - Second, the target/value can be any arbitrary value hence
> > the system designer and/or operator can better optimize service rates
> > and inter-cluster network traffic locating.
> >
> > - Third, since the storage requirement is much smaller than a hash-based
> > flow table (i.e. better fit for CPU cache), EFD can scale to
> > millions of flow keys.
> > Finally, with current optimized library implementation performance
> > is fully scalable with number of CPU cores.
> >
> > Signed-off-by: Byron Marohn <byron.marohn@intel.com>
> > Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
> > Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
> > Acked-by: Christian Maciocco <christian.maciocco@intel.com>
> > ---
> > +#if (RTE_EFD_VALUE_NUM_BITS == 8 || RTE_EFD_VALUE_NUM_BITS ==
> 16 || \
> > + RTE_EFD_VALUE_NUM_BITS == 24 || RTE_EFD_VALUE_NUM_BITS
> == 32)
> > +#define EFD_LOAD_SI128(val) _mm_load_si128(val)
> > +#else
> > +#define EFD_LOAD_SI128(val) _mm_lddqu_si128(val)
> > +#endif
> > +
> > +static inline efd_value_t
> > +efd_lookup_internal(const struct efd_online_group_entry * const
> group,
> > + const uint32_t hash_val_a, const uint32_t hash_val_b,
> > + enum rte_efd_compare_function cmp_fn)
> > +{
> > + efd_value_t value = 0;
> > + uint32_t i;
> > +
> > + switch (cmp_fn) {
> > +#ifdef RTE_MACHINE_CPUFLAG_AVX2
> > + case RTE_HASH_COMPARE_AVX2:
> > +
> > + i = 0;
> > + __m256i vhash_val_a = _mm256_set1_epi32(hash_val_a);
> > + __m256i vhash_val_b = _mm256_set1_epi32(hash_val_b);
> > +
>
> Could you please abstract and move SIMD specific code to another file like
> other
> libraries(example: lib_acl) to enable smooth integration with neon and
> altivec
> SIMD implementations in future.
Sure, will modify it and send another version shortly.
Thanks,
Pablo
>
> > + for (; i < RTE_EFD_VALUE_NUM_BITS; i += 8) {
> > + __m256i vhash_idx =
> > +
> _mm256_cvtepu16_epi32(EFD_LOAD_SI128(
> > + (__m128i const *) &group-
> >hash_idx[i]));
> > + __m256i vlookup_table =
> _mm256_cvtepu16_epi32(
> > + EFD_LOAD_SI128((__m128i const *)
> > + &group->lookup_table[i]));
> > + __m256i vhash =
> _mm256_add_epi32(vhash_val_a,
> > + _mm256_mullo_epi32(vhash_idx,
> vhash_val_b));
> > + __m256i vbucket_idx = _mm256_srli_epi32(vhash,
> > + EFD_LOOKUPTBL_SHIFT);
> > + __m256i vresult =
> _mm256_srlv_epi32(vlookup_table,
> > + vbucket_idx);
> > +
> > + value |= (_mm256_movemask_ps(
> > + (__m256) _mm256_slli_epi32(vresult, 31))
> > + & ((1 << (RTE_EFD_VALUE_NUM_BITS - i)) -
> 1)) << i;
> > + }
> > + break;
> > +#endif
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v6 0/5] Elastic Flow Distributor
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 0/5] Elastic Flow Distributor Pablo de Lara
` (5 preceding siblings ...)
2017-01-16 15:08 ` [dpdk-dev] [PATCH v5 0/5] Elastic Flow Distributor Thomas Monjalon
@ 2017-01-16 19:21 ` Pablo de Lara
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 1/5] efd: new Elastic Flow Distributor library Pablo de Lara
` (6 more replies)
6 siblings, 7 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-16 19:21 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara
EFD is a distributor library that uses perfect hashing to determine a
target/value for a given incoming flow key. It has the following advantages:
first, because it uses perfect hashing it does not store the key itself and
hence lookup performance is not dependent on the key size. Second, the
target/value can be any arbitrary value hence the system designer and/or
operator can better optimize service rates and inter-cluster network traffic
locating. Third, since the storage requirement is much smaller than a
hash-based flow table (i.e. better fit for CPU cache), EFD can scale to millions
of flow keys. Finally, with current optimized library implementation performance
is fully scalable with number of CPU cores.
The basic idea of EFD is when a given key is to be inserted, a family of hash
functions is searched until the correct hash function that maps the input key to
the correct value is found. However, rather than explicitly storing all keys and
their associated values, EFD stores only indices of hash functions that map keys
to values, and thereby consumes much less space than conventional flow-based
tables. The lookup operation is very simple, similar to computational-based
scheme, given an input key the lookup operation is reduced to hashing that key
with the correct hash function.
Intuitively, finding a hash function that maps each of a large number (millions)
of input keys to the correct output value is effectively impossible, as a result
EFD, breaks the problem into smaller pieces (divide and conquer). EFD divides
the entire input key set into many small groups. Each group consists of
approximately 20-28 keys (a configurable parameter for the library), then, for
each small group, a brute force search to find a hash function that produces the
correct outputs for each key in the group.
It should be mentioned that since in the online lookup table for EFD doesn’t
store the key itself, the size of the EFD table is independent of the key size
and hence EFD lookup performance which is almost constant irrespective of the
length of the key which is a highly desirable feature especially for longer
keys.
Library code is included in the patch, plus an sample application that shows
how the library can be used.
RFC for this library was already sent:
http://dpdk.org/ml/archives/dev/2016-October/049238.html
Changes in v6:
- Separated x86 SIMD code in different file
- Fixed shared library compilation
- Fixed figure reference in documentation
- Added missing parameter documentation
Changes in v5:
- Removed trailing whitespace in doc
Changes in v4:
- Added References section
Changes in v3:
- Fixed SVG files
- Fixed copyright dates
- Reformatted parts of documentation
Changes in v2:
- Added documentation for library and sample app
- Fixed checkpatch errors/warnings
- Added functional and performance tests
- Made key size variable at runtime
- Made code multi-architecture compatible at runtime
Pablo de Lara (5):
efd: new Elastic Flow Distributor library
app/test: add EFD functional and perf tests
examples/flow_distributor: sample app to demonstrate EFD usage
doc: add EFD library section in Programmers guide
doc: add flow distributor guide
MAINTAINERS | 9 +
app/test/Makefile | 5 +-
app/test/test_efd.c | 494 ++++++++
app/test/test_efd_perf.c | 407 +++++++
config/common_base | 5 +
doc/api/doxy-api-index.md | 3 +-
doc/api/doxy-api.conf | 1 +
doc/api/examples.dox | 4 +
doc/guides/prog_guide/efd_lib.rst | 440 +++++++
doc/guides/prog_guide/img/efd_i1.svg | 130 ++
doc/guides/prog_guide/img/efd_i10.svg | 384 ++++++
doc/guides/prog_guide/img/efd_i11.svg | 319 +++++
doc/guides/prog_guide/img/efd_i12.svg | 1008 ++++++++++++++++
doc/guides/prog_guide/img/efd_i2.svg | 280 +++++
doc/guides/prog_guide/img/efd_i3.svg | 634 ++++++++++
doc/guides/prog_guide/img/efd_i4.svg | 203 ++++
doc/guides/prog_guide/img/efd_i5.svg | 183 +++
doc/guides/prog_guide/img/efd_i6.svg | 1254 +++++++++++++++++++
doc/guides/prog_guide/img/efd_i7.svg | 790 ++++++++++++
doc/guides/prog_guide/img/efd_i8.svg | 182 +++
doc/guides/prog_guide/img/efd_i9.svg | 390 ++++++
doc/guides/prog_guide/index.rst | 23 +
doc/guides/rel_notes/release_17_02.rst | 14 +
doc/guides/sample_app_ug/flow_distributor.rst | 494 ++++++++
doc/guides/sample_app_ug/img/flow_distributor.svg | 1254 +++++++++++++++++++
doc/guides/sample_app_ug/index.rst | 1 +
examples/Makefile | 1 +
examples/flow_distributor/Makefile | 44 +
examples/flow_distributor/distributor/Makefile | 57 +
examples/flow_distributor/distributor/args.c | 200 +++
examples/flow_distributor/distributor/args.h | 39 +
examples/flow_distributor/distributor/init.c | 371 ++++++
examples/flow_distributor/distributor/init.h | 76 ++
examples/flow_distributor/distributor/main.c | 362 ++++++
examples/flow_distributor/node/Makefile | 48 +
examples/flow_distributor/node/node.c | 417 +++++++
examples/flow_distributor/shared/common.h | 99 ++
lib/Makefile | 3 +-
lib/librte_eal/common/include/rte_log.h | 3 +-
lib/librte_efd/Makefile | 59 +
lib/librte_efd/rte_efd.c | 1340 +++++++++++++++++++++
lib/librte_efd/rte_efd.h | 300 +++++
lib/librte_efd/rte_efd_version.map | 13 +
lib/librte_efd/rte_efd_x86.h | 85 ++
mk/rte.app.mk | 3 +-
45 files changed, 12426 insertions(+), 5 deletions(-)
create mode 100644 app/test/test_efd.c
create mode 100644 app/test/test_efd_perf.c
create mode 100644 doc/guides/prog_guide/efd_lib.rst
create mode 100644 doc/guides/prog_guide/img/efd_i1.svg
create mode 100644 doc/guides/prog_guide/img/efd_i10.svg
create mode 100644 doc/guides/prog_guide/img/efd_i11.svg
create mode 100644 doc/guides/prog_guide/img/efd_i12.svg
create mode 100644 doc/guides/prog_guide/img/efd_i2.svg
create mode 100644 doc/guides/prog_guide/img/efd_i3.svg
create mode 100644 doc/guides/prog_guide/img/efd_i4.svg
create mode 100644 doc/guides/prog_guide/img/efd_i5.svg
create mode 100644 doc/guides/prog_guide/img/efd_i6.svg
create mode 100644 doc/guides/prog_guide/img/efd_i7.svg
create mode 100644 doc/guides/prog_guide/img/efd_i8.svg
create mode 100644 doc/guides/prog_guide/img/efd_i9.svg
create mode 100644 doc/guides/sample_app_ug/flow_distributor.rst
create mode 100644 doc/guides/sample_app_ug/img/flow_distributor.svg
create mode 100644 examples/flow_distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/args.c
create mode 100644 examples/flow_distributor/distributor/args.h
create mode 100644 examples/flow_distributor/distributor/init.c
create mode 100644 examples/flow_distributor/distributor/init.h
create mode 100644 examples/flow_distributor/distributor/main.c
create mode 100644 examples/flow_distributor/node/Makefile
create mode 100644 examples/flow_distributor/node/node.c
create mode 100644 examples/flow_distributor/shared/common.h
create mode 100644 lib/librte_efd/Makefile
create mode 100644 lib/librte_efd/rte_efd.c
create mode 100644 lib/librte_efd/rte_efd.h
create mode 100644 lib/librte_efd/rte_efd_version.map
create mode 100644 lib/librte_efd/rte_efd_x86.h
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v6 1/5] efd: new Elastic Flow Distributor library
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 " Pablo de Lara
@ 2017-01-16 19:21 ` Pablo de Lara
2017-01-17 20:32 ` Thomas Monjalon
2017-01-17 21:11 ` Thomas Monjalon
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 2/5] app/test: add EFD functional and perf tests Pablo de Lara
` (5 subsequent siblings)
6 siblings, 2 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-16 19:21 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Byron Marohn, Saikrishna Edupuganti
Elastic Flow Distributor (EFD) is a distributor library that uses
perfect hashing to determine a target/value for a given incoming flow key.
It has the following advantages:
- First, because it uses perfect hashing, it does not store
the key itself and hence lookup performance is not dependent
on the key size.
- Second, the target/value can be any arbitrary value hence
the system designer and/or operator can better optimize service rates
and inter-cluster network traffic locating.
- Third, since the storage requirement is much smaller than a hash-based
flow table (i.e. better fit for CPU cache), EFD can scale to
millions of flow keys.
Finally, with current optimized library implementation performance
is fully scalable with number of CPU cores.
Signed-off-by: Byron Marohn <byron.marohn@intel.com>
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 5 +
config/common_base | 5 +
doc/api/doxy-api-index.md | 3 +-
doc/api/doxy-api.conf | 1 +
doc/guides/rel_notes/release_17_02.rst | 11 +
lib/Makefile | 3 +-
lib/librte_eal/common/include/rte_log.h | 3 +-
lib/librte_efd/Makefile | 59 ++
lib/librte_efd/rte_efd.c | 1340 +++++++++++++++++++++++++++++++
lib/librte_efd/rte_efd.h | 300 +++++++
lib/librte_efd/rte_efd_version.map | 13 +
lib/librte_efd/rte_efd_x86.h | 85 ++
mk/rte.app.mk | 3 +-
13 files changed, 1827 insertions(+), 4 deletions(-)
create mode 100644 lib/librte_efd/Makefile
create mode 100644 lib/librte_efd/rte_efd.c
create mode 100644 lib/librte_efd/rte_efd.h
create mode 100644 lib/librte_efd/rte_efd_version.map
create mode 100644 lib/librte_efd/rte_efd_x86.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 9645c9b..9c60d67 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -528,6 +528,11 @@ F: app/test/test_acl.*
F: examples/l3fwd-acl/
F: doc/guides/sample_app_ug/l3_forward_access_ctrl.rst
+EFD
+M: Byron Marohn <byron.marohn@intel.com>
+M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
+F: lib/librte_efd/
+
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
M: Pablo de Lara <pablo.de.lara.guarch@intel.com>
diff --git a/config/common_base b/config/common_base
index 8e9dcfa..869d8fb 100644
--- a/config/common_base
+++ b/config/common_base
@@ -467,6 +467,11 @@ CONFIG_RTE_LIBRTE_HASH=y
CONFIG_RTE_LIBRTE_HASH_DEBUG=n
#
+# Compile librte_efd
+#
+CONFIG_RTE_LIBRTE_EFD=y
+
+#
# Compile librte_jobstats
#
CONFIG_RTE_LIBRTE_JOBSTATS=y
diff --git a/doc/api/doxy-api-index.md b/doc/api/doxy-api-index.md
index 72d59b2..0d34e2f 100644
--- a/doc/api/doxy-api-index.md
+++ b/doc/api/doxy-api-index.md
@@ -90,7 +90,8 @@ There are many libraries, so their headers may be grouped by topics:
[frag/reass] (@ref rte_ip_frag.h),
[LPM IPv4 route] (@ref rte_lpm.h),
[LPM IPv6 route] (@ref rte_lpm6.h),
- [ACL] (@ref rte_acl.h)
+ [ACL] (@ref rte_acl.h),
+ [EFD] (@ref rte_efd.h)
- **QoS**:
[metering] (@ref rte_meter.h),
diff --git a/doc/api/doxy-api.conf b/doc/api/doxy-api.conf
index b340fcf..6892315 100644
--- a/doc/api/doxy-api.conf
+++ b/doc/api/doxy-api.conf
@@ -40,6 +40,7 @@ INPUT = doc/api/doxy-api-index.md \
lib/librte_compat \
lib/librte_cryptodev \
lib/librte_distributor \
+ lib/librte_efd \
lib/librte_ether \
lib/librte_hash \
lib/librte_ip_frag \
diff --git a/doc/guides/rel_notes/release_17_02.rst b/doc/guides/rel_notes/release_17_02.rst
index d445d64..8673d22 100644
--- a/doc/guides/rel_notes/release_17_02.rst
+++ b/doc/guides/rel_notes/release_17_02.rst
@@ -57,6 +57,17 @@ New Features
Six new APIs have been added to the ixgbe PMD for MACsec offload support.
The declarations for the APIs can be found in ``rte_pmd_ixgbe.h``.
+* **Added Elastic Flow Distributor library (rte_efd).**
+
+ This new library uses perfect hashing to determine a target/value for a
+ given incoming flow key.
+
+ It does not store the key itself for lookup operations, and therefore,
+ lookup performance is not dependent on the key size. Also, the target/value
+ can be any arbitrary value (8 bits by default). Finally, the storage requirement
+ is much smaller than a hash-based flow table and therefore, it can better fit for
+ CPU cache, being able to scale to millions of flow keys.
+
Resolved Issues
---------------
diff --git a/lib/Makefile b/lib/Makefile
index 990f23a..4178325 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -1,6 +1,6 @@
# BSD LICENSE
#
-# Copyright(c) 2010-2015 Intel Corporation. All rights reserved.
+# Copyright(c) 2010-2017 Intel Corporation. All rights reserved.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -43,6 +43,7 @@ DIRS-$(CONFIG_RTE_LIBRTE_ETHER) += librte_ether
DIRS-$(CONFIG_RTE_LIBRTE_CRYPTODEV) += librte_cryptodev
DIRS-$(CONFIG_RTE_LIBRTE_VHOST) += librte_vhost
DIRS-$(CONFIG_RTE_LIBRTE_HASH) += librte_hash
+DIRS-$(CONFIG_RTE_LIBRTE_EFD) += librte_efd
DIRS-$(CONFIG_RTE_LIBRTE_LPM) += librte_lpm
DIRS-$(CONFIG_RTE_LIBRTE_ACL) += librte_acl
DIRS-$(CONFIG_RTE_LIBRTE_NET) += librte_net
diff --git a/lib/librte_eal/common/include/rte_log.h b/lib/librte_eal/common/include/rte_log.h
index 671e274..954b96c 100644
--- a/lib/librte_eal/common/include/rte_log.h
+++ b/lib/librte_eal/common/include/rte_log.h
@@ -1,7 +1,7 @@
/*-
* BSD LICENSE
*
- * Copyright(c) 2010-2014 Intel Corporation. All rights reserved.
+ * Copyright(c) 2010-2017 Intel Corporation. All rights reserved.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -79,6 +79,7 @@ extern struct rte_logs rte_logs;
#define RTE_LOGTYPE_PIPELINE 0x00008000 /**< Log related to pipeline. */
#define RTE_LOGTYPE_MBUF 0x00010000 /**< Log related to mbuf. */
#define RTE_LOGTYPE_CRYPTODEV 0x00020000 /**< Log related to cryptodev. */
+#define RTE_LOGTYPE_EFD 0x00040000 /**< Log related to EFD. */
/* these log types can be used in an application */
#define RTE_LOGTYPE_USER1 0x01000000 /**< User-defined log type 1. */
diff --git a/lib/librte_efd/Makefile b/lib/librte_efd/Makefile
new file mode 100644
index 0000000..c591ce7
--- /dev/null
+++ b/lib/librte_efd/Makefile
@@ -0,0 +1,59 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+# library name
+LIB = librte_efd.a
+
+LDLIBS += -lm
+
+CFLAGS += -O3
+CFLAGS += $(WERROR_FLAGS) -I$(SRCDIR)
+
+EXPORT_MAP := rte_efd_version.map
+
+LIBABIVER := 1
+
+# all source are stored in SRCS-y
+SRCS-$(CONFIG_RTE_LIBRTE_EFD) := rte_efd.c
+
+# install this header file
+SYMLINK-$(CONFIG_RTE_LIBRTE_EFD)-include := rte_efd.h
+
+# this lib depends upon:
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_mbuf
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_mempool
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_eal
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_ether
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_ring
+
+include $(RTE_SDK)/mk/rte.lib.mk
diff --git a/lib/librte_efd/rte_efd.c b/lib/librte_efd/rte_efd.c
new file mode 100644
index 0000000..2adc4d3
--- /dev/null
+++ b/lib/librte_efd/rte_efd.c
@@ -0,0 +1,1340 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <immintrin.h>
+#include <math.h>
+#include <sys/queue.h>
+
+#include <rte_log.h>
+#include <rte_eal_memconfig.h>
+#include <rte_errno.h>
+#include <rte_malloc.h>
+#include <rte_memzone.h>
+#include <rte_prefetch.h>
+#include <rte_branch_prediction.h>
+#include <rte_memcpy.h>
+#include <rte_ring.h>
+#include <rte_jhash.h>
+#include <rte_hash_crc.h>
+
+#include "rte_efd.h"
+#if defined(RTE_ARCH_X86)
+#include "rte_efd_x86.h"
+#endif
+
+#define EFD_KEY(key_idx, table) (table->keys + ((key_idx) * table->key_len))
+/** Hash function used to determine chunk_id and bin_id for a group */
+#define EFD_HASH(key, table) \
+ (uint32_t)(rte_jhash(key, table->key_len, 0xbc9f1d34))
+/** Hash function used as constant component of perfect hash search */
+#define EFD_HASHFUNCA(key, table) \
+ (uint32_t)(rte_hash_crc(key, table->key_len, 0xbc9f1d35))
+/** Hash function used as multiplicative component of perfect hash search */
+#define EFD_HASHFUNCB(key, table) \
+ (uint32_t)(rte_hash_crc(key, table->key_len, 0xbc9f1d36))
+
+/*************************************************************************
+ * Fixed constants
+ *************************************************************************/
+
+/* These parameters are fixed by the efd_bin_to_group balancing table */
+#define EFD_CHUNK_NUM_GROUPS (64)
+#define EFD_CHUNK_NUM_BINS (256)
+#define EFD_CHUNK_NUM_BIN_TO_GROUP_SETS \
+ (EFD_CHUNK_NUM_BINS / EFD_CHUNK_NUM_GROUPS)
+
+/*
+ * Target number of rules that each chunk is created to handle.
+ * Used when initially allocating the table
+ */
+#define EFD_TARGET_CHUNK_NUM_RULES \
+ (EFD_CHUNK_NUM_GROUPS * EFD_TARGET_GROUP_NUM_RULES)
+/*
+ * Max number of rules that each chunk is created to handle.
+ * Used when initially allocating the table
+ */
+#define EFD_TARGET_CHUNK_MAX_NUM_RULES \
+ (EFD_CHUNK_NUM_GROUPS * EFD_MAX_GROUP_NUM_RULES)
+
+/** This is fixed based on the bin_to_group permutation array */
+#define EFD_MAX_GROUP_NUM_BINS (16)
+
+/**
+ * The end of the chunks array needs some extra padding to ensure
+ * that vectorization over-reads on the last online chunk stay within
+allocated memory
+ */
+#define EFD_NUM_CHUNK_PADDING_BYTES (256)
+
+/* All different internal lookup functions */
+enum efd_lookup_internal_function {
+ EFD_LOOKUP_SCALAR = 0,
+ EFD_LOOKUP_AVX2,
+ EFD_LOOKUP_NUM
+};
+
+TAILQ_HEAD(rte_efd_list, rte_tailq_entry);
+
+static struct rte_tailq_elem rte_efd_tailq = {
+ .name = "RTE_EFD",
+};
+EAL_REGISTER_TAILQ(rte_efd_tailq);
+
+/** Internal permutation array used to shuffle bins into pseudorandom groups */
+const uint32_t efd_bin_to_group[EFD_CHUNK_NUM_BIN_TO_GROUP_SETS][EFD_CHUNK_NUM_BINS] = {
+ {
+ 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3,
+ 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
+ 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11,
+ 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15,
+ 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19,
+ 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23,
+ 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27,
+ 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31,
+ 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35,
+ 36, 36, 36, 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39,
+ 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, 42, 43, 43, 43, 43,
+ 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47,
+ 48, 48, 48, 48, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51,
+ 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, 54, 55, 55, 55, 55,
+ 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59,
+ 60, 60, 60, 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63
+ },
+ {
+ 34, 33, 48, 59, 0, 21, 36, 18, 9, 49, 54, 38, 51, 23, 31, 5,
+ 44, 23, 37, 52, 11, 4, 58, 20, 38, 40, 38, 22, 26, 28, 42, 6,
+ 46, 16, 31, 28, 46, 14, 60, 0, 35, 53, 16, 58, 16, 29, 39, 7,
+ 1, 54, 15, 11, 48, 3, 62, 9, 58, 5, 30, 43, 17, 7, 36, 34,
+ 6, 36, 2, 14, 10, 1, 47, 47, 20, 45, 62, 56, 34, 25, 39, 18,
+ 51, 41, 61, 25, 56, 40, 41, 37, 52, 35, 30, 57, 11, 42, 37, 27,
+ 54, 19, 26, 13, 48, 31, 46, 15, 12, 10, 16, 20, 43, 17, 12, 55,
+ 45, 18, 8, 41, 7, 31, 42, 63, 12, 14, 21, 57, 24, 40, 5, 41,
+ 13, 44, 23, 59, 25, 57, 52, 50, 62, 1, 2, 49, 32, 57, 26, 43,
+ 56, 60, 55, 5, 49, 6, 3, 50, 46, 39, 27, 33, 17, 4, 53, 13,
+ 2, 19, 36, 51, 63, 0, 22, 33, 59, 28, 29, 23, 45, 33, 53, 27,
+ 22, 21, 40, 56, 4, 18, 44, 47, 28, 17, 4, 50, 21, 62, 8, 39,
+ 0, 8, 15, 24, 29, 24, 9, 11, 48, 61, 35, 55, 43, 1, 54, 42,
+ 53, 60, 22, 3, 32, 52, 25, 8, 15, 60, 7, 55, 27, 63, 19, 10,
+ 63, 24, 61, 19, 12, 38, 6, 29, 13, 37, 10, 3, 45, 32, 32, 30,
+ 49, 61, 44, 14, 20, 58, 35, 30, 2, 26, 34, 51, 9, 59, 47, 50
+ },
+ {
+ 32, 35, 32, 34, 55, 5, 6, 23, 49, 11, 6, 23, 52, 37, 29, 54,
+ 55, 40, 63, 50, 29, 52, 61, 25, 12, 56, 39, 38, 29, 11, 46, 1,
+ 40, 11, 19, 56, 7, 28, 51, 16, 15, 48, 21, 51, 60, 31, 14, 22,
+ 41, 47, 59, 56, 53, 28, 58, 26, 43, 27, 41, 33, 24, 52, 44, 38,
+ 13, 59, 48, 51, 60, 15, 3, 30, 15, 0, 10, 62, 44, 14, 28, 51,
+ 38, 2, 41, 26, 25, 49, 10, 12, 55, 57, 27, 35, 19, 33, 0, 30,
+ 5, 36, 47, 53, 5, 53, 20, 43, 34, 37, 52, 41, 21, 63, 59, 9,
+ 24, 1, 45, 24, 39, 44, 45, 16, 9, 17, 7, 50, 57, 22, 18, 28,
+ 25, 45, 2, 40, 58, 15, 17, 3, 1, 27, 61, 39, 19, 0, 19, 21,
+ 57, 62, 54, 60, 54, 40, 48, 33, 36, 37, 4, 42, 1, 43, 58, 8,
+ 13, 42, 10, 56, 35, 22, 48, 61, 63, 10, 49, 9, 24, 9, 25, 57,
+ 33, 18, 13, 31, 42, 36, 36, 55, 30, 37, 53, 34, 59, 4, 4, 23,
+ 8, 16, 58, 14, 30, 11, 12, 63, 49, 62, 2, 39, 47, 22, 2, 60,
+ 18, 8, 46, 31, 6, 20, 32, 29, 46, 42, 20, 31, 32, 61, 34, 4,
+ 47, 26, 20, 43, 26, 21, 7, 3, 16, 35, 18, 44, 27, 62, 13, 23,
+ 6, 50, 12, 8, 45, 17, 3, 46, 50, 7, 14, 5, 17, 54, 38, 0
+ },
+ {
+ 29, 56, 5, 7, 54, 48, 23, 37, 35, 44, 52, 40, 33, 49, 60, 0,
+ 59, 51, 28, 12, 41, 26, 2, 23, 34, 5, 59, 40, 3, 19, 6, 26,
+ 35, 53, 45, 49, 29, 57, 28, 62, 58, 59, 19, 53, 59, 62, 6, 54,
+ 13, 15, 48, 50, 45, 21, 41, 12, 34, 40, 24, 56, 19, 21, 35, 18,
+ 55, 45, 9, 61, 47, 61, 19, 15, 16, 39, 17, 31, 3, 51, 21, 50,
+ 17, 25, 25, 11, 44, 16, 18, 28, 14, 2, 37, 61, 58, 27, 62, 4,
+ 14, 17, 1, 9, 46, 28, 37, 0, 53, 43, 57, 7, 57, 46, 21, 41,
+ 39, 14, 52, 60, 44, 53, 49, 60, 49, 63, 13, 11, 29, 1, 55, 47,
+ 55, 12, 60, 43, 54, 37, 13, 6, 42, 10, 36, 13, 9, 8, 34, 51,
+ 31, 32, 12, 7, 57, 2, 26, 14, 3, 30, 63, 3, 32, 1, 5, 11,
+ 27, 24, 26, 44, 31, 23, 56, 38, 62, 0, 40, 30, 6, 23, 38, 2,
+ 47, 5, 15, 27, 16, 10, 31, 25, 22, 63, 30, 25, 20, 33, 32, 50,
+ 29, 43, 55, 10, 50, 45, 56, 20, 4, 7, 27, 46, 11, 16, 22, 52,
+ 35, 20, 41, 54, 46, 33, 42, 18, 63, 8, 22, 58, 36, 4, 51, 42,
+ 38, 32, 38, 22, 17, 0, 47, 8, 48, 8, 48, 1, 61, 36, 33, 20,
+ 24, 39, 39, 18, 30, 36, 9, 43, 42, 24, 10, 58, 4, 15, 34, 52
+ },
+};
+
+/*************************************************************************
+ * Offline region structures
+ *************************************************************************/
+
+/** Online group containing number of rules, values, keys and their bins
+ * for EFD_MAX_GROUP_NUM_RULES rules.
+ */
+struct efd_offline_group_rules {
+ uint32_t num_rules;
+ /**< Sum of the number of rules in all bins assigned to this group. */
+
+ uint32_t key_idx[EFD_MAX_GROUP_NUM_RULES];
+ /**< Array with all keys of the group. */
+ efd_value_t value[EFD_MAX_GROUP_NUM_RULES];
+ /**< Array with all values of the keys of the group. */
+
+ uint8_t bin_id[EFD_MAX_GROUP_NUM_RULES];
+ /**< Stores the bin for each correspending key to
+ * avoid having to recompute it
+ */
+};
+
+/** Offline chunk record, containing EFD_TARGET_CHUNK_NUM_RULES rules.
+ * Those rules are split into EFD_CHUNK_NUM_GROUPS groups per chunk.
+ */
+struct efd_offline_chunk_rules {
+ uint16_t num_rules;
+ /**< Number of rules in the entire chunk;
+ * used to detect unbalanced groups
+ */
+
+ struct efd_offline_group_rules group_rules[EFD_CHUNK_NUM_GROUPS];
+ /**< Array of all groups in the chunk. */
+};
+
+/*************************************************************************
+ * Online region structures
+ *************************************************************************/
+
+/** Online group containing values for EFD_MAX_GROUP_NUM_RULES rules. */
+struct efd_online_group_entry {
+ efd_hashfunc_t hash_idx[RTE_EFD_VALUE_NUM_BITS];
+ efd_lookuptbl_t lookup_table[RTE_EFD_VALUE_NUM_BITS];
+} __attribute__((__packed__));
+
+/**
+ * A single chunk record, containing EFD_TARGET_CHUNK_NUM_RULES rules.
+ * Those rules are split into EFD_CHUNK_NUM_GROUPS groups per chunk.
+ */
+struct efd_online_chunk {
+ uint8_t bin_choice_list[(EFD_CHUNK_NUM_BINS * 2 + 7) / 8];
+ /**< This is a packed indirection index into the 'groups' array.
+ * Each byte contains four two-bit values which index into
+ * the efd_bin_to_group array.
+ * The efd_bin_to_group array returns the index into the groups array
+ */
+
+ struct efd_online_group_entry groups[EFD_CHUNK_NUM_GROUPS];
+ /**< Array of all the groups in the chunk. */
+} __attribute__((__packed__));
+
+/**
+ * EFD table structure
+ */
+struct rte_efd_table {
+ char name[RTE_EFD_NAMESIZE]; /**< Name of the efd table. */
+
+ uint32_t key_len; /**< Length of the key stored offline */
+
+ uint32_t max_num_rules;
+ /**< Static maximum number of entries the table was constructed to hold. */
+
+ uint32_t num_rules;
+ /**< Number of entries currently in the table . */
+
+ uint32_t num_chunks;
+ /**< Number of chunks in the table needed to support num_rules. */
+
+ uint32_t num_chunks_shift;
+ /**< Bits to shift to get chunk id, instead of dividing by num_chunk. */
+
+ enum efd_lookup_internal_function lookup_fn;
+ /**< Indicates which lookup function to use. */
+
+ struct efd_online_chunk *chunks[RTE_MAX_NUMA_NODES];
+ /**< Dynamic array of size num_chunks of chunk records. */
+
+ struct efd_offline_chunk_rules *offline_chunks;
+ /**< Dynamic array of size num_chunks of key-value pairs. */
+
+ struct rte_ring *free_slots;
+ /**< Ring that stores all indexes of the free slots in the key table */
+
+ uint8_t *keys; /**< Dynamic array of size max_num_rules of keys */
+};
+
+/**
+ * Computes the chunk ID for a given key hash
+ *
+ * @param table
+ * EFD table to reference
+ * @param hashed_key
+ * 32-bit key hash returned by EFD_HASH
+ *
+ * @return
+ * chunk ID containing this key hash
+ */
+static inline uint32_t
+efd_get_chunk_id(const struct rte_efd_table * const table,
+ const uint32_t hashed_key)
+{
+ return hashed_key & (table->num_chunks - 1);
+}
+
+/**
+ * Computes the bin ID for a given key hash
+ *
+ * @param table
+ * EFD table to reference
+ * @param hashed_key
+ * 32-bit key hash returned by EFD_HASH
+ *
+ * @return bin ID containing this key hash
+ */
+static inline uint32_t
+efd_get_bin_id(const struct rte_efd_table * const table,
+ const uint32_t hashed_key)
+{
+ return (hashed_key >> table->num_chunks_shift) & (EFD_CHUNK_NUM_BINS - 1);
+}
+
+/**
+ * Looks up the current permutation choice for a particular bin in the online table
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to look up existing values (ideally caller's socket id)
+ * @param chunk_id
+ * Chunk ID of bin to look up
+ * @param bin_id
+ * Bin ID to look up
+ *
+ * @return
+ * Currently active permutation choice in the online table
+ */
+static inline uint8_t
+efd_get_choice(const struct rte_efd_table * const table,
+ const unsigned int socket_id, const uint32_t chunk_id,
+ const uint32_t bin_id)
+{
+ struct efd_online_chunk *chunk = &table->chunks[socket_id][chunk_id];
+
+ /*
+ * Grab the chunk (byte) that contains the choices
+ * for four neighboring bins.
+ */
+ uint8_t choice_chunk =
+ chunk->bin_choice_list[bin_id / EFD_CHUNK_NUM_BIN_TO_GROUP_SETS];
+
+ /*
+ * Compute the offset into the chunk that contains
+ * the group_id lookup position
+ */
+ int offset = (bin_id & 0x3) * 2;
+
+ /* Extract from the byte just the desired lookup position */
+ return (uint8_t) ((choice_chunk >> offset) & 0x3);
+}
+
+/**
+ * Compute the chunk_id and bin_id for a given key
+ *
+ * @param table
+ * EFD table to reference
+ * @param key
+ * Key to hash and find location of
+ * @param chunk_id
+ * Computed chunk ID
+ * @param bin_id
+ * Computed bin ID
+ *
+ */
+static inline void
+efd_compute_ids(const struct rte_efd_table * const table,
+ const void *key, uint32_t * const chunk_id, uint32_t * const bin_id)
+{
+ /* Compute the position of the entry in the hash table */
+ uint32_t h = EFD_HASH(key, table);
+
+ /* Compute the chunk_id where that entry can be found */
+ *chunk_id = efd_get_chunk_id(table, h);
+
+ /*
+ * Compute the bin within that chunk where the entry
+ * can be found (0 - 255)
+ */
+ *bin_id = efd_get_bin_id(table, h);
+}
+
+/**
+ * Search for a hash function for a group that satisfies all group results
+ */
+static inline int
+efd_search_hash(struct rte_efd_table * const table,
+ const struct efd_offline_group_rules * const off_group,
+ struct efd_online_group_entry * const on_group)
+{
+ efd_hashfunc_t hash_idx;
+ efd_hashfunc_t start_hash_idx[RTE_EFD_VALUE_NUM_BITS];
+ efd_lookuptbl_t start_lookup_table[RTE_EFD_VALUE_NUM_BITS];
+
+ uint32_t i, j, rule_id;
+ uint32_t hash_val_a[EFD_MAX_GROUP_NUM_RULES];
+ uint32_t hash_val_b[EFD_MAX_GROUP_NUM_RULES];
+ uint32_t hash_val[EFD_MAX_GROUP_NUM_RULES];
+
+
+ rte_prefetch0(off_group->value);
+
+ /*
+ * Prepopulate the hash_val tables by running the two hash functions
+ * for each provided rule
+ */
+ for (i = 0; i < off_group->num_rules; i++) {
+ void *key_stored = EFD_KEY(off_group->key_idx[i], table);
+ hash_val_b[i] = EFD_HASHFUNCB(key_stored, table);
+ hash_val_a[i] = EFD_HASHFUNCA(key_stored, table);
+ }
+
+ for (i = 0; i < RTE_EFD_VALUE_NUM_BITS; i++) {
+ hash_idx = on_group->hash_idx[i];
+ start_hash_idx[i] = hash_idx;
+ start_lookup_table[i] = on_group->lookup_table[i];
+
+ do {
+ efd_lookuptbl_t lookup_table = 0;
+ efd_lookuptbl_t lookup_table_complement = 0;
+
+ for (rule_id = 0; rule_id < off_group->num_rules; rule_id++)
+ hash_val[rule_id] = hash_val_a[rule_id] + (hash_idx *
+ hash_val_b[rule_id]);
+
+ /*
+ * The goal here is to find a hash function for this
+ * particular bit entry that meets the following criteria:
+ * The most significant bits of the hash result define a
+ * shift into the lookup table where the bit will be stored
+ */
+
+ /* Iterate over each provided rule */
+ for (rule_id = 0; rule_id < off_group->num_rules;
+ rule_id++) {
+ /*
+ * Use the few most significant bits (number based on
+ * EFD_LOOKUPTBL_SIZE) to see what position the
+ * expected bit should be set in the lookup_table
+ */
+ uint32_t bucket_idx = hash_val[rule_id] >>
+ EFD_LOOKUPTBL_SHIFT;
+
+ /*
+ * Get the current bit of interest.
+ * This only find an appropriate hash function
+ * for one bit at a time of the rule
+ */
+ efd_lookuptbl_t expected =
+ (off_group->value[rule_id] >> i) & 0x1;
+
+ /*
+ * Add the expected bit (if set) to a map
+ * (lookup_table). Also set its complement
+ * in lookup_table_complement
+ */
+ lookup_table |= expected << bucket_idx;
+ lookup_table_complement |= (1 - expected)
+ << bucket_idx;
+
+ /*
+ * If ever the hash function of two different
+ * elements result in different values at the
+ * same location in the lookup_table,
+ * the current hash_idx is not valid.
+ */
+ if (lookup_table & lookup_table_complement)
+ break;
+ }
+
+ /*
+ * Check if the previous loop completed without
+ * breaking early
+ */
+ if (rule_id == off_group->num_rules) {
+ /*
+ * Current hash function worked, store it
+ * for the current group
+ */
+ on_group->hash_idx[i] = hash_idx;
+ on_group->lookup_table[i] = lookup_table;
+
+ /*
+ * Make sure that the hash function has changed
+ * from the starting value
+ */
+ hash_idx = start_hash_idx[i] + 1;
+ break;
+ }
+ hash_idx++;
+
+ } while (hash_idx != start_hash_idx[i]);
+
+ /* Failed to find perfect hash for this group */
+ if (hash_idx == start_hash_idx[i]) {
+ /*
+ * Restore previous hash_idx and lookup_table
+ * for all value bits
+ */
+ for (j = 0; j < i; j++) {
+ on_group->hash_idx[j] = start_hash_idx[j];
+ on_group->lookup_table[j] = start_lookup_table[j];
+ }
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+struct rte_efd_table *
+rte_efd_create(const char *name, uint32_t max_num_rules, uint32_t key_len,
+ uint8_t online_cpu_socket_bitmask, uint8_t offline_cpu_socket)
+{
+ struct rte_efd_table *table = NULL;
+ uint8_t *key_array = NULL;
+ uint32_t num_chunks, num_chunks_shift;
+ uint8_t socket_id;
+ struct rte_efd_list *efd_list = NULL;
+ struct rte_tailq_entry *te;
+ uint64_t offline_table_size;
+ char ring_name[RTE_RING_NAMESIZE];
+ struct rte_ring *r = NULL;
+ unsigned int i;
+
+ efd_list = RTE_TAILQ_CAST(rte_efd_tailq.head, rte_efd_list);
+
+ if (online_cpu_socket_bitmask == 0) {
+ RTE_LOG(ERR, EFD, "At least one CPU socket must be enabled "
+ "in the bitmask\n");
+ return NULL;
+ }
+
+ if (max_num_rules == 0) {
+ RTE_LOG(ERR, EFD, "Max num rules must be higher than 0\n");
+ return NULL;
+ }
+
+ /*
+ * Compute the minimum number of chunks (smallest power of 2)
+ * that can hold all of the rules
+ */
+ if (max_num_rules % EFD_TARGET_CHUNK_NUM_RULES == 0)
+ num_chunks = rte_align32pow2(max_num_rules /
+ EFD_TARGET_CHUNK_NUM_RULES);
+ else
+ num_chunks = rte_align32pow2((max_num_rules /
+ EFD_TARGET_CHUNK_NUM_RULES) + 1);
+
+ num_chunks_shift = log2(num_chunks);
+
+ rte_rwlock_write_lock(RTE_EAL_TAILQ_RWLOCK);
+
+ /*
+ * Guarantee there's no existing: this is normally already checked
+ * by ring creation above
+ */
+ TAILQ_FOREACH(te, efd_list, next)
+ {
+ table = (struct rte_efd_table *) te->data;
+ if (strncmp(name, table->name, RTE_EFD_NAMESIZE) == 0)
+ break;
+ }
+
+ table = NULL;
+ if (te != NULL) {
+ rte_errno = EEXIST;
+ te = NULL;
+ goto error_unlock_exit;
+ }
+
+ te = rte_zmalloc("EFD_TAILQ_ENTRY", sizeof(*te), 0);
+ if (te == NULL) {
+ RTE_LOG(ERR, EFD, "tailq entry allocation failed\n");
+ goto error_unlock_exit;
+ }
+
+ /* Create a new EFD table management structure */
+ table = (struct rte_efd_table *) rte_zmalloc_socket(NULL,
+ sizeof(struct rte_efd_table),
+ RTE_CACHE_LINE_SIZE,
+ offline_cpu_socket);
+ if (table == NULL) {
+ RTE_LOG(ERR, EFD, "Allocating EFD table management structure"
+ " on socket %u failed\n",
+ offline_cpu_socket);
+ goto error_unlock_exit;
+ }
+
+
+ RTE_LOG(DEBUG, EFD, "Allocated EFD table management structure "
+ "on socket %u\n", offline_cpu_socket);
+
+ table->max_num_rules = num_chunks * EFD_TARGET_CHUNK_MAX_NUM_RULES;
+ table->num_rules = 0;
+ table->num_chunks = num_chunks;
+ table->num_chunks_shift = num_chunks_shift;
+ table->key_len = key_len;
+
+ /* key_array */
+ key_array = (uint8_t *) rte_zmalloc_socket(NULL,
+ table->max_num_rules * table->key_len,
+ RTE_CACHE_LINE_SIZE,
+ offline_cpu_socket);
+ if (key_array == NULL) {
+ RTE_LOG(ERR, EFD, "Allocating key array"
+ " on socket %u failed\n",
+ offline_cpu_socket);
+ goto error_unlock_exit;
+ }
+ table->keys = key_array;
+ snprintf(table->name, sizeof(table->name), "%s", name);
+
+ RTE_LOG(DEBUG, EFD, "Creating an EFD table with %u chunks,"
+ " which potentially supports %u entries\n",
+ num_chunks, table->max_num_rules);
+
+ /* Make sure all the allocatable table pointers are NULL initially */
+ for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++)
+ table->chunks[socket_id] = NULL;
+ table->offline_chunks = NULL;
+
+ /*
+ * Allocate one online table per socket specified
+ * in the user-supplied bitmask
+ */
+ uint64_t online_table_size = num_chunks * sizeof(struct efd_online_chunk) +
+ EFD_NUM_CHUNK_PADDING_BYTES;
+
+ for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++) {
+ if ((online_cpu_socket_bitmask >> socket_id) & 0x01) {
+ /*
+ * Allocate all of the EFD table chunks (the online portion)
+ * as a continuous block
+ */
+ table->chunks[socket_id] =
+ (struct efd_online_chunk *) rte_zmalloc_socket(
+ NULL,
+ online_table_size,
+ RTE_CACHE_LINE_SIZE,
+ socket_id);
+ if (table->chunks[socket_id] == NULL) {
+ RTE_LOG(ERR, EFD,
+ "Allocating EFD online table on "
+ "socket %u failed\n",
+ socket_id);
+ goto error_unlock_exit;
+ }
+ RTE_LOG(DEBUG, EFD,
+ "Allocated EFD online table of size "
+ "%"PRIu64" bytes (%.2f MB) on socket %u\n",
+ online_table_size,
+ (float) online_table_size /
+ (1024.0F * 1024.0F),
+ socket_id);
+ }
+ }
+
+#if defined(RTE_ARCH_X86)
+ if (RTE_EFD_VALUE_NUM_BITS > 3 && rte_cpu_get_flag_enabled(RTE_CPUFLAG_AVX2))
+ table->lookup_fn = EFD_LOOKUP_AVX2;
+ else
+#endif
+ table->lookup_fn = EFD_LOOKUP_SCALAR;
+
+ /*
+ * Allocate the EFD table offline portion (with the actual rules
+ * mapping keys to values) as a continuous block.
+ * This could be several gigabytes of memory.
+ */
+ offline_table_size = num_chunks * sizeof(struct efd_offline_chunk_rules);
+ table->offline_chunks =
+ (struct efd_offline_chunk_rules *) rte_zmalloc_socket(NULL,
+ offline_table_size,
+ RTE_CACHE_LINE_SIZE,
+ offline_cpu_socket);
+ if (table->offline_chunks == NULL) {
+ RTE_LOG(ERR, EFD, "Allocating EFD offline table on socket %u "
+ "failed\n", offline_cpu_socket);
+ goto error_unlock_exit;
+ }
+
+ RTE_LOG(DEBUG, EFD,
+ "Allocated EFD offline table of size %"PRIu64" bytes "
+ " (%.2f MB) on socket %u\n", offline_table_size,
+ (float) offline_table_size / (1024.0F * 1024.0F),
+ offline_cpu_socket);
+
+ te->data = (void *) table;
+ TAILQ_INSERT_TAIL(efd_list, te, next);
+ rte_rwlock_write_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+ snprintf(ring_name, sizeof(ring_name), "HT_%s", table->name);
+ /* Create ring (Dummy slot index is not enqueued) */
+ r = rte_ring_create(ring_name, rte_align32pow2(table->max_num_rules),
+ offline_cpu_socket, 0);
+ if (r == NULL) {
+ RTE_LOG(ERR, EFD, "memory allocation failed\n");
+ goto error_unlock_exit;
+ }
+
+ /* Populate free slots ring. Entry zero is reserved for key misses. */
+ for (i = 0; i < table->max_num_rules; i++)
+ rte_ring_sp_enqueue(r, (void *) ((uintptr_t) i));
+
+ table->free_slots = r;
+ return table;
+
+error_unlock_exit:
+ rte_rwlock_write_unlock(RTE_EAL_TAILQ_RWLOCK);
+ rte_efd_free(table);
+
+ return NULL;
+}
+
+struct rte_efd_table *
+rte_efd_find_existing(const char *name)
+{
+ struct rte_efd_table *table = NULL;
+ struct rte_tailq_entry *te;
+ struct rte_efd_list *efd_list;
+
+ efd_list = RTE_TAILQ_CAST(rte_efd_tailq.head, rte_efd_list);
+
+ rte_rwlock_read_lock(RTE_EAL_TAILQ_RWLOCK);
+
+ TAILQ_FOREACH(te, efd_list, next)
+ {
+ table = (struct rte_efd_table *) te->data;
+ if (strncmp(name, table->name, RTE_EFD_NAMESIZE) == 0)
+ break;
+ }
+ rte_rwlock_read_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+ if (te == NULL) {
+ rte_errno = ENOENT;
+ return NULL;
+ }
+ return table;
+}
+
+void
+rte_efd_free(struct rte_efd_table *table)
+{
+ uint8_t socket_id;
+
+ if (table == NULL)
+ return;
+
+ for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++)
+ rte_free(table->chunks[socket_id]);
+
+ rte_ring_free(table->free_slots);
+ rte_free(table->offline_chunks);
+ rte_free(table->keys);
+ rte_free(table);
+}
+
+/**
+ * Applies a previously computed table entry to the specified table for all
+ * socket-local copies of the online table.
+ * Intended to apply an update for only a single change
+ * to a key/value pair at a time
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing values (ideally caller's socket id)
+ * @param chunk_id
+ * Chunk index to update
+ * @param group_id
+ * Group index to update
+ * @param bin_id
+ * Bin within the group that this update affects
+ * @param new_bin_choice
+ * Newly chosen permutation which this bin should use - only lower 2 bits
+ * @param new_group_entry
+ * Previously computed updated chunk/group entry
+ */
+static inline void
+efd_apply_update(struct rte_efd_table * const table, const unsigned int socket_id,
+ const uint32_t chunk_id, const uint32_t group_id,
+ const uint32_t bin_id, const uint8_t new_bin_choice,
+ const struct efd_online_group_entry * const new_group_entry)
+{
+ int i;
+ struct efd_online_chunk *chunk = &table->chunks[socket_id][chunk_id];
+ uint8_t bin_index = bin_id / EFD_CHUNK_NUM_BIN_TO_GROUP_SETS;
+
+ /*
+ * Grab the current byte that contains the choices
+ * for four neighboring bins
+ */
+ uint8_t choice_chunk =
+ chunk->bin_choice_list[bin_index];
+
+
+ /* Compute the offset into the chunk that needs to be updated */
+ int offset = (bin_id & 0x3) * 2;
+
+ /* Zero the two bits of interest and set them to new_bin_choice */
+ choice_chunk = (choice_chunk & (~(0x03 << offset)))
+ | ((new_bin_choice & 0x03) << offset);
+
+ /* Update the online table with the new data across all sockets */
+ for (i = 0; i < RTE_MAX_NUMA_NODES; i++) {
+ if (table->chunks[i] != NULL) {
+ memcpy(&(table->chunks[i][chunk_id].groups[group_id]),
+ new_group_entry,
+ sizeof(struct efd_online_group_entry));
+ table->chunks[i][chunk_id].bin_choice_list[bin_index] =
+ choice_chunk;
+ }
+ }
+}
+
+/*
+ * Move the bin from prev group to the new group
+ */
+static inline void
+move_groups(uint32_t bin_id, uint8_t bin_size,
+ struct efd_offline_group_rules *new_group,
+ struct efd_offline_group_rules * const current_group)
+{
+
+ uint8_t empty_idx = 0;
+ unsigned int i;
+
+ if (new_group == current_group)
+ return;
+
+ for (i = 0; i < current_group->num_rules; i++) {
+ /*
+ * Move keys that belong to the same bin
+ * to the new group
+ */
+ if (current_group->bin_id[i] == bin_id) {
+ new_group->key_idx[new_group->num_rules] =
+ current_group->key_idx[i];
+ new_group->value[new_group->num_rules] =
+ current_group->value[i];
+ new_group->bin_id[new_group->num_rules] =
+ current_group->bin_id[i];
+ new_group->num_rules++;
+ } else {
+ if (i != empty_idx) {
+ /*
+ * Need to move this key towards
+ * the top of the array
+ */
+ current_group->key_idx[empty_idx] =
+ current_group->key_idx[i];
+ current_group->value[empty_idx] =
+ current_group->value[i];
+ current_group->bin_id[empty_idx] =
+ current_group->bin_id[i];
+ }
+ empty_idx++;
+ }
+
+ }
+ current_group->num_rules -= bin_size;
+}
+
+/*
+ * Revert group/s to their previous state before
+ * trying to insert/add a new key
+ */
+static inline void
+revert_groups(struct efd_offline_group_rules *previous_group,
+ struct efd_offline_group_rules *current_group, uint8_t bin_size)
+{
+ unsigned int i;
+
+ if (current_group == previous_group)
+ return;
+
+ /* Move keys back to previous group */
+ for (i = current_group->num_rules - bin_size;
+ i < current_group->num_rules; i++) {
+ previous_group->key_idx[previous_group->num_rules] =
+ current_group->key_idx[i];
+ previous_group->value[previous_group->num_rules] =
+ current_group->value[i];
+ previous_group->bin_id[previous_group->num_rules] =
+ current_group->bin_id[i];
+ previous_group->num_rules++;
+ }
+
+ /*
+ * Decrease number of rules after the move
+ * in the new group
+ */
+ current_group->num_rules -= bin_size;
+}
+
+/**
+ * Computes an updated table entry where the supplied key points to a new host.
+ * If no entry exists, one is inserted.
+ *
+ * This function does NOT modify the online table(s)
+ * This function DOES modify the offline table
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing values (ideally caller's socket id)
+ * @param key
+ * Key to insert
+ * @param value
+ * Value to associate with key
+ * @param chunk_id
+ * Chunk ID of the chunk that was modified
+ * @param group_id
+ * Group ID of the group that was modified
+ * @param bin_id
+ * Bin ID that was modified
+ * @param new_bin_choice
+ * Newly chosen permutation which this bin will use
+ * @param entry
+ * Newly computed online entry to apply later with efd_apply_update
+ *
+ * @return
+ * RTE_EFD_UPDATE_WARN_GROUP_FULL
+ * Operation is insert, and the last available space in the
+ * key's group was just used. Future inserts may fail as groups fill up.
+ * This operation was still successful, and entry contains a valid update
+ * RTE_EFD_UPDATE_FAILED
+ * Either the EFD failed to find a suitable perfect hash or the group was full
+ * This is a fatal error, and the table is now in an indeterminite state
+ * RTE_EFD_UPDATE_NO_CHANGE
+ * Operation resulted in no change to the table (same value already exists)
+ * 0
+ * Insert or update was successful, and the new efd_online_group_entry
+ * is stored in *entry
+ *
+ * @warning
+ * Note that entry will be UNCHANGED if the update has no effect, and thus any
+ * subsequent use of the entry content will likely be invalid
+ */
+static inline int
+efd_compute_update(struct rte_efd_table * const table,
+ const unsigned int socket_id, const void *key,
+ const efd_value_t value, uint32_t * const chunk_id,
+ uint32_t * const group_id, uint32_t * const bin_id,
+ uint8_t * const new_bin_choice,
+ struct efd_online_group_entry * const entry)
+{
+ unsigned int i;
+ int ret;
+ uint32_t new_idx;
+ void *new_k, *slot_id = NULL;
+ int status = EXIT_SUCCESS;
+ unsigned int found = 0;
+
+ efd_compute_ids(table, key, chunk_id, bin_id);
+
+ struct efd_offline_chunk_rules * const chunk =
+ &table->offline_chunks[*chunk_id];
+ struct efd_offline_group_rules *new_group;
+
+ uint8_t current_choice = efd_get_choice(table, socket_id,
+ *chunk_id, *bin_id);
+ uint32_t current_group_id = efd_bin_to_group[current_choice][*bin_id];
+ struct efd_offline_group_rules * const current_group =
+ &chunk->group_rules[current_group_id];
+ uint8_t bin_size = 0;
+ uint8_t key_changed_index = 0;
+ efd_value_t key_changed_previous_value = 0;
+ uint32_t key_idx_previous = 0;
+
+ /* Scan the current group and see if the key is already present */
+ for (i = 0; i < current_group->num_rules; i++) {
+ if (current_group->bin_id[i] == *bin_id)
+ bin_size++;
+ else
+ continue;
+
+ void *key_stored = EFD_KEY(current_group->key_idx[i], table);
+ if (found == 0 && unlikely(memcmp(key_stored, key,
+ table->key_len) == 0)) {
+ /* Key is already present */
+
+ /*
+ * If previous value is same as new value,
+ * no additional work is required
+ */
+ if (current_group->value[i] == value)
+ return RTE_EFD_UPDATE_NO_CHANGE;
+
+ key_idx_previous = current_group->key_idx[i];
+ key_changed_previous_value = current_group->value[i];
+ key_changed_index = i;
+ current_group->value[i] = value;
+ found = 1;
+ }
+ }
+
+ if (found == 0) {
+ /* Key does not exist. Insert the rule into the bin/group */
+ if (unlikely(current_group->num_rules >= EFD_MAX_GROUP_NUM_RULES)) {
+ RTE_LOG(ERR, EFD,
+ "Fatal: No room remaining for insert into "
+ "chunk %u group %u bin %u\n",
+ *chunk_id,
+ current_group_id, *bin_id);
+ return RTE_EFD_UPDATE_FAILED;
+ }
+
+ if (unlikely(current_group->num_rules ==
+ (EFD_MAX_GROUP_NUM_RULES - 1))) {
+ RTE_LOG(INFO, EFD, "Warn: Insert into last "
+ "available slot in chunk %u "
+ "group %u bin %u\n", *chunk_id,
+ current_group_id, *bin_id);
+ status = RTE_EFD_UPDATE_WARN_GROUP_FULL;
+ }
+
+ if (rte_ring_sc_dequeue(table->free_slots, &slot_id) != 0)
+ return RTE_EFD_UPDATE_FAILED;
+
+ new_k = RTE_PTR_ADD(table->keys, (uintptr_t) slot_id *
+ table->key_len);
+ rte_prefetch0(new_k);
+ new_idx = (uint32_t) ((uintptr_t) slot_id);
+
+ rte_memcpy(EFD_KEY(new_idx, table), key, table->key_len);
+ current_group->key_idx[current_group->num_rules] = new_idx;
+ current_group->value[current_group->num_rules] = value;
+ current_group->bin_id[current_group->num_rules] = *bin_id;
+ current_group->num_rules++;
+ table->num_rules++;
+ bin_size++;
+ } else {
+ uint32_t last = current_group->num_rules - 1;
+ /* Swap the key with the last key inserted*/
+ current_group->key_idx[key_changed_index] =
+ current_group->key_idx[last];
+ current_group->value[key_changed_index] =
+ current_group->value[last];
+ current_group->bin_id[key_changed_index] =
+ current_group->bin_id[last];
+
+ /*
+ * Key to be updated will always be available
+ * at the end of the group
+ */
+ current_group->key_idx[last] = key_idx_previous;
+ current_group->value[last] = value;
+ current_group->bin_id[last] = *bin_id;
+ }
+
+ *new_bin_choice = current_choice;
+ *group_id = current_group_id;
+ new_group = current_group;
+
+ /* Group need to be rebalanced when it starts to get loaded */
+ if (current_group->num_rules > EFD_MIN_BALANCED_NUM_RULES) {
+
+ /*
+ * Subtract the number of entries in the bin from
+ * the original group
+ */
+ current_group->num_rules -= bin_size;
+
+ /*
+ * Figure out which of the available groups that this bin
+ * can map to is the smallest (using the current group
+ * as baseline)
+ */
+ uint8_t smallest_choice = current_choice;
+ uint8_t smallest_size = current_group->num_rules;
+ uint32_t smallest_group_id = current_group_id;
+ unsigned char choice;
+
+ for (choice = 0; choice < EFD_CHUNK_NUM_BIN_TO_GROUP_SETS;
+ choice++) {
+ uint32_t test_group_id =
+ efd_bin_to_group[choice][*bin_id];
+ uint32_t num_rules =
+ chunk->group_rules[test_group_id].num_rules;
+ if (num_rules < smallest_size) {
+ smallest_choice = choice;
+ smallest_size = num_rules;
+ smallest_group_id = test_group_id;
+ }
+ }
+
+ *new_bin_choice = smallest_choice;
+ *group_id = smallest_group_id;
+ new_group = &chunk->group_rules[smallest_group_id];
+ current_group->num_rules += bin_size;
+
+ }
+
+ uint8_t choice = 0;
+ for (;;) {
+ if (current_group != new_group &&
+ new_group->num_rules + bin_size >
+ EFD_MAX_GROUP_NUM_RULES) {
+ RTE_LOG(DEBUG, EFD,
+ "Unable to move_groups to dest group "
+ "containing %u entries."
+ "bin_size:%u choice:%02x\n",
+ new_group->num_rules, bin_size,
+ choice - 1);
+ goto next_choice;
+ }
+ move_groups(*bin_id, bin_size, new_group, current_group);
+ /*
+ * Recompute the hash function for the modified group,
+ * and return it to the caller
+ */
+ ret = efd_search_hash(table, new_group, entry);
+
+ if (!ret)
+ return status;
+
+ RTE_LOG(DEBUG, EFD,
+ "Failed to find perfect hash for group "
+ "containing %u entries. bin_size:%u choice:%02x\n",
+ new_group->num_rules, bin_size, choice - 1);
+ /* Restore groups modified to their previous state */
+ revert_groups(current_group, new_group, bin_size);
+
+next_choice:
+ if (choice == EFD_CHUNK_NUM_BIN_TO_GROUP_SETS)
+ break;
+ *new_bin_choice = choice;
+ *group_id = efd_bin_to_group[choice][*bin_id];
+ new_group = &chunk->group_rules[*group_id];
+ choice++;
+ }
+
+ if (!found) {
+ current_group->num_rules--;
+ table->num_rules--;
+ } else
+ current_group->value[current_group->num_rules - 1] =
+ key_changed_previous_value;
+ return RTE_EFD_UPDATE_FAILED;
+}
+
+int
+rte_efd_update(struct rte_efd_table * const table, const unsigned int socket_id,
+ const void *key, const efd_value_t value)
+{
+ uint32_t chunk_id = 0, group_id = 0, bin_id = 0;
+ uint8_t new_bin_choice = 0;
+ struct efd_online_group_entry entry;
+
+ int status = efd_compute_update(table, socket_id, key, value,
+ &chunk_id, &group_id, &bin_id,
+ &new_bin_choice, &entry);
+
+ if (status == RTE_EFD_UPDATE_NO_CHANGE)
+ return EXIT_SUCCESS;
+
+ if (status == RTE_EFD_UPDATE_FAILED)
+ return status;
+
+ efd_apply_update(table, socket_id, chunk_id, group_id, bin_id,
+ new_bin_choice, &entry);
+ return status;
+}
+
+int
+rte_efd_delete(struct rte_efd_table * const table, const unsigned int socket_id,
+ const void *key, efd_value_t * const prev_value)
+{
+ unsigned int i;
+ uint32_t chunk_id, bin_id;
+ uint8_t not_found = 1;
+
+ efd_compute_ids(table, key, &chunk_id, &bin_id);
+
+ struct efd_offline_chunk_rules * const chunk =
+ &table->offline_chunks[chunk_id];
+
+ uint8_t current_choice = efd_get_choice(table, socket_id,
+ chunk_id, bin_id);
+ uint32_t current_group_id = efd_bin_to_group[current_choice][bin_id];
+ struct efd_offline_group_rules * const current_group =
+ &chunk->group_rules[current_group_id];
+
+ /*
+ * Search the current group for the specified key.
+ * If it exists, remove it and re-pack the other values
+ */
+ for (i = 0; i < current_group->num_rules; i++) {
+ if (not_found) {
+ /* Found key that needs to be removed */
+ if (memcmp(EFD_KEY(current_group->key_idx[i], table),
+ key, table->key_len) == 0) {
+ /* Store previous value if requested by caller */
+ if (prev_value != NULL)
+ *prev_value = current_group->value[i];
+
+ not_found = 0;
+ rte_ring_sp_enqueue(table->free_slots,
+ (void *)((uintptr_t)current_group->key_idx[i]));
+ }
+ } else {
+ /*
+ * If the desired key has been found,
+ * need to shift other values up one
+ */
+
+ /* Need to shift this entry back up one index */
+ current_group->key_idx[i - 1] = current_group->key_idx[i];
+ current_group->value[i - 1] = current_group->value[i];
+ current_group->bin_id[i - 1] = current_group->bin_id[i];
+ }
+ }
+
+ if (not_found == 0) {
+ table->num_rules--;
+ current_group->num_rules--;
+ }
+
+ return not_found;
+}
+
+static inline efd_value_t
+efd_lookup_internal_scalar(const efd_hashfunc_t *group_hash_idx,
+ const efd_lookuptbl_t *group_lookup_table,
+ const uint32_t hash_val_a, const uint32_t hash_val_b)
+{
+ efd_value_t value = 0;
+ uint32_t i;
+
+ for (i = 0; i < RTE_EFD_VALUE_NUM_BITS; i++) {
+ value <<= 1;
+ uint32_t h = hash_val_a + (hash_val_b *
+ group_hash_idx[RTE_EFD_VALUE_NUM_BITS - i - 1]);
+ uint16_t bucket_idx = h >> EFD_LOOKUPTBL_SHIFT;
+ value |= (group_lookup_table[
+ RTE_EFD_VALUE_NUM_BITS - i - 1] >>
+ bucket_idx) & 0x1;
+ }
+
+ return value;
+}
+
+
+static inline efd_value_t
+efd_lookup_internal(const struct efd_online_group_entry * const group,
+ const uint32_t hash_val_a, const uint32_t hash_val_b,
+ enum efd_lookup_internal_function lookup_fn)
+{
+ efd_value_t value = 0;
+
+ switch (lookup_fn) {
+
+#if defined(RTE_ARCH_X86)
+ case EFD_LOOKUP_AVX2:
+ return efd_lookup_internal_avx2(group->hash_idx,
+ group->lookup_table,
+ hash_val_a,
+ hash_val_b);
+#endif
+ case EFD_LOOKUP_SCALAR:
+ default:
+ return efd_lookup_internal_scalar(group->hash_idx,
+ group->lookup_table,
+ hash_val_a,
+ hash_val_b);
+ }
+
+ return value;
+}
+
+efd_value_t
+rte_efd_lookup(const struct rte_efd_table * const table,
+ const unsigned int socket_id, const void *key)
+{
+ uint32_t chunk_id, group_id, bin_id;
+ uint8_t bin_choice;
+ const struct efd_online_group_entry *group;
+ const struct efd_online_chunk * const chunks = table->chunks[socket_id];
+
+ /* Determine the chunk and group location for the given key */
+ efd_compute_ids(table, key, &chunk_id, &bin_id);
+ bin_choice = efd_get_choice(table, socket_id, chunk_id, bin_id);
+ group_id = efd_bin_to_group[bin_choice][bin_id];
+ group = &chunks[chunk_id].groups[group_id];
+
+ return efd_lookup_internal(group,
+ EFD_HASHFUNCA(key, table),
+ EFD_HASHFUNCB(key, table),
+ table->lookup_fn);
+}
+
+void rte_efd_lookup_bulk(const struct rte_efd_table * const table,
+ const unsigned int socket_id, const int num_keys,
+ const void **key_list, efd_value_t * const value_list)
+{
+ int i;
+ uint32_t chunk_id_list[RTE_EFD_BURST_MAX];
+ uint32_t bin_id_list[RTE_EFD_BURST_MAX];
+ uint8_t bin_choice_list[RTE_EFD_BURST_MAX];
+ uint32_t group_id_list[RTE_EFD_BURST_MAX];
+ struct efd_online_group_entry *group;
+
+ struct efd_online_chunk *chunks = table->chunks[socket_id];
+
+ for (i = 0; i < num_keys; i++) {
+ efd_compute_ids(table, key_list[i], &chunk_id_list[i],
+ &bin_id_list[i]);
+ rte_prefetch0(&chunks[chunk_id_list[i]].bin_choice_list);
+ }
+
+ for (i = 0; i < num_keys; i++) {
+ bin_choice_list[i] = efd_get_choice(table, socket_id,
+ chunk_id_list[i], bin_id_list[i]);
+ group_id_list[i] =
+ efd_bin_to_group[bin_choice_list[i]][bin_id_list[i]];
+ group = &chunks[chunk_id_list[i]].groups[group_id_list[i]];
+ rte_prefetch0(group);
+ }
+
+ for (i = 0; i < num_keys; i++) {
+ group = &chunks[chunk_id_list[i]].groups[group_id_list[i]];
+ value_list[i] = efd_lookup_internal(group,
+ EFD_HASHFUNCA(key_list[i], table),
+ EFD_HASHFUNCB(key_list[i], table),
+ table->lookup_fn);
+ }
+}
diff --git a/lib/librte_efd/rte_efd.h b/lib/librte_efd/rte_efd.h
new file mode 100644
index 0000000..1a1cb5b
--- /dev/null
+++ b/lib/librte_efd/rte_efd.h
@@ -0,0 +1,300 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _RTE_EFD_H_
+#define _RTE_EFD_H_
+
+/**
+ * @file
+ *
+ * RTE EFD Table
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*************************************************************************
+ * User selectable constants
+ *************************************************************************/
+
+/*
+ * If possible, best lookup performance will be achieved by ensuring that
+ * the entire table fits in the L3 cache.
+ *
+ * Some formulas for calculating various sizes are listed below:
+ *
+ * # of chunks =
+ * 2 ^ (ceiling(log2((requested # of rules) /
+ * (EFD_CHUNK_NUM_GROUPS * EFD_TARGET_GROUP_NUM_RULES))))
+ *
+ * Target # of rules = (# of chunks) * EFD_CHUNK_NUM_GROUPS *
+ * EFD_TARGET_GROUP_NUM_RULES
+ *
+ * Group Size (in bytes) = 4 (per value bit)
+ *
+ * Table size (in bytes) = RTE_EFD_VALUE_NUM_BITS * (# of chunks) *
+ * EFD_CHUNK_NUM_GROUPS * (group size)
+ */
+
+/**
+ * !!! This parameter should be adjusted for your application !!!
+ *
+ * This parameter adjusts the number of bits of value that can be
+ * stored in the table.
+ * For example, setting the number of bits to 3 will allow storing 8 values
+ * in the table (between 0 and 7).
+ *
+ * This number directly affects the performance of both lookups and insertion.
+ * In general, performance decreases as more bits are stored in the table.
+ *
+ * This number is directly proportional to the size of the online region
+ * used for lookups.
+ *
+ * Note that due to the way the CPU operates on memory, best lookup performance
+ * will be achieved when RTE_EFD_VALUE_NUM_BITS is a multiple of 8.
+ * These values align the hash indexes on 16-byte boundaries.
+ * The greatest performance drop is moving from 8->9 bits, 16->17 bits, etc.
+ *
+ * This value must be between 1 and 32
+ */
+#ifndef RTE_EFD_VALUE_NUM_BITS
+#define RTE_EFD_VALUE_NUM_BITS (8)
+#endif
+
+/*
+ * EFD_TARGET_GROUP_NUM_RULES:
+ * Adjusts how many groups/chunks are allocated at table creation time
+ * to support the requested number of rules. Higher values pack entries
+ * more tightly in memory, resulting in a smaller memory footprint
+ * for the online table.
+ * This comes at the cost of lower insert/update performance.
+ *
+ * EFD_MAX_GROUP_NUM_RULES:
+ * This adjusts the amount of offline memory allocated to store key/value
+ * pairs for the table. The recommended numbers are upper-bounds for
+ * this parameter
+ * - any higher and it becomes very unlikely that a perfect hash function
+ * can be found for that group size. This value should be at
+ * least 40% larger than EFD_TARGET_GROUP_NUM_RULES
+ *
+ * Recommended values for various lookuptable and hashfunc sizes are:
+ *
+ * HASH_FUNC_SIZE = 16, LOOKUPTBL_SIZE = 16:
+ * EFD_TARGET_GROUP_NUM_RULES = 22
+ * EFD_MAX_GROUP_NUM_RULES = 28
+ */
+#define EFD_TARGET_GROUP_NUM_RULES (22)
+#define EFD_MAX_GROUP_NUM_RULES (28LU)
+
+#define EFD_MIN_BALANCED_NUM_RULES 5
+
+/**
+ * Maximum number of keys that can be looked up in one call to efd_lookup_bulk
+ */
+#ifndef RTE_EFD_BURST_MAX
+#define RTE_EFD_BURST_MAX (32)
+#endif
+
+/** Maximum number of characters in efd name.*/
+#define RTE_EFD_NAMESIZE 32
+
+#if (RTE_EFD_VALUE_NUM_BITS > 0 && RTE_EFD_VALUE_NUM_BITS <= 8)
+typedef uint8_t efd_value_t;
+#elif (RTE_EFD_VALUE_NUM_BITS > 8 && RTE_EFD_VALUE_NUM_BITS <= 16)
+typedef uint16_t efd_value_t;
+#elif (RTE_EFD_VALUE_NUM_BITS > 16 && RTE_EFD_VALUE_NUM_BITS <= 32)
+typedef uint32_t efd_value_t;
+#else
+#error("RTE_EFD_VALUE_NUM_BITS must be in the range [1:32]")
+#endif
+
+#define EFD_LOOKUPTBL_SHIFT (32 - 4)
+typedef uint16_t efd_lookuptbl_t;
+typedef uint16_t efd_hashfunc_t;
+
+/**
+ * Creates an EFD table with a single offline region and multiple per-socket
+ * internally-managed copies of the online table used for lookups
+ *
+ * @param name
+ * EFD table name
+ * @param max_num_rules
+ * Minimum number of rules the table should be sized to hold.
+ * Will be rounded up to the next smallest valid table size
+ * @param key_len
+ * Length of the key
+ * @param online_cpu_socket_bitmask
+ * Bitmask specifying which sockets should get a copy of the online table.
+ * LSB = socket 0, etc.
+ * @param offline_cpu_socket
+ * Identifies the socket where the offline table will be allocated
+ * (and most efficiently accessed in the case of updates/insertions)
+ *
+ * @return
+ * EFD table, or NULL if table allocation failed or the bitmask is invalid
+ */
+struct rte_efd_table *
+rte_efd_create(const char *name, uint32_t max_num_rules, uint32_t key_len,
+ uint8_t online_cpu_socket_bitmask, uint8_t offline_cpu_socket);
+
+/**
+ * Releases the resources from an EFD table
+ *
+ * @param table
+ * Table to free
+ */
+void
+rte_efd_free(struct rte_efd_table *table);
+
+/**
+ * Find an existing EFD table object and return a pointer to it.
+ *
+ * @param name
+ * Name of the EFD table as passed to rte_efd_create()
+ * @return
+ * Pointer to EFD table or NULL if object not found
+ * with rte_errno set appropriately. Possible rte_errno values include:
+ * - ENOENT - value not available for return
+ */
+struct rte_efd_table*
+rte_efd_find_existing(const char *name);
+
+#define RTE_EFD_UPDATE_WARN_GROUP_FULL (1)
+#define RTE_EFD_UPDATE_NO_CHANGE (2)
+#define RTE_EFD_UPDATE_FAILED (3)
+
+/**
+ * Computes an updated table entry for the supplied key/value pair.
+ * The update is then immediately applied to the provided table and
+ * all socket-local copies of the chunks are updated.
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param key
+ * EFD table key to modify
+ * @param value
+ * Value to associate with the key
+ *
+ * @return
+ * RTE_EFD_UPDATE_WARN_GROUP_FULL
+ * Operation is insert, and the last available space in the
+ * key's group was just used
+ * Future inserts may fail as groups fill up
+ * This operation was still successful, and entry contains a valid update
+ * RTE_EFD_UPDATE_FAILED
+ * Either the EFD failed to find a suitable perfect hash or the group was full
+ * This is a fatal error, and the table is now in an indeterminite state
+ * RTE_EFD_UPDATE_NO_CHANGE
+ * Operation resulted in no change to the table (same value already exists)
+ * 0 - success
+ */
+int
+rte_efd_update(struct rte_efd_table *table, unsigned int socket_id,
+ const void *key, efd_value_t value);
+
+/**
+ * Removes any value currently associated with the specified key from the table
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param key
+ * EFD table key to delete
+ * @param prev_value
+ * If not NULL, will store the previous value here before deleting it
+ *
+ * @return
+ * 0 - successfully found and deleted the key
+ * nonzero otherwise
+ */
+int
+rte_efd_delete(struct rte_efd_table *table, unsigned int socket_id,
+ const void *key, efd_value_t *prev_value);
+
+/**
+ * Looks up the value associated with a key
+ *
+ * NOTE: Lookups will *always* succeed - this is a property of
+ * using a perfect hash table.
+ * If the specified key was never inserted, a pseudorandom answer will be returned.
+ * There is no way to know based on the lookup if the key was ever inserted
+ * originally, so this must be tracked elsewhere.
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param key
+ * EFD table key to look up
+ *
+ * @return
+ * Value associated with the key, or random junk if they key was never inserted
+ */
+efd_value_t
+rte_efd_lookup(const struct rte_efd_table *table, unsigned int socket_id,
+ const void *key);
+
+/**
+ * Looks up the value associated with several keys.
+ *
+ * NOTE: Lookups will *always* succeed - this is a property of
+ * using a perfect hash table.
+ * If the specified key was never inserted, a pseudorandom answer will be returned.
+ * There is no way to know based on the lookup if the key was ever inserted
+ * originally, so this must be tracked elsewhere.
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param num_keys
+ * Number of keys in the key_list array, must be less than RTE_EFD_BURST_MAX
+ * @param key_list
+ * Array of num_keys pointers which point to keys to look up
+ * @param value_list
+ * Array of size num_keys where lookup values will be stored
+ */
+void
+rte_efd_lookup_bulk(const struct rte_efd_table *table, unsigned int socket_id,
+ int num_keys, const void **key_list,
+ efd_value_t *value_list);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RTE_EFD_H_ */
diff --git a/lib/librte_efd/rte_efd_version.map b/lib/librte_efd/rte_efd_version.map
new file mode 100644
index 0000000..ae60a64
--- /dev/null
+++ b/lib/librte_efd/rte_efd_version.map
@@ -0,0 +1,13 @@
+DPDK_17.02 {
+ global:
+
+ rte_efd_create;
+ rte_efd_delete;
+ rte_efd_find_existing;
+ rte_efd_free;
+ rte_efd_lookup;
+ rte_efd_lookup_bulk;
+ rte_efd_update;
+
+ local: *;
+};
diff --git a/lib/librte_efd/rte_efd_x86.h b/lib/librte_efd/rte_efd_x86.h
new file mode 100644
index 0000000..00b40e8
--- /dev/null
+++ b/lib/librte_efd/rte_efd_x86.h
@@ -0,0 +1,85 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* rte_efd_x86.h
+ * This file holds all x86 specific EFD functions
+ */
+
+#if (RTE_EFD_VALUE_NUM_BITS == 8 || RTE_EFD_VALUE_NUM_BITS == 16 || \
+ RTE_EFD_VALUE_NUM_BITS == 24 || RTE_EFD_VALUE_NUM_BITS == 32)
+#define EFD_LOAD_SI128(val) _mm_load_si128(val)
+#else
+#define EFD_LOAD_SI128(val) _mm_lddqu_si128(val)
+#endif
+
+static inline efd_value_t
+efd_lookup_internal_avx2(const efd_hashfunc_t *group_hash_idx,
+ const efd_lookuptbl_t *group_lookup_table,
+ const uint32_t hash_val_a, const uint32_t hash_val_b)
+{
+#ifdef RTE_MACHINE_CPUFLAG_AVX2
+ efd_value_t value = 0;
+ uint32_t i = 0;
+ __m256i vhash_val_a = _mm256_set1_epi32(hash_val_a);
+ __m256i vhash_val_b = _mm256_set1_epi32(hash_val_b);
+
+ for (; i < RTE_EFD_VALUE_NUM_BITS; i += 8) {
+ __m256i vhash_idx =
+ _mm256_cvtepu16_epi32(EFD_LOAD_SI128(
+ (__m128i const *) &group_hash_idx[i]));
+ __m256i vlookup_table = _mm256_cvtepu16_epi32(
+ EFD_LOAD_SI128((__m128i const *)
+ &group_lookup_table[i]));
+ __m256i vhash = _mm256_add_epi32(vhash_val_a,
+ _mm256_mullo_epi32(vhash_idx, vhash_val_b));
+ __m256i vbucket_idx = _mm256_srli_epi32(vhash,
+ EFD_LOOKUPTBL_SHIFT);
+ __m256i vresult = _mm256_srlv_epi32(vlookup_table,
+ vbucket_idx);
+
+ value |= (_mm256_movemask_ps(
+ (__m256) _mm256_slli_epi32(vresult, 31))
+ & ((1 << (RTE_EFD_VALUE_NUM_BITS - i)) - 1)) << i;
+ }
+
+ return value;
+#else
+ RTE_SET_USED(group_hash_idx);
+ RTE_SET_USED(group_lookup_table);
+ RTE_SET_USED(hash_val_a);
+ RTE_SET_USED(hash_val_b);
+ /* Return dummy value, only to avoid compilation breakage */
+ return 0;
+#endif
+
+}
diff --git a/mk/rte.app.mk b/mk/rte.app.mk
index f75f0e2..ed1e68a 100644
--- a/mk/rte.app.mk
+++ b/mk/rte.app.mk
@@ -1,6 +1,6 @@
# BSD LICENSE
#
-# Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+# Copyright(c) 2010-2017 Intel Corporation. All rights reserved.
# Copyright(c) 2014-2015 6WIND S.A.
# All rights reserved.
#
@@ -86,6 +86,7 @@ _LDLIBS-y += --whole-archive
_LDLIBS-$(CONFIG_RTE_LIBRTE_TIMER) += -lrte_timer
_LDLIBS-$(CONFIG_RTE_LIBRTE_HASH) += -lrte_hash
+_LDLIBS-$(CONFIG_RTE_LIBRTE_EFD) += -lrte_efd
_LDLIBS-$(CONFIG_RTE_LIBRTE_VHOST) += -lrte_vhost
_LDLIBS-$(CONFIG_RTE_LIBRTE_KVARGS) += -lrte_kvargs
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v6 2/5] app/test: add EFD functional and perf tests
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 " Pablo de Lara
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 1/5] efd: new Elastic Flow Distributor library Pablo de Lara
@ 2017-01-16 19:21 ` Pablo de Lara
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 3/5] examples/flow_distributor: sample app to demonstrate EFD usage Pablo de Lara
` (4 subsequent siblings)
6 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-16 19:21 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Byron Marohn, Karla Saur, Saikrishna Edupuganti
Signed-off-by: Byron Marohn <byron.marohn@intel.com>
Signed-off-by: Karla Saur <karla.saur@intel.com>
Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 1 +
app/test/Makefile | 5 +-
app/test/test_efd.c | 494 +++++++++++++++++++++++++++++++++++++++++++++++
app/test/test_efd_perf.c | 407 ++++++++++++++++++++++++++++++++++++++
4 files changed, 906 insertions(+), 1 deletion(-)
create mode 100644 app/test/test_efd.c
create mode 100644 app/test/test_efd_perf.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 9c60d67..d812962 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -532,6 +532,7 @@ EFD
M: Byron Marohn <byron.marohn@intel.com>
M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
F: lib/librte_efd/
+F: app/test/test_efd*
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
diff --git a/app/test/Makefile b/app/test/Makefile
index 5be023a..9de301f 100644
--- a/app/test/Makefile
+++ b/app/test/Makefile
@@ -1,6 +1,6 @@
# BSD LICENSE
#
-# Copyright(c) 2010-2015 Intel Corporation. All rights reserved.
+# Copyright(c) 2010-2017 Intel Corporation. All rights reserved.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -123,6 +123,9 @@ SRCS-y += test_logs.c
SRCS-y += test_memcpy.c
SRCS-y += test_memcpy_perf.c
+SRCS-$(CONFIG_RTE_LIBRTE_EFD) += test_efd.c
+SRCS-$(CONFIG_RTE_LIBRTE_EFD) += test_efd_perf.c
+
SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_hash.c
SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_thash.c
SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_hash_perf.c
diff --git a/app/test/test_efd.c b/app/test/test_efd.c
new file mode 100644
index 0000000..d5c3bd9
--- /dev/null
+++ b/app/test/test_efd.c
@@ -0,0 +1,494 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rte_memcpy.h>
+#include <rte_malloc.h>
+#include <rte_efd.h>
+#include <rte_byteorder.h>
+#include <rte_random.h>
+#include <rte_debug.h>
+#include <rte_ip.h>
+
+#include "test.h"
+
+#define EFD_TEST_KEY_LEN 8
+#define TABLE_SIZE (1 << 21)
+#define ITERATIONS 3
+static unsigned int test_socket_id;
+
+/* 5-tuple key type */
+struct flow_key {
+ uint32_t ip_src;
+ uint32_t ip_dst;
+ uint16_t port_src;
+ uint16_t port_dst;
+ uint8_t proto;
+} __attribute__((packed));
+/*
+ * Print out result of unit test efd operation.
+ */
+#if defined(UNIT_TEST_EFD_VERBOSE)
+
+static void print_key_info(const char *msg, const struct flow_key *key,
+ efd_value_t val)
+{
+ const uint8_t *p = (const uint8_t *) key;
+ unsigned int i;
+
+ printf("%s key:0x", msg);
+ for (i = 0; i < sizeof(struct flow_key); i++)
+ printf("%02X", p[i]);
+
+ printf(" @ val %d\n", val);
+}
+#else
+
+static void print_key_info(__attribute__((unused)) const char *msg,
+ __attribute__((unused)) const struct flow_key *key,
+ __attribute__((unused)) efd_value_t val)
+{
+}
+#endif
+
+/* Keys used by unit test functions */
+static struct flow_key keys[5] = {
+ {
+ .ip_src = IPv4(0x03, 0x02, 0x01, 0x00),
+ .ip_dst = IPv4(0x07, 0x06, 0x05, 0x04),
+ .port_src = 0x0908,
+ .port_dst = 0x0b0a,
+ .proto = 0x0c,
+ },
+ {
+ .ip_src = IPv4(0x13, 0x12, 0x11, 0x10),
+ .ip_dst = IPv4(0x17, 0x16, 0x15, 0x14),
+ .port_src = 0x1918,
+ .port_dst = 0x1b1a,
+ .proto = 0x1c,
+ },
+ {
+ .ip_src = IPv4(0x23, 0x22, 0x21, 0x20),
+ .ip_dst = IPv4(0x27, 0x26, 0x25, 0x24),
+ .port_src = 0x2928,
+ .port_dst = 0x2b2a,
+ .proto = 0x2c,
+ },
+ {
+ .ip_src = IPv4(0x33, 0x32, 0x31, 0x30),
+ .ip_dst = IPv4(0x37, 0x36, 0x35, 0x34),
+ .port_src = 0x3938,
+ .port_dst = 0x3b3a,
+ .proto = 0x3c,
+ },
+ {
+ .ip_src = IPv4(0x43, 0x42, 0x41, 0x40),
+ .ip_dst = IPv4(0x47, 0x46, 0x45, 0x44),
+ .port_src = 0x4948,
+ .port_dst = 0x4b4a,
+ .proto = 0x4c,
+ }
+};
+/* Array to store the data */
+efd_value_t data[5];
+
+static inline uint8_t efd_get_all_sockets_bitmask(void)
+{
+ uint8_t all_cpu_sockets_bitmask = 0;
+ unsigned int i;
+ unsigned int next_lcore = rte_get_master_lcore();
+ const int val_true = 1, val_false = 0;
+ for (i = 0; i < rte_lcore_count(); i++) {
+ all_cpu_sockets_bitmask |= 1 << rte_lcore_to_socket_id(next_lcore);
+ next_lcore = rte_get_next_lcore(next_lcore, val_false, val_true);
+ }
+
+ return all_cpu_sockets_bitmask;
+}
+
+/*
+ * Basic sequence of operations for a single key:
+ * - add
+ * - lookup (hit)
+ * - delete
+ * Note: lookup (miss) is not applicable since this is a filter
+ */
+static int test_add_delete(void)
+{
+ struct rte_efd_table *handle;
+ /* test with standard add/lookup/delete functions */
+ efd_value_t prev_value;
+ printf("Entering %s\n", __func__);
+
+ handle = rte_efd_create("test_add_delete",
+ TABLE_SIZE, sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the EFD table\n");
+
+ data[0] = mrand48() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id, &keys[0],
+ data[0]),
+ "Error inserting the key");
+ print_key_info("Add", &keys[0], data[0]);
+
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id, &keys[0]),
+ data[0],
+ "failed to find key");
+
+ TEST_ASSERT_SUCCESS(rte_efd_delete(handle, test_socket_id, &keys[0],
+ &prev_value),
+ "failed to delete key");
+ TEST_ASSERT_EQUAL(prev_value, data[0],
+ "failed to delete the expected value, got %d, "
+ "expected %d", prev_value, data[0]);
+ print_key_info("Del", &keys[0], data[0]);
+
+ rte_efd_free(handle);
+
+ return 0;
+}
+
+/*
+ * Sequence of operations for a single key:
+ * - add
+ * - lookup: hit
+ * - add: update
+ * - lookup: hit (updated data)
+ * - delete: hit
+ */
+static int test_add_update_delete(void)
+{
+ struct rte_efd_table *handle;
+ printf("Entering %s\n", __func__);
+ /* test with standard add/lookup/delete functions */
+ efd_value_t prev_value;
+ data[1] = mrand48() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+
+ handle = rte_efd_create("test_add_update_delete", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the efd table\n");
+
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id, &keys[1],
+ data[1]), "Error inserting the key");
+ print_key_info("Add", &keys[1], data[1]);
+
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id, &keys[1]),
+ data[1], "failed to find key");
+ print_key_info("Lkp", &keys[1], data[1]);
+
+ data[1] = data[1] + 1;
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id, &keys[1],
+ data[1]), "Error re-inserting the key");
+ print_key_info("Add", &keys[1], data[1]);
+
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id, &keys[1]),
+ data[1], "failed to find key");
+ print_key_info("Lkp", &keys[1], data[1]);
+
+ TEST_ASSERT_SUCCESS(rte_efd_delete(handle, test_socket_id, &keys[1],
+ &prev_value), "failed to delete key");
+ TEST_ASSERT_EQUAL(prev_value, data[1],
+ "failed to delete the expected value, got %d, "
+ "expected %d", prev_value, data[1]);
+ print_key_info("Del", &keys[1], data[1]);
+
+
+ rte_efd_free(handle);
+ return 0;
+}
+
+/*
+ * Sequence of operations for find existing EFD table
+ *
+ * - create table
+ * - find existing table: hit
+ * - find non-existing table: miss
+ *
+ */
+static int test_efd_find_existing(void)
+{
+ struct rte_efd_table *handle = NULL, *result = NULL;
+
+ printf("Entering %s\n", __func__);
+
+ /* Create EFD table. */
+ handle = rte_efd_create("efd_find_existing", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the efd table\n");
+
+ /* Try to find existing EFD table */
+ result = rte_efd_find_existing("efd_find_existing");
+ TEST_ASSERT_EQUAL(result, handle, "could not find existing efd table");
+
+ /* Try to find non-existing EFD table */
+ result = rte_efd_find_existing("efd_find_non_existing");
+ TEST_ASSERT_NULL(result, "found table that shouldn't exist");
+
+ /* Cleanup. */
+ rte_efd_free(handle);
+
+ return 0;
+}
+
+/*
+ * Sequence of operations for 5 keys
+ * - add keys
+ * - lookup keys: hit (bulk)
+ * - add keys (update)
+ * - lookup keys: hit (updated data)
+ * - delete keys : hit
+ */
+static int test_five_keys(void)
+{
+ struct rte_efd_table *handle;
+ const void *key_array[5] = {0};
+ efd_value_t result[5] = {0};
+ efd_value_t prev_value;
+ unsigned int i;
+ printf("Entering %s\n", __func__);
+
+ handle = rte_efd_create("test_five_keys", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the efd table\n");
+
+ /* Setup data */
+ for (i = 0; i < 5; i++)
+ data[i] = mrand48() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+
+ /* Add */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id,
+ &keys[i], data[i]),
+ "Error inserting the key");
+ print_key_info("Add", &keys[i], data[i]);
+ }
+
+ /* Lookup */
+ for (i = 0; i < 5; i++)
+ key_array[i] = &keys[i];
+
+ rte_efd_lookup_bulk(handle, test_socket_id, 5,
+ (const void **) (void *) &key_array, result);
+
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_EQUAL(result[i], data[i],
+ "bulk: failed to find key. Expected %d, got %d",
+ data[i], result[i]);
+ print_key_info("Lkp", &keys[i], data[i]);
+ }
+
+ /* Modify data (bulk) */
+ for (i = 0; i < 5; i++)
+ data[i] = data[i] + 1;
+
+ /* Add - update */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id,
+ &keys[i], data[i]),
+ "Error inserting the key");
+ print_key_info("Add", &keys[i], data[i]);
+ }
+
+ /* Lookup */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id,
+ &keys[i]), data[i],
+ "failed to find key");
+ print_key_info("Lkp", &keys[i], data[i]);
+ }
+
+ /* Delete */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_SUCCESS(rte_efd_delete(handle, test_socket_id,
+ &keys[i], &prev_value),
+ "failed to delete key");
+ TEST_ASSERT_EQUAL(prev_value, data[i],
+ "failed to delete the expected value, got %d, "
+ "expected %d", prev_value, data[i]);
+ print_key_info("Del", &keys[i], data[i]);
+ }
+
+
+ rte_efd_free(handle);
+
+ return 0;
+}
+
+/*
+ * Test to see the average table utilization (entries added/max entries)
+ * before hitting a random entry that cannot be added
+ */
+static int test_average_table_utilization(void)
+{
+ struct rte_efd_table *handle = NULL;
+ uint32_t num_rules_in = TABLE_SIZE;
+ uint8_t simple_key[EFD_TEST_KEY_LEN];
+ unsigned int i, j;
+ unsigned int added_keys, average_keys_added = 0;
+
+ printf("Evaluating table utilization and correctness, please wait\n");
+ fflush(stdout);
+
+ for (j = 0; j < ITERATIONS; j++) {
+ handle = rte_efd_create("test_efd", num_rules_in,
+ EFD_TEST_KEY_LEN, efd_get_all_sockets_bitmask(),
+ test_socket_id);
+ if (handle == NULL) {
+ printf("efd table creation failed\n");
+ return -1;
+ }
+
+ unsigned int succeeded = 0;
+ unsigned int lost_keys = 0;
+
+ /* Add random entries until key cannot be added */
+ for (added_keys = 0; added_keys < num_rules_in; added_keys++) {
+
+ for (i = 0; i < EFD_TEST_KEY_LEN; i++)
+ simple_key[i] = rte_rand() & 0xFF;
+
+ efd_value_t val = simple_key[0];
+
+ if (rte_efd_update(handle, test_socket_id, simple_key,
+ val))
+ break; /* continue;*/
+ if (rte_efd_lookup(handle, test_socket_id, simple_key)
+ != val)
+ lost_keys++;
+ else
+ succeeded++;
+ }
+
+ average_keys_added += succeeded;
+
+ /* Reset the table */
+ rte_efd_free(handle);
+
+ /* Print progress on operations */
+ printf("Added %10u Succeeded %10u Lost %10u\n",
+ added_keys, succeeded, lost_keys);
+ fflush(stdout);
+ }
+
+ average_keys_added /= ITERATIONS;
+
+ printf("\nAverage table utilization = %.2f%% (%u/%u)\n",
+ ((double) average_keys_added / num_rules_in * 100),
+ average_keys_added, num_rules_in);
+
+ return 0;
+}
+
+/*
+ * Do tests for EFD creation with bad parameters.
+ */
+static int test_efd_creation_with_bad_parameters(void)
+{
+ struct rte_efd_table *handle, *tmp;
+ printf("Entering %s, **Errors are expected **\n", __func__);
+
+ handle = rte_efd_create("creation_with_bad_parameters_0", TABLE_SIZE, 0,
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ if (handle != NULL) {
+ rte_efd_free(handle);
+ printf("Impossible creating EFD table successfully "
+ "if key_len in parameter is zero\n");
+ return -1;
+ }
+
+ handle = rte_efd_create("creation_with_bad_parameters_1", TABLE_SIZE,
+ sizeof(struct flow_key), 0, test_socket_id);
+ if (handle != NULL) {
+ rte_efd_free(handle);
+ printf("Impossible creating EFD table successfully "
+ "with invalid socket bitmask\n");
+ return -1;
+ }
+
+ handle = rte_efd_create("creation_with_bad_parameters_2", TABLE_SIZE,
+ sizeof(struct flow_key), efd_get_all_sockets_bitmask(),
+ 255);
+ if (handle != NULL) {
+ rte_efd_free(handle);
+ printf("Impossible creating EFD table successfully "
+ "with invalid socket\n");
+ return -1;
+ }
+
+ /* test with same name should fail */
+ handle = rte_efd_create("same_name", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), 0);
+ if (handle == NULL) {
+ printf("Cannot create first EFD table with 'same_name'\n");
+ return -1;
+ }
+ tmp = rte_efd_create("same_name", TABLE_SIZE, sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), 0);
+ if (tmp != NULL) {
+ printf("Creation of EFD table with same name should fail\n");
+ rte_efd_free(handle);
+ rte_efd_free(tmp);
+ return -1;
+ }
+ rte_efd_free(handle);
+
+ printf("# Test successful. No more errors expected\n");
+
+ return 0;
+}
+
+static int
+test_efd(void)
+{
+
+ /* Unit tests */
+ if (test_add_delete() < 0)
+ return -1;
+ if (test_efd_find_existing() < 0)
+ return -1;
+ if (test_add_update_delete() < 0)
+ return -1;
+ if (test_five_keys() < 0)
+ return -1;
+ if (test_efd_creation_with_bad_parameters() < 0)
+ return -1;
+ if (test_average_table_utilization() < 0)
+ return -1;
+
+ return 0;
+}
+
+REGISTER_TEST_COMMAND(efd_autotest, test_efd);
diff --git a/app/test/test_efd_perf.c b/app/test/test_efd_perf.c
new file mode 100644
index 0000000..998a25b
--- /dev/null
+++ b/app/test/test_efd_perf.c
@@ -0,0 +1,407 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <inttypes.h>
+
+#include <rte_lcore.h>
+#include <rte_cycles.h>
+#include <rte_malloc.h>
+#include <rte_random.h>
+#include <rte_efd.h>
+#include <rte_memcpy.h>
+#include <rte_thash.h>
+
+#include "test.h"
+#define NUM_KEYSIZES 10
+#define NUM_SHUFFLES 10
+#define MAX_KEYSIZE 64
+#define MAX_ENTRIES (1 << 19)
+#define KEYS_TO_ADD (MAX_ENTRIES * 3 / 4) /* 75% table utilization */
+#define NUM_LOOKUPS (KEYS_TO_ADD * 5) /* Loop among keys added, several times */
+static unsigned int test_socket_id;
+
+static inline uint8_t efd_get_all_sockets_bitmask(void)
+{
+ uint8_t all_cpu_sockets_bitmask = 0;
+ unsigned int i;
+ unsigned int next_lcore = rte_get_master_lcore();
+ const int val_true = 1, val_false = 0;
+ for (i = 0; i < rte_lcore_count(); i++) {
+ all_cpu_sockets_bitmask |= 1 << rte_lcore_to_socket_id(next_lcore);
+ next_lcore = rte_get_next_lcore(next_lcore, val_false, val_true);
+ }
+
+ return all_cpu_sockets_bitmask;
+}
+
+enum operations {
+ ADD = 0,
+ LOOKUP,
+ LOOKUP_MULTI,
+ DELETE,
+ NUM_OPERATIONS
+};
+
+struct efd_perf_params {
+ struct rte_efd_table *efd_table;
+ uint32_t key_size;
+ unsigned int cycle;
+};
+
+static uint32_t hashtest_key_lens[] = {
+ /* standard key sizes */
+ 4, 8, 16, 32, 48, 64,
+ /* IPv4 SRC + DST + protocol, unpadded */
+ 9,
+ /* IPv4 5-tuple, unpadded */
+ 13,
+ /* IPv6 5-tuple, unpadded */
+ 37,
+ /* IPv6 5-tuple, padded to 8-byte boundary */
+ 40
+};
+
+/* Array to store number of cycles per operation */
+uint64_t cycles[NUM_KEYSIZES][NUM_OPERATIONS];
+
+/* Array to store the data */
+efd_value_t data[KEYS_TO_ADD];
+
+/* Array to store all input keys */
+uint8_t keys[KEYS_TO_ADD][MAX_KEYSIZE];
+
+/* Shuffle the keys that have been added, so lookups will be totally random */
+static void
+shuffle_input_keys(struct efd_perf_params *params)
+{
+ efd_value_t temp_data;
+ unsigned int i;
+ uint32_t swap_idx;
+ uint8_t temp_key[MAX_KEYSIZE];
+
+ for (i = KEYS_TO_ADD - 1; i > 0; i--) {
+ swap_idx = rte_rand() % i;
+
+ memcpy(temp_key, keys[i], hashtest_key_lens[params->cycle]);
+ temp_data = data[i];
+
+ memcpy(keys[i], keys[swap_idx], hashtest_key_lens[params->cycle]);
+ data[i] = data[swap_idx];
+
+ memcpy(keys[swap_idx], temp_key, hashtest_key_lens[params->cycle]);
+ data[swap_idx] = temp_data;
+ }
+}
+
+static int key_compare(const void *key1, const void *key2)
+{
+ return memcmp(key1, key2, MAX_KEYSIZE);
+}
+
+/*
+ * TODO: we could "error proof" these as done in test_hash_perf.c ln 165:
+ *
+ * The current setup may give errors if too full in some cases which we check
+ * for. However, since EFD allows for ~99% capacity, these errors are rare for
+ * #"KEYS_TO_ADD" which is 75% capacity.
+ */
+static int
+setup_keys_and_data(struct efd_perf_params *params, unsigned int cycle)
+{
+ unsigned int i, j;
+ int num_duplicates;
+
+ params->key_size = hashtest_key_lens[cycle];
+ params->cycle = cycle;
+
+ /* Reset all arrays */
+ for (i = 0; i < params->key_size; i++)
+ keys[0][i] = 0;
+
+ /* Generate a list of keys, some of which may be duplicates */
+ for (i = 0; i < KEYS_TO_ADD; i++) {
+ for (j = 0; j < params->key_size; j++)
+ keys[i][j] = rte_rand() & 0xFF;
+
+ data[i] = rte_rand() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+ }
+
+ /* Remove duplicates from the keys array */
+ do {
+ num_duplicates = 0;
+
+ /* Sort the list of keys to make it easier to find duplicates */
+ qsort(keys, KEYS_TO_ADD, MAX_KEYSIZE, key_compare);
+
+ /* Sift through the list of keys and look for duplicates */
+ int num_duplicates = 0;
+ for (i = 0; i < KEYS_TO_ADD - 1; i++) {
+ if (memcmp(keys[i], keys[i + 1], params->key_size) == 0) {
+ /* This key already exists, try again */
+ num_duplicates++;
+ for (j = 0; j < params->key_size; j++)
+ keys[i][j] = rte_rand() & 0xFF;
+ }
+ }
+ } while (num_duplicates != 0);
+
+ /* Shuffle the random values again */
+ shuffle_input_keys(params);
+
+ params->efd_table = rte_efd_create("test_efd_perf",
+ MAX_ENTRIES, params->key_size,
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(params->efd_table, "Error creating the efd table\n");
+
+ return 0;
+}
+
+static int
+timed_adds(struct efd_perf_params *params)
+{
+ const uint64_t start_tsc = rte_rdtsc();
+ unsigned int i, a;
+ int32_t ret;
+
+ for (i = 0; i < KEYS_TO_ADD; i++) {
+ ret = rte_efd_update(params->efd_table, test_socket_id, keys[i],
+ data[i]);
+ if (ret != 0) {
+ printf("Error %d in rte_efd_update - key=0x", ret);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x", keys[i][a]);
+ printf(" value=%d\n", data[i]);
+
+ return -1;
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][ADD] = time_taken / KEYS_TO_ADD;
+ return 0;
+}
+
+static int
+timed_lookups(struct efd_perf_params *params)
+{
+ unsigned int i, j, a;
+ const uint64_t start_tsc = rte_rdtsc();
+ efd_value_t ret_data;
+
+ for (i = 0; i < NUM_LOOKUPS / KEYS_TO_ADD; i++) {
+ for (j = 0; j < KEYS_TO_ADD; j++) {
+ ret_data = rte_efd_lookup(params->efd_table,
+ test_socket_id, keys[j]);
+ if (ret_data != data[j]) {
+ printf("Value mismatch using rte_efd_lookup: "
+ "key #%d (0x", i);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x", keys[i][a]);
+ printf(")\n");
+ printf(" Expected %d, got %d\n", data[i],
+ ret_data);
+
+ return -1;
+ }
+
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][LOOKUP] = time_taken / NUM_LOOKUPS;
+
+ return 0;
+}
+
+static int
+timed_lookups_multi(struct efd_perf_params *params)
+{
+ unsigned int i, j, k, a;
+ efd_value_t result[RTE_EFD_BURST_MAX] = {0};
+ const void *keys_burst[RTE_EFD_BURST_MAX];
+ const uint64_t start_tsc = rte_rdtsc();
+
+ for (i = 0; i < NUM_LOOKUPS / KEYS_TO_ADD; i++) {
+ for (j = 0; j < KEYS_TO_ADD / RTE_EFD_BURST_MAX; j++) {
+ for (k = 0; k < RTE_EFD_BURST_MAX; k++)
+ keys_burst[k] = keys[j * RTE_EFD_BURST_MAX + k];
+
+ rte_efd_lookup_bulk(params->efd_table, test_socket_id,
+ RTE_EFD_BURST_MAX,
+ keys_burst, result);
+
+ for (k = 0; k < RTE_EFD_BURST_MAX; k++) {
+ uint32_t data_idx = j * RTE_EFD_BURST_MAX + k;
+ if (result[k] != data[data_idx]) {
+ printf("Value mismatch using "
+ "rte_efd_lookup_bulk: key #%d "
+ "(0x", i);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x",
+ keys[data_idx][a]);
+ printf(")\n");
+ printf(" Expected %d, got %d\n",
+ data[data_idx], result[k]);
+
+ return -1;
+ }
+ }
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][LOOKUP_MULTI] = time_taken / NUM_LOOKUPS;
+
+ return 0;
+}
+
+static int
+timed_deletes(struct efd_perf_params *params)
+{
+ unsigned int i, a;
+ const uint64_t start_tsc = rte_rdtsc();
+ int32_t ret;
+
+ for (i = 0; i < KEYS_TO_ADD; i++) {
+ ret = rte_efd_delete(params->efd_table, test_socket_id, keys[i],
+ NULL);
+
+ if (ret != 0) {
+ printf("Error %d in rte_efd_delete - key=0x", ret);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x", keys[i][a]);
+ printf("\n");
+
+ return -1;
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][DELETE] = time_taken / KEYS_TO_ADD;
+
+ return 0;
+}
+
+static void
+perform_frees(struct efd_perf_params *params)
+{
+ if (params->efd_table != NULL) {
+ rte_efd_free(params->efd_table);
+ params->efd_table = NULL;
+ }
+}
+
+static int
+exit_with_fail(const char *testname, struct efd_perf_params *params,
+ unsigned int i)
+{
+
+ printf("<<<<<Test %s failed at keysize %d iteration %d >>>>>\n",
+ testname, hashtest_key_lens[params->cycle], i);
+ perform_frees(params);
+ return -1;
+}
+
+static int
+run_all_tbl_perf_tests(void)
+{
+ unsigned int i, j;
+ struct efd_perf_params params;
+
+ printf("Measuring performance, please wait\n");
+ fflush(stdout);
+
+ test_socket_id = rte_socket_id();
+
+ for (i = 0; i < NUM_KEYSIZES; i++) {
+
+ if (setup_keys_and_data(¶ms, i) < 0) {
+ printf("Could not create keys/data/table\n");
+ return -1;
+ }
+
+ if (timed_adds(¶ms) < 0)
+ return exit_with_fail("timed_adds", ¶ms, i);
+
+ for (j = 0; j < NUM_SHUFFLES; j++)
+ shuffle_input_keys(¶ms);
+
+ if (timed_lookups(¶ms) < 0)
+ return exit_with_fail("timed_lookups", ¶ms, i);
+
+ if (timed_lookups_multi(¶ms) < 0)
+ return exit_with_fail("timed_lookups_multi", ¶ms, i);
+
+ if (timed_deletes(¶ms) < 0)
+ return exit_with_fail("timed_deletes", ¶ms, i);
+
+ /* Print a dot to show progress on operations */
+ printf(".");
+ fflush(stdout);
+
+ perform_frees(¶ms);
+ }
+
+ printf("\nResults (in CPU cycles/operation)\n");
+ printf("-----------------------------------\n");
+ printf("\n%-18s%-18s%-18s%-18s%-18s\n",
+ "Keysize", "Add", "Lookup", "Lookup_bulk", "Delete");
+ for (i = 0; i < NUM_KEYSIZES; i++) {
+ printf("%-18d", hashtest_key_lens[i]);
+ for (j = 0; j < NUM_OPERATIONS; j++)
+ printf("%-18"PRIu64, cycles[i][j]);
+ printf("\n");
+ }
+ return 0;
+}
+
+static int
+test_efd_perf(void)
+{
+
+ if (run_all_tbl_perf_tests() < 0)
+ return -1;
+
+ return 0;
+}
+
+REGISTER_TEST_COMMAND(efd_perf_autotest, test_efd_perf);
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v6 3/5] examples/flow_distributor: sample app to demonstrate EFD usage
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 " Pablo de Lara
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 1/5] efd: new Elastic Flow Distributor library Pablo de Lara
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 2/5] app/test: add EFD functional and perf tests Pablo de Lara
@ 2017-01-16 19:21 ` Pablo de Lara
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 4/5] doc: add EFD library section in Programmers guide Pablo de Lara
` (3 subsequent siblings)
6 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-16 19:21 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Saikrishna Edupuganti
This new sample app, based on the client/server sample app,
shows the user an scenario using the EFD library.
It consists of:
- A front-end server which has an EFD table that stores the
node id for each flow key, which will distribute the incoming
packets to the different nodes
- A back-end node, which has a hash table where node checks,
after reading packets coming from the server, whether the packet
is meant to be used in such node, in which case it will be TXed,
or not, in which case, packet will be dropped.
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 1 +
doc/api/examples.dox | 4 +
examples/Makefile | 1 +
examples/flow_distributor/Makefile | 44 +++
examples/flow_distributor/distributor/Makefile | 57 ++++
examples/flow_distributor/distributor/args.c | 200 ++++++++++++
examples/flow_distributor/distributor/args.h | 39 +++
examples/flow_distributor/distributor/init.c | 371 ++++++++++++++++++++++
examples/flow_distributor/distributor/init.h | 76 +++++
examples/flow_distributor/distributor/main.c | 362 +++++++++++++++++++++
examples/flow_distributor/node/Makefile | 48 +++
examples/flow_distributor/node/node.c | 417 +++++++++++++++++++++++++
examples/flow_distributor/shared/common.h | 99 ++++++
13 files changed, 1719 insertions(+)
create mode 100644 examples/flow_distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/args.c
create mode 100644 examples/flow_distributor/distributor/args.h
create mode 100644 examples/flow_distributor/distributor/init.c
create mode 100644 examples/flow_distributor/distributor/init.h
create mode 100644 examples/flow_distributor/distributor/main.c
create mode 100644 examples/flow_distributor/node/Makefile
create mode 100644 examples/flow_distributor/node/node.c
create mode 100644 examples/flow_distributor/shared/common.h
diff --git a/MAINTAINERS b/MAINTAINERS
index d812962..b124f6e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -533,6 +533,7 @@ M: Byron Marohn <byron.marohn@intel.com>
M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
F: lib/librte_efd/
F: app/test/test_efd*
+F: examples/flow_distributor/
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
diff --git a/doc/api/examples.dox b/doc/api/examples.dox
index 1626852..c13e574 100644
--- a/doc/api/examples.dox
+++ b/doc/api/examples.dox
@@ -52,6 +52,10 @@
@example load_balancer/init.c
@example load_balancer/main.c
@example load_balancer/runtime.c
+@example flow_distributor/distributor/args.c
+@example flow_distributor/distributor/init.c
+@example flow_distributor/distributor/main.c
+@example flow_distributor/node/node.c
@example multi_process/client_server_mp/mp_client/client.c
@example multi_process/client_server_mp/mp_server/args.c
@example multi_process/client_server_mp/mp_server/init.c
diff --git a/examples/Makefile b/examples/Makefile
index d49c7f2..b404982 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -45,6 +45,7 @@ DIRS-y += dpdk_qat
endif
DIRS-y += ethtool
DIRS-y += exception_path
+DIRS-$(CONFIG_RTE_LIBRTE_EFD) += flow_distributor
DIRS-y += helloworld
DIRS-$(CONFIG_RTE_LIBRTE_PIPELINE) += ip_pipeline
ifeq ($(CONFIG_RTE_LIBRTE_LPM),y)
diff --git a/examples/flow_distributor/Makefile b/examples/flow_distributor/Makefile
new file mode 100644
index 0000000..5bae706
--- /dev/null
+++ b/examples/flow_distributor/Makefile
@@ -0,0 +1,44 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overridden by command line or environment
+RTE_TARGET ?= x86_64-native-linuxapp-gcc
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+DIRS-$(CONFIG_RTE_EXEC_ENV_LINUXAPP) += distributor
+DIRS-$(CONFIG_RTE_EXEC_ENV_LINUXAPP) += node
+
+include $(RTE_SDK)/mk/rte.extsubdir.mk
diff --git a/examples/flow_distributor/distributor/Makefile b/examples/flow_distributor/distributor/Makefile
new file mode 100644
index 0000000..8714151
--- /dev/null
+++ b/examples/flow_distributor/distributor/Makefile
@@ -0,0 +1,57 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overridden by command line or environment
+RTE_TARGET ?= x86_64-native-linuxapp-gcc
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+ifneq ($(CONFIG_RTE_EXEC_ENV), "linuxapp")
+$(error This application can only operate in a linuxapp environment, \
+please change the definition of the RTE_TARGET environment variable)
+endif
+
+# binary name
+APP = distributor
+
+# all source are stored in SRCS-y
+SRCS-y := main.c init.c args.c
+
+INC := $(wildcard *.h)
+
+CFLAGS += $(WERROR_FLAGS) -O3
+CFLAGS += -I$(SRCDIR)/../shared
+
+include $(RTE_SDK)/mk/rte.extapp.mk
diff --git a/examples/flow_distributor/distributor/args.c b/examples/flow_distributor/distributor/args.c
new file mode 100644
index 0000000..ee29203
--- /dev/null
+++ b/examples/flow_distributor/distributor/args.c
@@ -0,0 +1,200 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <getopt.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include <rte_memory.h>
+#include <rte_string_fns.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+/* 1M flows by default */
+#define DEFAULT_NUM_FLOWS 0x100000
+
+/* global var for number of nodes - extern in header */
+uint8_t num_nodes;
+/* global var for number of flows - extern in header */
+uint32_t num_flows = DEFAULT_NUM_FLOWS;
+
+static const char *progname;
+
+/**
+ * Prints out usage information to stdout
+ */
+static void
+usage(void)
+{
+ printf("%s [EAL options] -- -p PORTMASK -n NUM_NODES -f NUM_FLOWS\n"
+ " -p PORTMASK: hexadecimal bitmask of ports to use\n"
+ " -n NUM_NODES: number of node processes to use\n"
+ " -f NUM_FLOWS: number of flows to be added in the EFD table\n",
+ progname);
+}
+
+/**
+ * The ports to be used by the application are passed in
+ * the form of a bitmask. This function parses the bitmask
+ * and places the port numbers to be used into the port[]
+ * array variable
+ */
+static int
+parse_portmask(uint8_t max_ports, const char *portmask)
+{
+ char *end = NULL;
+ unsigned long pm;
+ uint8_t count = 0;
+
+ if (portmask == NULL || *portmask == '\0')
+ return -1;
+
+ /* convert parameter to a number and verify */
+ pm = strtoul(portmask, &end, 16);
+ if (end == NULL || *end != '\0' || pm == 0)
+ return -1;
+
+ /* loop through bits of the mask and mark ports */
+ while (pm != 0) {
+ if (pm & 0x01) { /* bit is set in mask, use port */
+ if (count >= max_ports)
+ printf("WARNING: requested port %u not present"
+ " - ignoring\n", (unsigned int)count);
+ else
+ info->id[info->num_ports++] = count;
+ }
+ pm = (pm >> 1);
+ count++;
+ }
+
+ return 0;
+}
+
+/**
+ * Take the number of nodes parameter passed to the app
+ * and convert to a number to store in the num_nodes variable
+ */
+static int
+parse_num_nodes(const char *nodes)
+{
+ char *end = NULL;
+ unsigned long temp;
+
+ if (nodes == NULL || *nodes == '\0')
+ return -1;
+
+ temp = strtoul(nodes, &end, 10);
+ if (end == NULL || *end != '\0' || temp == 0)
+ return -1;
+
+ num_nodes = (uint8_t)temp;
+ return 0;
+}
+
+static int
+parse_num_flows(const char *flows)
+{
+ char *end = NULL;
+
+ /* parse hexadecimal string */
+ num_flows = strtoul(flows, &end, 16);
+ if ((flows[0] == '\0') || (end == NULL) || (*end != '\0'))
+ return -1;
+
+ if (num_flows == 0)
+ return -1;
+
+ return 0;
+}
+
+/**
+ * The application specific arguments follow the DPDK-specific
+ * arguments which are stripped by the DPDK init. This function
+ * processes these application arguments, printing usage info
+ * on error.
+ */
+int
+parse_app_args(uint8_t max_ports, int argc, char *argv[])
+{
+ int option_index, opt;
+ char **argvopt = argv;
+ static struct option lgopts[] = { /* no long options */
+ {NULL, 0, 0, 0 }
+ };
+ progname = argv[0];
+
+ while ((opt = getopt_long(argc, argvopt, "n:f:p:", lgopts,
+ &option_index)) != EOF) {
+ switch (opt) {
+ case 'p':
+ if (parse_portmask(max_ports, optarg) != 0) {
+ usage();
+ return -1;
+ }
+ break;
+ case 'n':
+ if (parse_num_nodes(optarg) != 0) {
+ usage();
+ return -1;
+ }
+ break;
+ case 'f':
+ if (parse_num_flows(optarg) != 0) {
+ usage();
+ return -1;
+ }
+ break;
+ default:
+ printf("ERROR: Unknown option '%c'\n", opt);
+ usage();
+ return -1;
+ }
+ }
+
+ if (info->num_ports == 0 || num_nodes == 0) {
+ usage();
+ return -1;
+ }
+
+ if (info->num_ports % 2 != 0) {
+ printf("ERROR: application requires an even "
+ "number of ports to use\n");
+ return -1;
+ }
+ return 0;
+}
diff --git a/examples/flow_distributor/distributor/args.h b/examples/flow_distributor/distributor/args.h
new file mode 100644
index 0000000..cacf395
--- /dev/null
+++ b/examples/flow_distributor/distributor/args.h
@@ -0,0 +1,39 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _ARGS_H_
+#define _ARGS_H_
+
+int parse_app_args(uint8_t max_ports, int argc, char *argv[]);
+
+#endif /* ifndef _ARGS_H_ */
diff --git a/examples/flow_distributor/distributor/init.c b/examples/flow_distributor/distributor/init.c
new file mode 100644
index 0000000..3b0aa85
--- /dev/null
+++ b/examples/flow_distributor/distributor/init.c
@@ -0,0 +1,371 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/queue.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <inttypes.h>
+
+#include <rte_common.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_byteorder.h>
+#include <rte_atomic.h>
+#include <rte_launch.h>
+#include <rte_per_lcore.h>
+#include <rte_lcore.h>
+#include <rte_branch_prediction.h>
+#include <rte_debug.h>
+#include <rte_ring.h>
+#include <rte_log.h>
+#include <rte_mempool.h>
+#include <rte_memcpy.h>
+#include <rte_mbuf.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ether.h>
+#include <rte_ethdev.h>
+#include <rte_malloc.h>
+#include <rte_string_fns.h>
+#include <rte_cycles.h>
+#include <rte_efd.h>
+#include <rte_hash.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+#define MBUFS_PER_NODE 1536
+#define MBUFS_PER_PORT 1536
+#define MBUF_CACHE_SIZE 512
+
+#define RTE_MP_RX_DESC_DEFAULT 512
+#define RTE_MP_TX_DESC_DEFAULT 512
+#define NODE_QUEUE_RINGSIZE 128
+
+#define NO_FLAGS 0
+
+/* The mbuf pool for packet rx */
+struct rte_mempool *pktmbuf_pool;
+
+/* array of info/queues for nodes */
+struct node *nodes;
+
+/* Flow distributor table */
+struct rte_efd_table *efd_table;
+
+/* Shared info between distributor and nodes */
+struct shared_info *info;
+
+/**
+ * Initialise the mbuf pool for packet reception for the NIC, and any other
+ * buffer pools needed by the app - currently none.
+ */
+static int
+init_mbuf_pools(void)
+{
+ const unsigned int num_mbufs = (num_nodes * MBUFS_PER_NODE) +
+ (info->num_ports * MBUFS_PER_PORT);
+
+ /*
+ * Don't pass single-producer/single-consumer flags to mbuf create as it
+ * seems faster to use a cache instead
+ */
+ printf("Creating mbuf pool '%s' [%u mbufs] ...\n",
+ PKTMBUF_POOL_NAME, num_mbufs);
+ pktmbuf_pool = rte_pktmbuf_pool_create(PKTMBUF_POOL_NAME, num_mbufs,
+ MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
+
+ return pktmbuf_pool == NULL; /* 0 on success */
+}
+
+/**
+ * Initialise an individual port:
+ * - configure number of rx and tx rings
+ * - set up each rx ring, to pull from the main mbuf pool
+ * - set up each tx ring
+ * - start the port and report its status to stdout
+ */
+static int
+init_port(uint8_t port_num)
+{
+ /* for port configuration all features are off by default */
+ const struct rte_eth_conf port_conf = {
+ .rxmode = {
+ .mq_mode = ETH_MQ_RX_RSS
+ }
+ };
+ const uint16_t rx_rings = 1, tx_rings = num_nodes;
+ const uint16_t rx_ring_size = RTE_MP_RX_DESC_DEFAULT;
+ const uint16_t tx_ring_size = RTE_MP_TX_DESC_DEFAULT;
+
+ uint16_t q;
+ int retval;
+
+ printf("Port %u init ... ", (unsigned int)port_num);
+ fflush(stdout);
+
+ /*
+ * Standard DPDK port initialisation - config port, then set up
+ * rx and tx rings.
+ */
+ retval = rte_eth_dev_configure(port_num, rx_rings, tx_rings, &port_conf);
+ if (retval != 0)
+ return retval;
+
+ for (q = 0; q < rx_rings; q++) {
+ retval = rte_eth_rx_queue_setup(port_num, q, rx_ring_size,
+ rte_eth_dev_socket_id(port_num),
+ NULL, pktmbuf_pool);
+ if (retval < 0)
+ return retval;
+ }
+
+ for (q = 0; q < tx_rings; q++) {
+ retval = rte_eth_tx_queue_setup(port_num, q, tx_ring_size,
+ rte_eth_dev_socket_id(port_num),
+ NULL);
+ if (retval < 0)
+ return retval;
+ }
+
+ rte_eth_promiscuous_enable(port_num);
+
+ retval = rte_eth_dev_start(port_num);
+ if (retval < 0)
+ return retval;
+
+ printf("done:\n");
+
+ return 0;
+}
+
+/**
+ * Set up the DPDK rings which will be used to pass packets, via
+ * pointers, between the multi-process distributor and node processes.
+ * Each node needs one RX queue.
+ */
+static int
+init_shm_rings(void)
+{
+ unsigned int i;
+ unsigned int socket_id;
+ const char *q_name;
+ const unsigned int ringsize = NODE_QUEUE_RINGSIZE;
+
+ nodes = rte_malloc("node details",
+ sizeof(*nodes) * num_nodes, 0);
+ if (nodes == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot allocate memory for "
+ "node program details\n");
+
+ for (i = 0; i < num_nodes; i++) {
+ /* Create an RX queue for each node */
+ socket_id = rte_socket_id();
+ q_name = get_rx_queue_name(i);
+ nodes[i].rx_q = rte_ring_create(q_name,
+ ringsize, socket_id,
+ RING_F_SP_ENQ | RING_F_SC_DEQ);
+ if (nodes[i].rx_q == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot create rx ring queue "
+ "for node %u\n", i);
+ }
+ return 0;
+}
+
+/*
+ * Create flow distributor table which will contain all the flows
+ * that will be distributed among the nodes
+ */
+static void
+create_flow_distributor_table(void)
+{
+ uint8_t socket_id = rte_socket_id();
+
+ /* create table */
+ efd_table = rte_efd_create("flow table", num_flows * 2, sizeof(uint32_t),
+ 1 << socket_id, socket_id);
+
+ if (efd_table == NULL)
+ rte_exit(EXIT_FAILURE, "Problem creating the flow table\n");
+}
+
+static void
+populate_flow_distributor_table(void)
+{
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint8_t socket_id = rte_socket_id();
+ uint64_t node_id;
+
+ /* Add flows in table */
+ for (i = 0; i < num_flows; i++) {
+ node_id = i % num_nodes;
+
+ ip_dst = rte_cpu_to_be_32(i);
+ ret = rte_efd_update(efd_table, socket_id,
+ (void *)&ip_dst, (efd_value_t)node_id);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u in "
+ "flow distributor table\n", i);
+ }
+
+ printf("EFD table: Adding 0x%x keys\n", num_flows);
+}
+
+/* Check the link status of all ports in up to 9s, and print them finally */
+static void
+check_all_ports_link_status(uint8_t port_num, uint32_t port_mask)
+{
+#define CHECK_INTERVAL 100 /* 100ms */
+#define MAX_CHECK_TIME 90 /* 9s (90 * 100ms) in total */
+ uint8_t portid, count, all_ports_up, print_flag = 0;
+ struct rte_eth_link link;
+
+ printf("\nChecking link status");
+ fflush(stdout);
+ for (count = 0; count <= MAX_CHECK_TIME; count++) {
+ all_ports_up = 1;
+ for (portid = 0; portid < port_num; portid++) {
+ if ((port_mask & (1 << info->id[portid])) == 0)
+ continue;
+ memset(&link, 0, sizeof(link));
+ rte_eth_link_get_nowait(info->id[portid], &link);
+ /* print link status if flag set */
+ if (print_flag == 1) {
+ if (link.link_status)
+ printf("Port %d Link Up - speed %u "
+ "Mbps - %s\n", info->id[portid],
+ (unsigned int)link.link_speed,
+ (link.link_duplex == ETH_LINK_FULL_DUPLEX) ?
+ ("full-duplex") : ("half-duplex\n"));
+ else
+ printf("Port %d Link Down\n",
+ (uint8_t)info->id[portid]);
+ continue;
+ }
+ /* clear all_ports_up flag if any link down */
+ if (link.link_status == ETH_LINK_DOWN) {
+ all_ports_up = 0;
+ break;
+ }
+ }
+ /* after finally printing all link status, get out */
+ if (print_flag == 1)
+ break;
+
+ if (all_ports_up == 0) {
+ printf(".");
+ fflush(stdout);
+ rte_delay_ms(CHECK_INTERVAL);
+ }
+
+ /* set the print_flag if all ports up or timeout */
+ if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
+ print_flag = 1;
+ printf("done\n");
+ }
+ }
+}
+
+/**
+ * Main init function for the multi-process distributor app,
+ * calls subfunctions to do each stage of the initialisation.
+ */
+int
+init(int argc, char *argv[])
+{
+ int retval;
+ const struct rte_memzone *mz;
+ uint8_t i, total_ports;
+
+ /* init EAL, parsing EAL args */
+ retval = rte_eal_init(argc, argv);
+ if (retval < 0)
+ return -1;
+ argc -= retval;
+ argv += retval;
+
+ /* get total number of ports */
+ total_ports = rte_eth_dev_count();
+
+ /* set up array for port data */
+ mz = rte_memzone_reserve(MZ_SHARED_INFO, sizeof(*info),
+ rte_socket_id(), NO_FLAGS);
+ if (mz == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot reserve memory zone "
+ "for port information\n");
+ memset(mz->addr, 0, sizeof(*info));
+ info = mz->addr;
+
+ /* parse additional, application arguments */
+ retval = parse_app_args(total_ports, argc, argv);
+ if (retval != 0)
+ return -1;
+
+ /* initialise mbuf pools */
+ retval = init_mbuf_pools();
+ if (retval != 0)
+ rte_exit(EXIT_FAILURE, "Cannot create needed mbuf pools\n");
+
+ /* now initialise the ports we will use */
+ for (i = 0; i < info->num_ports; i++) {
+ retval = init_port(info->id[i]);
+ if (retval != 0)
+ rte_exit(EXIT_FAILURE, "Cannot initialise port %u\n",
+ (unsigned int) i);
+ }
+
+ check_all_ports_link_status(info->num_ports, (~0x0));
+
+ /* initialise the node queues/rings for inter-eu comms */
+ init_shm_rings();
+
+ /* Create the flow distributor table */
+ create_flow_distributor_table();
+
+ /* Populate the flow distributor table */
+ populate_flow_distributor_table();
+
+ /* Share the total number of nodes */
+ info->num_nodes = num_nodes;
+
+ /* Share the total number of flows */
+ info->num_flows = num_flows;
+ return 0;
+}
diff --git a/examples/flow_distributor/distributor/init.h b/examples/flow_distributor/distributor/init.h
new file mode 100644
index 0000000..d11aacf
--- /dev/null
+++ b/examples/flow_distributor/distributor/init.h
@@ -0,0 +1,76 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _INIT_H_
+#define _INIT_H_
+
+/*
+ * #include <rte_ring.h>
+ * #include "args.h"
+ */
+
+/*
+ * Define a node structure with all needed info, including
+ * stats from the nodes.
+ */
+struct node {
+ struct rte_ring *rx_q;
+ unsigned int node_id;
+ /* these stats hold how many packets the node will actually receive,
+ * and how many packets were dropped because the node's queue was full.
+ * The port-info stats, in contrast, record how many packets were received
+ * or transmitted on an actual NIC port.
+ */
+ struct {
+ uint64_t rx;
+ uint64_t rx_drop;
+ } stats;
+};
+
+extern struct rte_efd_table *efd_table;
+extern struct node *nodes;
+
+/*
+ * shared information between distributor and nodes: number of clients,
+ * port numbers, rx and tx stats etc.
+ */
+extern struct shared_info *info;
+
+extern struct rte_mempool *pktmbuf_pool;
+extern uint8_t num_nodes;
+extern unsigned int num_sockets;
+extern uint32_t num_flows;
+
+int init(int argc, char *argv[]);
+
+#endif /* ifndef _INIT_H_ */
diff --git a/examples/flow_distributor/distributor/main.c b/examples/flow_distributor/distributor/main.c
new file mode 100644
index 0000000..f97f003
--- /dev/null
+++ b/examples/flow_distributor/distributor/main.c
@@ -0,0 +1,362 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <inttypes.h>
+#include <sys/queue.h>
+#include <errno.h>
+#include <netinet/ip.h>
+
+#include <rte_common.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_byteorder.h>
+#include <rte_launch.h>
+#include <rte_per_lcore.h>
+#include <rte_lcore.h>
+#include <rte_branch_prediction.h>
+#include <rte_atomic.h>
+#include <rte_ring.h>
+#include <rte_log.h>
+#include <rte_debug.h>
+#include <rte_mempool.h>
+#include <rte_memcpy.h>
+#include <rte_mbuf.h>
+#include <rte_ether.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ethdev.h>
+#include <rte_byteorder.h>
+#include <rte_malloc.h>
+#include <rte_string_fns.h>
+#include <rte_efd.h>
+#include <rte_ip.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+/*
+ * When doing reads from the NIC or the node queues,
+ * use this batch size
+ */
+#define PACKET_READ_SIZE 32
+
+/*
+ * Local buffers to put packets in, used to send packets in bursts to the
+ * nodes
+ */
+struct node_rx_buf {
+ struct rte_mbuf *buffer[PACKET_READ_SIZE];
+ uint16_t count;
+};
+
+struct flow_distributor_stats {
+ uint64_t distributed;
+ uint64_t drop;
+} flow_dist_stats;
+
+/* One buffer per node rx queue - dynamically allocate array */
+static struct node_rx_buf *cl_rx_buf;
+
+static const char *
+get_printable_mac_addr(uint8_t port)
+{
+ static const char err_address[] = "00:00:00:00:00:00";
+ static char addresses[RTE_MAX_ETHPORTS][sizeof(err_address)];
+ struct ether_addr mac;
+
+ if (unlikely(port >= RTE_MAX_ETHPORTS))
+ return err_address;
+ if (unlikely(addresses[port][0] == '\0')) {
+ rte_eth_macaddr_get(port, &mac);
+ snprintf(addresses[port], sizeof(addresses[port]),
+ "%02x:%02x:%02x:%02x:%02x:%02x\n",
+ mac.addr_bytes[0], mac.addr_bytes[1],
+ mac.addr_bytes[2], mac.addr_bytes[3],
+ mac.addr_bytes[4], mac.addr_bytes[5]);
+ }
+ return addresses[port];
+}
+
+/*
+ * This function displays the recorded statistics for each port
+ * and for each node. It uses ANSI terminal codes to clear
+ * screen when called. It is called from a single non-master
+ * thread in the distributor process, when the process is run with more
+ * than one lcore enabled.
+ */
+static void
+do_stats_display(void)
+{
+ unsigned int i, j;
+ const char clr[] = {27, '[', '2', 'J', '\0'};
+ const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
+ uint64_t port_tx[RTE_MAX_ETHPORTS], port_tx_drop[RTE_MAX_ETHPORTS];
+ uint64_t node_tx[MAX_NODES], node_tx_drop[MAX_NODES];
+
+ /* to get TX stats, we need to do some summing calculations */
+ memset(port_tx, 0, sizeof(port_tx));
+ memset(port_tx_drop, 0, sizeof(port_tx_drop));
+ memset(node_tx, 0, sizeof(node_tx));
+ memset(node_tx_drop, 0, sizeof(node_tx_drop));
+
+ for (i = 0; i < num_nodes; i++) {
+ const struct tx_stats *tx = &info->tx_stats[i];
+
+ for (j = 0; j < info->num_ports; j++) {
+ const uint64_t tx_val = tx->tx[info->id[j]];
+ const uint64_t drop_val = tx->tx_drop[info->id[j]];
+
+ port_tx[j] += tx_val;
+ port_tx_drop[j] += drop_val;
+ node_tx[i] += tx_val;
+ node_tx_drop[i] += drop_val;
+ }
+ }
+
+ /* Clear screen and move to top left */
+ printf("%s%s", clr, topLeft);
+
+ printf("PORTS\n");
+ printf("-----\n");
+ for (i = 0; i < info->num_ports; i++)
+ printf("Port %u: '%s'\t", (unsigned int)info->id[i],
+ get_printable_mac_addr(info->id[i]));
+ printf("\n\n");
+ for (i = 0; i < info->num_ports; i++) {
+ printf("Port %u - rx: %9"PRIu64"\t"
+ "tx: %9"PRIu64"\n",
+ (unsigned int)info->id[i], info->rx_stats.rx[i],
+ port_tx[i]);
+ }
+
+ printf("\nFLOW DISTRIBUTOR\n");
+ printf("-----\n");
+ printf("distributed: %9"PRIu64", drop: %9"PRIu64"\n",
+ flow_dist_stats.distributed, flow_dist_stats.drop);
+
+ printf("\nNODES\n");
+ printf("-------\n");
+ for (i = 0; i < num_nodes; i++) {
+ const unsigned long long rx = nodes[i].stats.rx;
+ const unsigned long long rx_drop = nodes[i].stats.rx_drop;
+ const struct filter_stats *filter = &info->filter_stats[i];
+
+ printf("Node %2u - rx: %9llu, rx_drop: %9llu\n"
+ " tx: %9"PRIu64", tx_drop: %9"PRIu64"\n"
+ " filter_passed: %9"PRIu64", "
+ "filter_drop: %9"PRIu64"\n",
+ i, rx, rx_drop, node_tx[i], node_tx_drop[i],
+ filter->passed, filter->drop);
+ }
+
+ printf("\n");
+}
+
+/*
+ * The function called from each non-master lcore used by the process.
+ * The test_and_set function is used to randomly pick a single lcore on which
+ * the code to display the statistics will run. Otherwise, the code just
+ * repeatedly sleeps.
+ */
+static int
+sleep_lcore(__attribute__((unused)) void *dummy)
+{
+ /* Used to pick a display thread - static, so zero-initialised */
+ static rte_atomic32_t display_stats;
+
+ /* Only one core should display stats */
+ if (rte_atomic32_test_and_set(&display_stats)) {
+ const unsigned int sleeptime = 1;
+
+ printf("Core %u displaying statistics\n", rte_lcore_id());
+
+ /* Longer initial pause so above printf is seen */
+ sleep(sleeptime * 3);
+
+ /* Loop forever: sleep always returns 0 or <= param */
+ while (sleep(sleeptime) <= sleeptime)
+ do_stats_display();
+ }
+ return 0;
+}
+
+/*
+ * Function to set all the node statistic values to zero.
+ * Called at program startup.
+ */
+static void
+clear_stats(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_nodes; i++)
+ nodes[i].stats.rx = nodes[i].stats.rx_drop = 0;
+}
+
+/*
+ * send a burst of traffic to a node, assuming there are packets
+ * available to be sent to this node
+ */
+static void
+flush_rx_queue(uint16_t node)
+{
+ uint16_t j;
+ struct node *cl;
+
+ if (cl_rx_buf[node].count == 0)
+ return;
+
+ cl = &nodes[node];
+ if (rte_ring_enqueue_bulk(cl->rx_q, (void **)cl_rx_buf[node].buffer,
+ cl_rx_buf[node].count) != 0){
+ for (j = 0; j < cl_rx_buf[node].count; j++)
+ rte_pktmbuf_free(cl_rx_buf[node].buffer[j]);
+ cl->stats.rx_drop += cl_rx_buf[node].count;
+ } else
+ cl->stats.rx += cl_rx_buf[node].count;
+
+ cl_rx_buf[node].count = 0;
+}
+
+/*
+ * marks a packet down to be sent to a particular node process
+ */
+static inline void
+enqueue_rx_packet(uint8_t node, struct rte_mbuf *buf)
+{
+ cl_rx_buf[node].buffer[cl_rx_buf[node].count++] = buf;
+}
+
+/*
+ * This function takes a group of packets and routes them
+ * individually to the node process. Very simply round-robins the packets
+ * without checking any of the packet contents.
+ */
+static void
+process_packets(uint32_t port_num __rte_unused, struct rte_mbuf *pkts[],
+ uint16_t rx_count, unsigned int socket_id)
+{
+ uint16_t i;
+ uint8_t node;
+ efd_value_t data[RTE_EFD_BURST_MAX];
+ const void *key_ptrs[RTE_EFD_BURST_MAX];
+
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[RTE_EFD_BURST_MAX];
+
+ for (i = 0; i < rx_count; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(pkts[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = (void *)&ipv4_dst_ip[i];
+ }
+
+ rte_efd_lookup_bulk(efd_table, socket_id, rx_count,
+ (const void **) key_ptrs, data);
+ for (i = 0; i < rx_count; i++) {
+ node = (uint8_t) ((uintptr_t)data[i]);
+
+ if (node >= num_nodes) {
+ /*
+ * Node is out of range, which means that
+ * flow has not been inserted
+ */
+ flow_dist_stats.drop++;
+ rte_pktmbuf_free(pkts[i]);
+ } else {
+ flow_dist_stats.distributed++;
+ enqueue_rx_packet(node, pkts[i]);
+ }
+ }
+
+ for (i = 0; i < num_nodes; i++)
+ flush_rx_queue(i);
+}
+
+/*
+ * Function called by the master lcore of the DPDK process.
+ */
+static void
+do_packet_forwarding(void)
+{
+ unsigned int port_num = 0; /* indexes the port[] array */
+ unsigned int socket_id = rte_socket_id();
+
+ for (;;) {
+ struct rte_mbuf *buf[PACKET_READ_SIZE];
+ uint16_t rx_count;
+
+ /* read a port */
+ rx_count = rte_eth_rx_burst(info->id[port_num], 0,
+ buf, PACKET_READ_SIZE);
+ info->rx_stats.rx[port_num] += rx_count;
+
+ /* Now process the NIC packets read */
+ if (likely(rx_count > 0))
+ process_packets(port_num, buf, rx_count, socket_id);
+
+ /* move to next port */
+ if (++port_num == info->num_ports)
+ port_num = 0;
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ /* initialise the system */
+ if (init(argc, argv) < 0)
+ return -1;
+ RTE_LOG(INFO, APP, "Finished Process Init.\n");
+
+ cl_rx_buf = calloc(num_nodes, sizeof(cl_rx_buf[0]));
+
+ /* clear statistics */
+ clear_stats();
+
+ /* put all other cores to sleep bar master */
+ rte_eal_mp_remote_launch(sleep_lcore, NULL, SKIP_MASTER);
+
+ do_packet_forwarding();
+ return 0;
+}
diff --git a/examples/flow_distributor/node/Makefile b/examples/flow_distributor/node/Makefile
new file mode 100644
index 0000000..8cf7b65
--- /dev/null
+++ b/examples/flow_distributor/node/Makefile
@@ -0,0 +1,48 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overridden by command line or environment
+include $(RTE_SDK)/mk/rte.vars.mk
+
+# binary name
+APP = node
+
+# all source are stored in SRCS-y
+SRCS-y := node.c
+
+CFLAGS += $(WERROR_FLAGS) -O3
+CFLAGS += -I$(SRCDIR)/../shared
+
+include $(RTE_SDK)/mk/rte.extapp.mk
diff --git a/examples/flow_distributor/node/node.c b/examples/flow_distributor/node/node.c
new file mode 100644
index 0000000..1f1e7e7
--- /dev/null
+++ b/examples/flow_distributor/node/node.c
@@ -0,0 +1,417 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <sys/queue.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_malloc.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_atomic.h>
+#include <rte_branch_prediction.h>
+#include <rte_log.h>
+#include <rte_per_lcore.h>
+#include <rte_launch.h>
+#include <rte_lcore.h>
+#include <rte_ring.h>
+#include <rte_launch.h>
+#include <rte_lcore.h>
+#include <rte_debug.h>
+#include <rte_mempool.h>
+#include <rte_mbuf.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ether.h>
+#include <rte_ethdev.h>
+#include <rte_string_fns.h>
+#include <rte_ip.h>
+
+#include "common.h"
+
+/* Number of packets to attempt to read from queue */
+#define PKT_READ_SIZE ((uint16_t)32)
+
+/*
+ * Our node id number - tells us which rx queue to read, and NIC TX
+ * queue to write to.
+ */
+static uint8_t node_id;
+
+#define MBQ_CAPACITY 32
+
+/* maps input ports to output ports for packets */
+static uint8_t output_ports[RTE_MAX_ETHPORTS];
+
+/* buffers up a set of packet that are ready to send */
+struct rte_eth_dev_tx_buffer *tx_buffer[RTE_MAX_ETHPORTS];
+
+/* shared data from distributor. We update statistics here */
+static struct tx_stats *tx_stats;
+
+static struct filter_stats *filter_stats;
+
+/*
+ * print a usage message
+ */
+static void
+usage(const char *progname)
+{
+ printf("Usage: %s [EAL args] -- -n <node_id>\n\n", progname);
+}
+
+/*
+ * Convert the node id number from a string to an int.
+ */
+static int
+parse_node_num(const char *node)
+{
+ char *end = NULL;
+ unsigned long temp;
+
+ if (node == NULL || *node == '\0')
+ return -1;
+
+ temp = strtoul(node, &end, 10);
+ if (end == NULL || *end != '\0')
+ return -1;
+
+ node_id = (uint8_t)temp;
+ return 0;
+}
+
+/*
+ * Parse the application arguments to the node app.
+ */
+static int
+parse_app_args(int argc, char *argv[])
+{
+ int option_index, opt;
+ char **argvopt = argv;
+ const char *progname = NULL;
+ static struct option lgopts[] = { /* no long options */
+ {NULL, 0, 0, 0 }
+ };
+ progname = argv[0];
+
+ while ((opt = getopt_long(argc, argvopt, "n:", lgopts,
+ &option_index)) != EOF) {
+ switch (opt) {
+ case 'n':
+ if (parse_node_num(optarg) != 0) {
+ usage(progname);
+ return -1;
+ }
+ break;
+ default:
+ usage(progname);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Tx buffer error callback
+ */
+static void
+flush_tx_error_callback(struct rte_mbuf **unsent, uint16_t count,
+ void *userdata) {
+ int i;
+ uint8_t port_id = (uintptr_t)userdata;
+
+ tx_stats->tx_drop[port_id] += count;
+
+ /* free the mbufs which failed from transmit */
+ for (i = 0; i < count; i++)
+ rte_pktmbuf_free(unsent[i]);
+
+}
+
+static void
+configure_tx_buffer(uint8_t port_id, uint16_t size)
+{
+ int ret;
+
+ /* Initialize TX buffers */
+ tx_buffer[port_id] = rte_zmalloc_socket("tx_buffer",
+ RTE_ETH_TX_BUFFER_SIZE(size), 0,
+ rte_eth_dev_socket_id(port_id));
+ if (tx_buffer[port_id] == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot allocate buffer for tx "
+ "on port %u\n", (unsigned int) port_id);
+
+ rte_eth_tx_buffer_init(tx_buffer[port_id], size);
+
+ ret = rte_eth_tx_buffer_set_err_callback(tx_buffer[port_id],
+ flush_tx_error_callback, (void *)(intptr_t)port_id);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Cannot set error callback for "
+ "tx buffer on port %u\n", (unsigned int) port_id);
+}
+
+/*
+ * set up output ports so that all traffic on port gets sent out
+ * its paired port. Index using actual port numbers since that is
+ * what comes in the mbuf structure.
+ */
+static void
+configure_output_ports(const struct shared_info *info)
+{
+ int i;
+
+ if (info->num_ports > RTE_MAX_ETHPORTS)
+ rte_exit(EXIT_FAILURE, "Too many ethernet ports. "
+ "RTE_MAX_ETHPORTS = %u\n",
+ (unsigned int)RTE_MAX_ETHPORTS);
+ for (i = 0; i < info->num_ports - 1; i += 2) {
+ uint8_t p1 = info->id[i];
+ uint8_t p2 = info->id[i+1];
+
+ output_ports[p1] = p2;
+ output_ports[p2] = p1;
+
+ configure_tx_buffer(p1, MBQ_CAPACITY);
+ configure_tx_buffer(p2, MBQ_CAPACITY);
+
+ }
+}
+
+/*
+ * Create the hash table that will contain the flows that
+ * the node will handle, which will be used to decide if packet
+ * is transmitted or dropped.
+ */
+static struct rte_hash *
+create_hash_table(const struct shared_info *info)
+{
+ uint32_t num_flows_node = info->num_flows / info->num_nodes;
+ char name[RTE_HASH_NAMESIZE];
+ struct rte_hash *h;
+
+ /* create table */
+ struct rte_hash_parameters hash_params = {
+ .entries = num_flows_node * 2, /* table load = 50% */
+ .key_len = sizeof(uint32_t), /* Store IPv4 dest IP address */
+ .socket_id = rte_socket_id(),
+ .hash_func_init_val = 0,
+ };
+
+ snprintf(name, sizeof(name), "hash_table_%d", node_id);
+ hash_params.name = name;
+ h = rte_hash_create(&hash_params);
+
+ if (h == NULL)
+ rte_exit(EXIT_FAILURE,
+ "Problem creating the hash table for node %d\n",
+ node_id);
+ return h;
+}
+
+static void
+populate_hash_table(const struct rte_hash *h, const struct shared_info *info)
+{
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint32_t num_flows_node = 0;
+ uint64_t target_node;
+
+ /* Add flows in table */
+ for (i = 0; i < info->num_flows; i++) {
+ target_node = i % info->num_nodes;
+ if (target_node != node_id)
+ continue;
+
+ ip_dst = rte_cpu_to_be_32(i);
+
+ ret = rte_hash_add_key(h, (void *) &ip_dst);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u "
+ "in hash table\n", i);
+ else
+ num_flows_node++;
+
+ }
+
+ printf("Hash table: Adding 0x%x keys\n", num_flows_node);
+}
+
+/*
+ * This function performs routing of packets
+ * Just sends each input packet out an output port based solely on the input
+ * port it arrived on.
+ */
+static inline void
+transmit_packet(struct rte_mbuf *buf)
+{
+ int sent;
+ const uint8_t in_port = buf->port;
+ const uint8_t out_port = output_ports[in_port];
+ struct rte_eth_dev_tx_buffer *buffer = tx_buffer[out_port];
+
+ sent = rte_eth_tx_buffer(out_port, node_id, buffer, buf);
+ if (sent)
+ tx_stats->tx[out_port] += sent;
+
+}
+
+static inline void
+handle_packets(struct rte_hash *h, struct rte_mbuf **bufs, uint16_t num_packets)
+{
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[PKT_READ_SIZE];
+ const void *key_ptrs[PKT_READ_SIZE];
+ unsigned int i;
+ int32_t positions[PKT_READ_SIZE] = {0};
+
+ for (i = 0; i < num_packets; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(bufs[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = &ipv4_dst_ip[i];
+ }
+ /* Check if packets belongs to any flows handled by this node */
+ rte_hash_lookup_bulk(h, key_ptrs, num_packets, positions);
+
+ for (i = 0; i < num_packets; i++) {
+ if (likely(positions[i] >= 0)) {
+ filter_stats->passed++;
+ transmit_packet(bufs[i]);
+ } else {
+ filter_stats->drop++;
+ /* Drop packet, as flow is not handled by this node */
+ rte_pktmbuf_free(bufs[i]);
+ }
+ }
+}
+
+/*
+ * Application main function - loops through
+ * receiving and processing packets. Never returns
+ */
+int
+main(int argc, char *argv[])
+{
+ const struct rte_memzone *mz;
+ struct rte_ring *rx_ring;
+ struct rte_hash *h;
+ struct rte_mempool *mp;
+ struct shared_info *info;
+ int need_flush = 0; /* indicates whether we have unsent packets */
+ int retval;
+ void *pkts[PKT_READ_SIZE];
+ uint16_t sent;
+
+ retval = rte_eal_init(argc, argv);
+ if (retval < 0)
+ return -1;
+ argc -= retval;
+ argv += retval;
+
+ if (parse_app_args(argc, argv) < 0)
+ rte_exit(EXIT_FAILURE, "Invalid command-line arguments\n");
+
+ if (rte_eth_dev_count() == 0)
+ rte_exit(EXIT_FAILURE, "No Ethernet ports - bye\n");
+
+ rx_ring = rte_ring_lookup(get_rx_queue_name(node_id));
+ if (rx_ring == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get RX ring - "
+ "is distributor process running?\n");
+
+ mp = rte_mempool_lookup(PKTMBUF_POOL_NAME);
+ if (mp == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get mempool for mbufs\n");
+
+ mz = rte_memzone_lookup(MZ_SHARED_INFO);
+ if (mz == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get port info structure\n");
+ info = mz->addr;
+ tx_stats = &(info->tx_stats[node_id]);
+ filter_stats = &(info->filter_stats[node_id]);
+
+ configure_output_ports(info);
+
+ h = create_hash_table(info);
+
+ populate_hash_table(h, info);
+
+ RTE_LOG(INFO, APP, "Finished Process Init.\n");
+
+ printf("\nNode process %d handling packets\n", node_id);
+ printf("[Press Ctrl-C to quit ...]\n");
+
+ for (;;) {
+ uint16_t rx_pkts = PKT_READ_SIZE;
+ uint8_t port;
+
+ /*
+ * Try dequeuing max possible packets first, if that fails,
+ * get the most we can. Loop body should only execute once,
+ * maximum
+ */
+ while (rx_pkts > 0 &&
+ unlikely(rte_ring_dequeue_bulk(rx_ring, pkts,
+ rx_pkts) != 0))
+ rx_pkts = (uint16_t)RTE_MIN(rte_ring_count(rx_ring),
+ PKT_READ_SIZE);
+
+ if (unlikely(rx_pkts == 0)) {
+ if (need_flush)
+ for (port = 0; port < info->num_ports; port++) {
+ sent = rte_eth_tx_buffer_flush(
+ info->id[port],
+ node_id,
+ tx_buffer[port]);
+ if (unlikely(sent))
+ tx_stats->tx[port] += sent;
+ }
+ need_flush = 0;
+ continue;
+ }
+
+ handle_packets(h, (struct rte_mbuf **)pkts, rx_pkts);
+
+ need_flush = 1;
+ }
+}
diff --git a/examples/flow_distributor/shared/common.h b/examples/flow_distributor/shared/common.h
new file mode 100644
index 0000000..5dcffd6
--- /dev/null
+++ b/examples/flow_distributor/shared/common.h
@@ -0,0 +1,99 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _COMMON_H_
+#define _COMMON_H_
+
+#include <rte_hash_crc.h>
+#include <rte_hash.h>
+
+#define MAX_NODES 16
+/*
+ * Shared port info, including statistics information for display by distributor.
+ * Structure will be put in a memzone.
+ * - All port id values share one cache line as this data will be read-only
+ * during operation.
+ * - All rx statistic values share cache lines, as this data is written only
+ * by the distributor process. (rare reads by stats display)
+ * - The tx statistics have values for all ports per cache line, but the stats
+ * themselves are written by the nodes, so we have a distinct set, on different
+ * cache lines for each node to use.
+ */
+struct rx_stats {
+ uint64_t rx[RTE_MAX_ETHPORTS];
+} __rte_cache_aligned;
+
+struct tx_stats {
+ uint64_t tx[RTE_MAX_ETHPORTS];
+ uint64_t tx_drop[RTE_MAX_ETHPORTS];
+} __rte_cache_aligned;
+
+struct filter_stats {
+ uint64_t drop;
+ uint64_t passed;
+} __rte_cache_aligned;
+
+struct shared_info {
+ uint8_t num_nodes;
+ uint8_t num_ports;
+ uint32_t num_flows;
+ uint8_t id[RTE_MAX_ETHPORTS];
+ struct rx_stats rx_stats;
+ struct tx_stats tx_stats[MAX_NODES];
+ struct filter_stats filter_stats[MAX_NODES];
+};
+
+/* define common names for structures shared between distributor and node */
+#define MP_NODE_RXQ_NAME "MProc_Node_%u_RX"
+#define PKTMBUF_POOL_NAME "MProc_pktmbuf_pool"
+#define MZ_SHARED_INFO "MProc_shared_info"
+
+/*
+ * Given the rx queue name template above, get the queue name
+ */
+static inline const char *
+get_rx_queue_name(unsigned int id)
+{
+ /*
+ * Buffer for return value. Size calculated by %u being replaced
+ * by maximum 3 digits (plus an extra byte for safety)
+ */
+ static char buffer[sizeof(MP_NODE_RXQ_NAME) + 2];
+
+ snprintf(buffer, sizeof(buffer) - 1, MP_NODE_RXQ_NAME, id);
+ return buffer;
+}
+
+#define RTE_LOGTYPE_APP RTE_LOGTYPE_USER1
+
+#endif
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v6 4/5] doc: add EFD library section in Programmers guide
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 " Pablo de Lara
` (2 preceding siblings ...)
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 3/5] examples/flow_distributor: sample app to demonstrate EFD usage Pablo de Lara
@ 2017-01-16 19:21 ` Pablo de Lara
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 5/5] doc: add flow distributor guide Pablo de Lara
` (2 subsequent siblings)
6 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-16 19:21 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Sameh Gobriel
Signed-off-by: Sameh Gobriel <sameh.gobriel@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 1 +
doc/guides/prog_guide/efd_lib.rst | 440 +++++++++++
doc/guides/prog_guide/img/efd_i1.svg | 130 ++++
doc/guides/prog_guide/img/efd_i10.svg | 384 ++++++++++
doc/guides/prog_guide/img/efd_i11.svg | 319 ++++++++
doc/guides/prog_guide/img/efd_i12.svg | 1008 +++++++++++++++++++++++++
doc/guides/prog_guide/img/efd_i2.svg | 280 +++++++
doc/guides/prog_guide/img/efd_i3.svg | 634 ++++++++++++++++
doc/guides/prog_guide/img/efd_i4.svg | 203 ++++++
doc/guides/prog_guide/img/efd_i5.svg | 183 +++++
doc/guides/prog_guide/img/efd_i6.svg | 1254 ++++++++++++++++++++++++++++++++
doc/guides/prog_guide/img/efd_i7.svg | 790 ++++++++++++++++++++
doc/guides/prog_guide/img/efd_i8.svg | 182 +++++
doc/guides/prog_guide/img/efd_i9.svg | 390 ++++++++++
doc/guides/prog_guide/index.rst | 23 +
doc/guides/rel_notes/release_17_02.rst | 3 +
16 files changed, 6224 insertions(+)
create mode 100644 doc/guides/prog_guide/efd_lib.rst
create mode 100644 doc/guides/prog_guide/img/efd_i1.svg
create mode 100644 doc/guides/prog_guide/img/efd_i10.svg
create mode 100644 doc/guides/prog_guide/img/efd_i11.svg
create mode 100644 doc/guides/prog_guide/img/efd_i12.svg
create mode 100644 doc/guides/prog_guide/img/efd_i2.svg
create mode 100644 doc/guides/prog_guide/img/efd_i3.svg
create mode 100644 doc/guides/prog_guide/img/efd_i4.svg
create mode 100644 doc/guides/prog_guide/img/efd_i5.svg
create mode 100644 doc/guides/prog_guide/img/efd_i6.svg
create mode 100644 doc/guides/prog_guide/img/efd_i7.svg
create mode 100644 doc/guides/prog_guide/img/efd_i8.svg
create mode 100644 doc/guides/prog_guide/img/efd_i9.svg
diff --git a/MAINTAINERS b/MAINTAINERS
index b124f6e..66e9466 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -532,6 +532,7 @@ EFD
M: Byron Marohn <byron.marohn@intel.com>
M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
F: lib/librte_efd/
+F: doc/guides/prog_guide/efd_lib.rst
F: app/test/test_efd*
F: examples/flow_distributor/
diff --git a/doc/guides/prog_guide/efd_lib.rst b/doc/guides/prog_guide/efd_lib.rst
new file mode 100644
index 0000000..5b8e4e3
--- /dev/null
+++ b/doc/guides/prog_guide/efd_lib.rst
@@ -0,0 +1,440 @@
+.. BSD LICENSE
+ Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of Intel Corporation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+.. _Efd_Library:
+
+Elastic Flow Distributor Library
+================================
+
+Introduction
+------------
+
+In Data Centers today, clustering and scheduling of distributed workloads
+is a very common task. Many workloads require a deterministic
+partitioning of a flat key space among a cluster of machines. When a
+packet enters the cluster, the ingress node will direct the packet to
+its handling node. For example, data-centers with disaggregated storage
+use storage metadata tables to forward I/O requests to the correct back end
+storage cluster, stateful packet inspection will use match incoming
+flows to signatures in flow tables to send incoming packets to their
+intended deep packet inspection (DPI) devices, and so on.
+
+EFD is a distributor library that uses perfect hashing to determine a
+target/value for a given incoming flow key. It has the following
+advantages: first, because it uses perfect hashing it does not store the
+key itself and hence lookup performance is not dependent on the key
+size. Second, the target/value can be any arbitrary value hence the
+system designer and/or operator can better optimize service rates and
+inter-cluster network traffic locating. Third, since the storage
+requirement is much smaller than a hash-based flow table (i.e. better
+fit for CPU cache), EFD can scale to millions of flow keys. Finally,
+with the current optimized library implementation, performance is fully
+scalable with any number of CPU cores.
+
+Flow Based Distribution
+-----------------------
+
+Computation Based Schemes
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Flow distribution and/or load balancing can be simply done using a
+stateless computation, for instance using round-robin or a simple
+computation based on the flow key as an input. For example, a hash
+function can be used to direct a certain flow to a target based on
+the flow key (e.g. ``h(key) mod n``) where h(key) is the hash value of the
+flow key and n is the number of possible targets.
+
+.. _figure_efd1:
+
+.. figure:: img/efd_i1.*
+
+ Load Balancing Using Front End Node
+
+In this scheme (:numref:`figure_efd1`), the front end server/distributor/load balancer
+extracts the flow key from the input packet and applies a computation to determine where
+this flow should be directed. Intuitively, this scheme is very simple
+and requires no state to be kept at the front end node, and hence,
+storage requirements are minimum.
+
+.. _figure_efd2:
+
+.. figure:: img/efd_i2.*
+
+ Consistent Hashing
+
+A widely used flow distributor that belongs to the same category of
+computation-based schemes is ``consistent hashing``, shown in :numref:`figure_efd2`.
+Target destinations (shown in red) are hashed into the same space as the flow
+keys (shown in blue), and keys are mapped to the nearest target in a clockwise
+fashion. Dynamically adding and removing targets with consistent hashing
+requires only K/n keys to be remapped on average, where K is the number of
+keys, and n is the number of targets. In contrast, in a traditional hash-based
+scheme, a change in the number of targets causes nearly all keys to be
+remapped.
+
+Although computation-based schemes are simple and need very little
+storage requirement, they suffer from the drawback that the system
+designer/operator can’t fully control the target to assign a specific
+key, as this is dictated by the hash function.
+Deterministically co-locating of keys together (for example, to minimize
+inter-server traffic or to optimize for network traffic conditions,
+target load, etc.) is simply not possible.
+
+Flow-Table Based Schemes
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+When using a Flow-Table based scheme to handle flow distribution/load
+balancing, in contrast with computation-based schemes, the system designer
+has the flexibility of assigning a given flow to any given
+target. The flow table (e.g. DPDK RTE Hash Library) will simply store
+both the flow key and the target value.
+
+.. _figure_efd3:
+
+.. figure:: img/efd_i3.*
+
+ Table Based Flow Distribution
+
+As shown in :numref:`figure_efd3`, when doing a lookup, the flow-table
+is indexed with the hash of the flow key and the keys (more than one is possible,
+because of hash collision) stored in this index and corresponding values
+are retrieved. The retrieved key(s) is matched with the input flow key
+and if there is a match the value (target id) is returned.
+
+The drawback of using a hash table for flow distribution/load balancing
+is the storage requirement, since the flow table need to store keys,
+signatures and target values. This doesn't allow this scheme to scale to
+millions of flow keys. Large tables will usually not fit in
+the CPU cache, and hence, the lookup performance is degraded because of
+the latency to access the main memory.
+
+EFD Based Scheme
+~~~~~~~~~~~~~~~~
+
+EFD combines the advantages of both flow-table based and computation-based
+schemes. It doesn't require the large storage necessary for
+flow-table based schemes (because EFD doesn't store the key as explained
+below), and it supports any arbitrary value for any given key.
+
+.. _figure_efd4:
+
+.. figure:: img/efd_i4.*
+
+ Searching for Perfect Hash Function
+
+The basic idea of EFD is when a given key is to be inserted, a family of
+hash functions is searched until the correct hash function that maps the
+input key to the correct value is found, as shown in :numref:`figure_efd4`.
+However, rather than explicitly storing all keys and their associated values,
+EFD stores only indices of hash functions that map keys to values, and
+thereby consumes much less space than conventional flow-based tables.
+The lookup operation is very simple, similar to a computational-based
+scheme: given an input key the lookup operation is reduced to hashing
+that key with the correct hash function.
+
+.. _figure_efd5:
+
+.. figure:: img/efd_i5.*
+
+ Divide and Conquer for Millions of Keys
+
+Intuitively, finding a hash function that maps each of a large number
+(millions) of input keys to the correct output value is effectively
+impossible, as a result EFD, as shown in :numref:`figure_efd5`,
+breaks the problem into smaller pieces (divide and conquer).
+EFD divides the entire input key set into many small groups.
+Each group consists of approximately 20-28 keys (a configurable parameter
+for the library), then, for each small group, a brute force search to find
+a hash function that produces the correct outputs for each key in the group.
+
+It should be mentioned that, since the online lookup table for EFD
+doesn't store the key itself, the size of the EFD table is independent
+of the key size and hence EFD lookup performance which is almost
+constant irrespective of the length of the key which is a highly
+desirable feature especially for longer keys.
+
+In summary, EFD is a set separation data structure that supports millions of
+keys. It is used to distribute a given key to an intended target. By itself
+EFD is not a FIB data structure with an exact match the input flow key.
+
+.. _Efd_example:
+
+Example of EFD Library Usage
+----------------------------
+
+EFD can be used along the data path of many network functions and middleboxes.
+As previously mentioned, it can used as an index table for
+<key,value> pairs, meta-data for objects, a flow-level load balancer, etc.
+:numref:`figure_efd6` shows an example of using EFD as a flow-level load
+balancer, where flows are received at a front end server before being forwarded
+to the target back end server for processing. The system designer would
+deterministically co-locate flows together in order to minimize cross-server
+interaction.
+(For example, flows requesting certain webpage objects are co-located
+together, to minimize forwarding of common objects across servers).
+
+.. _figure_efd6:
+
+.. figure:: img/efd_i6.*
+
+ EFD as a Flow-Level Load Balancer
+
+As shown in :numref:`figure_efd6`, the front end server will have an EFD table that
+stores for each group what is the perfect hash index that satisfies the
+correct output. Because the table size is small and fits in cache (since
+keys are not stored), it sustains a large number of flows (N*X, where N
+is the maximum number of flows served by each back end server of the X
+possible targets).
+
+With an input flow key, the group id is computed (for example, using
+last few bits of CRC hash) and then the EFD table is indexed with the
+group id to retrieve the corresponding hash index to use. Once the index
+is retrieved the key is hashed using this hash function and the result
+will be the intended correct target where this flow is supposed to be
+processed.
+
+It should be noted that as a result of EFD not matching the exact key but
+rather distributing the flows to a target back end node based on the
+perfect hash index, a key that has not been inserted before
+will be distributed to a valid target. Hence, a local table which stores
+the flows served at each node is used and is
+exact matched with the input key to rule out new never seen before
+flows.
+
+.. _Efd_api:
+
+Library API Overview
+--------------------
+
+The EFD library API is created with a very similar semantics of a
+hash-index or a flow table. The application creates an EFD table for a
+given maximum number of flows, a function is called to insert a flow key
+with a specific target value, and another function is used to retrieve
+target values for a given individual flow key or a bulk of keys.
+
+EFD Table Create
+~~~~~~~~~~~~~~~~
+
+The function ``rte_efd_create()`` is used to create and return a pointer
+to an EFD table that is sized to hold up to num_flows key.
+The online version of the EFD table (the one that does
+not store the keys and is used for lookups) will be allocated and
+created in the last level cache (LLC) of the socket defined by the
+online_socket_bitmask, while the offline EFD table (the one that
+stores the keys and is used for key inserts and for computing the
+perfect hashing) is allocated and created in the LLC of the socket
+defined by offline_socket_bitmask. It should be noted, that for
+highest performance the socket id should match that where the thread is
+running, i.e. the online EFD lookup table should be created on the same
+socket as where the lookup thread is running.
+
+EFD Insert and Update
+~~~~~~~~~~~~~~~~~~~~~
+
+The EFD function to insert a key or update a key to a new value is
+``rte_efd_update()``. This function will update an existing key to
+a new value (target) if the key has already been inserted
+before, or will insert the <key,value> pair if this key has not been inserted
+before. It will return 0 upon success. It will return
+``EFD_UPDATE_WARN_GROUP_FULL (1)`` if the operation is insert, and the
+last available space in the key's group was just used. It will return
+``EFD_UPDATE_FAILED (2)`` when the insertion or update has failed (either it
+failed to find a suitable perfect hash or the group was full). The function
+will return ``EFD_UPDATE_NO_CHANGE (3)`` if there is no change to the EFD
+table (i.e, same value already exists).
+
+EFD Lookup
+~~~~~~~~~~
+
+To lookup a certain key in an EFD table, the function ``rte_efd_lookup()``
+is used to return the value associated with single key.
+As previously mentioned, if the key has been inserted, the correct value
+inserted is returned, if the key has not been inserted before,
+a ‘random’ value (based on hashing of the key) is returned.
+For better performance and to decrease the overhead of
+function calls per key, it is always recommended to use a bulk lookup
+function (simultaneous lookup of multiple keys) instead of a single key
+lookup function. ``rte_efd_lookup_bulk()`` is the bulk lookup function,
+that looks up num_keys simultaneously stored in the key_list and the
+corresponding return values will be returned in the value_list.
+
+EFD Delete
+~~~~~~~~~~
+
+To delete a certain key in an EFD table, the function
+``rte_efd_delete()`` can be used. The function returns zero upon success
+when the key has been found and deleted. Socket_id is the parameter to
+use to lookup the existing value, which is ideally the caller's socket id.
+The previous value associated with this key will be returned
+in the prev_value argument.
+
+.. _Efd_internals:
+
+Library Internals
+-----------------
+
+This section provides the brief high-level idea and an overview
+of the library internals to accompany the RFC. The intent of this
+section is to explain to readers the high-level implementation of
+insert, lookup and group rebalancing in the EFD library.
+
+Insert Function Internals
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As previously mentioned the EFD divides the whole set of keys into
+groups of a manageable size (e.g. 28 keys) and then searches for the
+perfect hash that satisfies the intended target value for each key. EFD
+stores two version of the <key,value> table:
+
+- Offline Version (in memory): Only used for the insertion/update
+ operation, which is less frequent than the lookup operation. In the
+ offline version the exact keys for each group is stored. When a new
+ key is added, the hash function is updated that will satisfy the
+ value for the new key together with the all old keys already inserted
+ in this group.
+
+- Online Version (in cache): Used for the frequent lookup operation. In
+ the online version, as previously mentioned, the keys are not stored
+ but rather only the hash index for each group.
+
+.. _figure_efd7:
+
+.. figure:: img/efd_i7.*
+
+ Group Assignment
+
+:numref:`figure_efd7` depicts the group assignment for 7 flow keys as an example.
+Given a flow key, a hash function (in our implementation CRC hash) is
+used to get the group id. As shown in the figure, the groups can be
+unbalanced. (We highlight group rebalancing further below).
+
+.. _figure_efd8:
+
+.. figure:: img/efd_i8.*
+
+ Perfect Hash Search - Assigned Keys & Target Value
+
+Focusing on one group that has four keys, :numref:`figure_efd8` depicts the search
+algorithm to find the perfect hash function. Assuming that the target
+value bit for the keys is as shown in the figure, then the online EFD
+table will store a 16 bit hash index and 16 bit lookup table per group
+per value bit.
+
+.. _figure_efd9:
+
+.. figure:: img/efd_i9.*
+
+ Perfect Hash Search - Satisfy Target Values
+
+For a given keyX, a hash function ``(h(keyX, seed1) + index * h(keyX, seed2))``
+is used to point to certain bit index in the 16bit lookup_table value,
+as shown in :numref:`figure_efd9`.
+The insert function will brute force search for all possible values for the
+hash index until a non conflicting lookup_table is found.
+
+.. _figure_efd10:
+
+.. figure:: img/efd_i10.*
+
+ Finding Hash Index for Conflict Free lookup_table
+
+For example, since both key3 and key7 have a target bit value of 1, it
+is okay if the hash function of both keys point to the same bit in the
+lookup table. A conflict will occur if a hash index is used that maps
+both Key4 and Key7 to the same index in the lookup_table,
+as shown in :numref:`figure_efd10`, since their target value bit are not the same.
+Once a hash index is found that produces a lookup_table with no
+contradictions, this index is stored for this group. This procedure is
+repeated for each bit of target value.
+
+Lookup Function Internals
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The design principle of EFD is that lookups are much more frequent than
+inserts, and hence, EFD's design optimizes for the lookups which are
+faster and much simpler than the slower insert procedure (inserts are
+slow, because of perfect hash search as previously discussed).
+
+.. _figure_efd11:
+
+.. figure:: img/efd_i11.*
+
+ EFD Lookup Operation
+
+:numref:`figure_efd11` depicts the lookup operation for EFD. Given an input key,
+the group id is computed (using CRC hash) and then the hash index for this
+group is retrieved from the EFD table. Using the retrieved hash index,
+the hash function ``h(key, seed1) + index *h(key, seed2)`` is used which will
+result in an index in the lookup_table, the bit corresponding to this
+index will be the target value bit. This procedure is repeated for each
+bit of the target value.
+
+Group Rebalancing Function Internals
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When discussing EFD inserts and lookups, the discussion is simplified by
+assuming that a group id is simply a result of hash function. However,
+since hashing in general is not perfect and will not always produce a
+uniform output, this simplified assumption will lead to unbalanced
+groups, i.e., some group will have more keys than other groups.
+Typically, and to minimize insert time with an increasing number of keys,
+it is preferable that all groups will have a balanced number of keys, so
+the brute force search for the perfect hash terminates with a valid hash
+index. In order to achieve this target, groups are rebalanced during
+runtime inserts, and keys are moved around from a busy group to a less
+crowded group as the more keys are inserted.
+
+.. _figure_efd12:
+
+.. figure:: img/efd_i12.*
+
+ Runtime Group Rebalancing
+
+:numref:`figure_efd12` depicts the high level idea of group rebalancing, given an
+input key the hash result is split into two parts a chunk id and 8-bit
+bin id. A chunk contains 64 different groups and 256 bins (i.e. for any
+given bin it can map to 4 distinct groups). When a key is inserted, the
+bin id is computed, for example in :numref:`figure_efd12` bin_id=2,
+and since each bin can be mapped to one of four different groups (2 bit storage),
+the four possible mappings are evaluated and the one that will result in a
+balanced key distribution across these four is selected the mapping result
+is stored in these two bits.
+
+
+.. _Efd_references:
+
+References
+-----------
+
+1- EFD is based on collaborative research work between Intel and
+Carnegie Mellon University (CMU), interested readers can refer to the paper
+“Scaling Up Clustered Network Appliances with ScaleBricks;” Dong Zhou et al.
+at SIGCOMM 2015 (`http://conferences.sigcomm.org/sigcomm/2015/pdf/papers/p241.pdf`)
+for more information.
diff --git a/doc/guides/prog_guide/img/efd_i1.svg b/doc/guides/prog_guide/img/efd_i1.svg
new file mode 100644
index 0000000..7f8fcb3
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i1.svg
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i1.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="3.25609in" height="3.375in"
+ viewBox="0 0 234.439 243" xml:space="preserve" color-interpolation-filters="sRGB" class="st10">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st5 {marker-end:url(#mrkr5-12);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:6}
+ .st6 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.70422535211268}
+ .st7 {stroke:#5b9bd5;stroke-dasharray:2.25,4.5;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.25}
+ .st8 {marker-end:url(#mrkr5-39);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st9 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st10 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-12" class="st6" v:arrowType="5" v:arrowSize="2" v:setback="2.485" refX="-2.485" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-1.42,-1.42) "/>
+ </marker>
+ <marker id="mrkr5-39" class="st9" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(77.718,-113.348)">
+ <title>Square</title>
+ <desc>LB</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="18" cy="225" width="36" height="36"/>
+ <g id="shadow2-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="207" width="36" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="207" width="36" height="36" class="st3"/>
+ <text x="13.18" y="228" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>LB</text> </g>
+ <g id="shape3-7" v:mID="3" v:groupContext="shape" transform="translate(37.0513,-131.348)">
+ <title>Sheet.3</title>
+ <path d="M0 243 L25.76 243" class="st5"/>
+ </g>
+ <g id="shape4-13" v:mID="4" v:groupContext="shape" transform="translate(167.718,-178.598)">
+ <title>Square.4</title>
+ <desc>Target 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="220.5" width="45" height="45"/>
+ <g id="shadow4-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="198" width="45" height="45" class="st2"/>
+ </g>
+ <rect x="0" y="198" width="45" height="45" class="st3"/>
+ <text x="5.74" y="223.5" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target 1</text> </g>
+ <g id="shape5-19" v:mID="5" v:groupContext="shape" transform="translate(167.718,-121.005)">
+ <title>Square.5</title>
+ <desc>Target 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="220.5" width="45" height="45"/>
+ <g id="shadow5-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="198" width="45" height="45" class="st2"/>
+ </g>
+ <rect x="0" y="198" width="45" height="45" class="st3"/>
+ <text x="5.74" y="223.5" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target 2</text> </g>
+ <g id="shape7-25" v:mID="7" v:groupContext="shape" transform="translate(167.718,-23.3478)">
+ <title>Square.7</title>
+ <desc>Target N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="220.5" width="45" height="45"/>
+ <g id="shadow7-26" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="198" width="45" height="45" class="st2"/>
+ </g>
+ <rect x="0" y="198" width="45" height="45" class="st3"/>
+ <text x="5.05" y="223.5" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target N</text> </g>
+ <g id="shape8-31" v:mID="8" v:groupContext="shape" transform="translate(433.218,132.402) rotate(90)">
+ <title>Sheet.8</title>
+ <path d="M0 243 L34.59 243" class="st7"/>
+ </g>
+ <g id="shape9-34" v:mID="9" v:groupContext="shape" transform="translate(-78.4279,-37.1059) rotate(-52.2532)">
+ <title>Sheet.9</title>
+ <path d="M0 243 L81.18 243" class="st8"/>
+ </g>
+ <g id="shape11-40" v:mID="11" v:groupContext="shape" transform="translate(60.3469,-125.414) rotate(-12.6875)">
+ <title>Sheet.11</title>
+ <path d="M0 243 L48.32 243" class="st8"/>
+ </g>
+ <g id="shape12-45" v:mID="12" v:groupContext="shape" transform="translate(319.172,-18.1081) rotate(57.7244)">
+ <title>Sheet.12</title>
+ <path d="M0 243 L94.09 243" class="st8"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i10.svg b/doc/guides/prog_guide/img/efd_i10.svg
new file mode 100644
index 0000000..d26ec61
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i10.svg
@@ -0,0 +1,384 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i11.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="9.76715in" height="2.82917in"
+ viewBox="0 0 703.234 203.701" xml:space="preserve" color-interpolation-filters="sRGB" class="st15">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st4 {fill:#000000;font-family:Arial;font-size:0.998566em}
+ .st5 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st6 {fill:#000000;font-family:Arial;font-size:0.918686em;font-style:italic}
+ .st7 {fill:#000000;font-family:Arial;font-size:0.918686em}
+ .st8 {fill:#7e8d96;font-family:Arial;font-size:0.998566em;font-weight:bold}
+ .st9 {fill:#00b050;font-family:Arial;font-size:0.998566em;font-weight:bold}
+ .st10 {fill:#ff0000;font-family:Arial;font-size:0.998566em;font-weight:bold}
+ .st11 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st12 {fill:#ffffff;font-family:Arial;font-size:1.49785em}
+ .st13 {stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st14 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st15 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(19.0195,-93.4328)">
+ <title>Sheet.3</title>
+ <path d="M0 182.93 C0 180.64 1.87 178.78 4.16 178.78 L109.18 178.78 C111.47 178.78 113.33 180.64 113.33 182.93 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.16 203.7 C1.87 203.7 0 201.84 0 199.55 L0 182.93 Z"
+ class="st1"/>
+ </g>
+ <g id="shape4-3" v:mID="4" v:groupContext="shape" transform="translate(19.0195,-93.4328)">
+ <title>Sheet.4</title>
+ <path d="M0 182.93 C0 180.64 1.87 178.78 4.16 178.78 L109.18 178.78 C111.47 178.78 113.33 180.64 113.33 182.93 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.16 203.7 C1.87 203.7 0 201.84 0 199.55 L0 182.93 Z"
+ class="st2"/>
+ </g>
+ <g id="shape5-5" v:mID="5" v:groupContext="shape" transform="translate(19.0195,-96.9057)">
+ <title>Sheet.5</title>
+ <desc>Key1: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.0575" cy="196.509" width="116.12" height="14.3829"/>
+ <path d="M116.11 189.32 L0 189.32 L0 203.7 L116.11 203.7 L116.11 189.32" class="st3"/>
+ <text x="15.59" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1: Value = 0</text> </g>
+ <g id="shape6-9" v:mID="6" v:groupContext="shape" transform="translate(19.0195,-68.6284)">
+ <title>Sheet.6</title>
+ <path d="M0 182.93 C0 180.64 1.87 178.78 4.16 178.78 L109.18 178.78 C111.47 178.78 113.33 180.64 113.33 182.93 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.16 203.7 C1.87 203.7 0 201.84 0 199.55 L0 182.93 Z"
+ class="st1"/>
+ </g>
+ <g id="shape7-11" v:mID="7" v:groupContext="shape" transform="translate(19.0195,-68.6284)">
+ <title>Sheet.7</title>
+ <path d="M0 182.93 C0 180.64 1.87 178.78 4.16 178.78 L109.18 178.78 C111.47 178.78 113.33 180.64 113.33 182.93 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.16 203.7 C1.87 203.7 0 201.84 0 199.55 L0 182.93 Z"
+ class="st2"/>
+ </g>
+ <g id="shape8-13" v:mID="8" v:groupContext="shape" transform="translate(19.0195,-72.0832)">
+ <title>Sheet.8</title>
+ <desc>Key3: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.0575" cy="196.509" width="116.12" height="14.3829"/>
+ <path d="M116.11 189.32 L0 189.32 L0 203.7 L116.11 203.7 L116.11 189.32" class="st3"/>
+ <text x="15.59" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3: Value = 1</text> </g>
+ <g id="shape9-17" v:mID="9" v:groupContext="shape" transform="translate(19.0195,-43.5843)">
+ <title>Sheet.9</title>
+ <path d="M0 182.84 C-0 180.53 1.88 178.66 4.19 178.66 L109.15 178.66 C111.46 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.53 C113.33 201.84 111.46 203.7 109.15 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.53 L0 182.84 Z"
+ class="st1"/>
+ </g>
+ <g id="shape10-19" v:mID="10" v:groupContext="shape" transform="translate(19.0195,-43.5843)">
+ <title>Sheet.10</title>
+ <path d="M0 182.84 C-0 180.53 1.88 178.66 4.19 178.66 L109.15 178.66 C111.46 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.53 C113.33 201.84 111.46 203.7 109.15 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.53 L0 182.84 Z"
+ class="st2"/>
+ </g>
+ <g id="shape11-21" v:mID="11" v:groupContext="shape" transform="translate(19.0195,-47.1109)">
+ <title>Sheet.11</title>
+ <desc>Key4: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.0575" cy="196.509" width="116.12" height="14.3829"/>
+ <path d="M116.11 189.32 L0 189.32 L0 203.7 L116.11 203.7 L116.11 189.32" class="st3"/>
+ <text x="15.59" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4: Value = 0</text> </g>
+ <g id="shape12-25" v:mID="12" v:groupContext="shape" transform="translate(19.0195,-19.0195)">
+ <title>Sheet.12</title>
+ <path d="M0 182.84 C-0 180.53 1.88 178.66 4.19 178.66 L109.15 178.66 C111.46 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.53 C113.33 201.84 111.46 203.7 109.15 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.53 L0 182.84 Z"
+ class="st1"/>
+ </g>
+ <g id="shape13-27" v:mID="13" v:groupContext="shape" transform="translate(19.0195,-19.0195)">
+ <title>Sheet.13</title>
+ <path d="M0 182.84 C-0 180.53 1.88 178.66 4.19 178.66 L109.15 178.66 C111.46 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.53 C113.33 201.84 111.46 203.7 109.15 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.53 L0 182.84 Z"
+ class="st2"/>
+ </g>
+ <g id="shape14-29" v:mID="14" v:groupContext="shape" transform="translate(19.0195,-22.5475)">
+ <title>Sheet.14</title>
+ <desc>Key7: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.0575" cy="196.509" width="116.12" height="14.3829"/>
+ <path d="M116.11 189.32 L0 189.32 L0 203.7 L116.11 203.7 L116.11 189.32" class="st3"/>
+ <text x="15.59" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7: Value = 1</text> </g>
+ <g id="shape15-33" v:mID="15" v:groupContext="shape" transform="translate(141.656,-45.5615)">
+ <title>Sheet.15</title>
+ <path d="M0 169.01 L22.75 169.01 L22.75 157.45 L45.5 180.57 L22.75 203.7 L22.75 192.14 L0 192.14 L0 169.01 Z"
+ class="st5"/>
+ </g>
+ <g id="shape16-35" v:mID="16" v:groupContext="shape" transform="translate(193.22,-56.0464)">
+ <title>Sheet.16</title>
+ <path d="M0 182.84 C0 180.53 1.88 178.66 4.19 178.66 L96.55 178.66 C98.87 178.66 100.73 180.53 100.73 182.84 L100.73
+ 199.54 C100.73 201.84 98.87 203.7 96.55 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.54 L0 182.84 Z"
+ class="st1"/>
+ </g>
+ <g id="shape17-37" v:mID="17" v:groupContext="shape" transform="translate(193.22,-56.0464)">
+ <title>Sheet.17</title>
+ <path d="M0 182.84 C0 180.53 1.88 178.66 4.19 178.66 L96.55 178.66 C98.87 178.66 100.73 180.53 100.73 182.84 L100.73
+ 199.54 C100.73 201.84 98.87 203.7 96.55 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.54 L0 182.84 Z"
+ class="st2"/>
+ </g>
+ <g id="shape18-39" v:mID="18" v:groupContext="shape" transform="translate(228.157,-66.9545)">
+ <title>Sheet.18</title>
+ <desc>F</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.63538" cy="197.084" width="11.28" height="13.2327"/>
+ <path d="M11.27 190.47 L0 190.47 L0 203.7 L11.27 203.7 L11.27 190.47" class="st3"/>
+ <text x="2.27" y="200.39" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>F</text> </g>
+ <g id="shape19-43" v:mID="19" v:groupContext="shape" transform="translate(234.88,-66.9545)">
+ <title>Sheet.19</title>
+ <desc>(key,</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.261" cy="197.084" width="34.53" height="13.2327"/>
+ <path d="M34.52 190.47 L0 190.47 L0 203.7 L34.52 203.7 L34.52 190.47" class="st3"/>
+ <text x="5.32" y="200.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(key, </text> </g>
+ <g id="shape20-47" v:mID="20" v:groupContext="shape" transform="translate(198.215,-53.7734)">
+ <title>Sheet.20</title>
+ <desc>hash_index =</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="41.4128" cy="197.084" width="82.83" height="13.2327"/>
+ <path d="M82.83 190.47 L0 190.47 L0 203.7 L82.83 203.7 L82.83 190.47" class="st3"/>
+ <text x="8.47" y="200.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash_index = </text> </g>
+ <g id="shape21-51" v:mID="21" v:groupContext="shape" transform="translate(274.858,-53.7734)">
+ <title>Sheet.21</title>
+ <desc>i)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.28241" cy="197.084" width="10.57" height="13.2327"/>
+ <path d="M10.56 190.47 L0 190.47 L0 203.7 L10.56 203.7 L10.56 190.47" class="st3"/>
+ <text x="2.22" y="200.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>i)</text> </g>
+ <g id="shape22-55" v:mID="22" v:groupContext="shape" transform="translate(351.453,-93.7923)">
+ <title>Sheet.22</title>
+ <path d="M0 182.84 C0 180.53 1.88 178.66 4.19 178.66 L109.16 178.66 C111.47 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.54 C113.33 201.84 111.47 203.7 109.16 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.54 L0 182.84 Z"
+ class="st1"/>
+ </g>
+ <g id="shape23-57" v:mID="23" v:groupContext="shape" transform="translate(351.453,-93.7923)">
+ <title>Sheet.23</title>
+ <path d="M0 182.84 C0 180.53 1.88 178.66 4.19 178.66 L109.16 178.66 C111.47 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.54 C113.33 201.84 111.47 203.7 109.16 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.54 L0 182.84 Z"
+ class="st2"/>
+ </g>
+ <g id="shape24-59" v:mID="24" v:groupContext="shape" transform="translate(355.798,-97.3147)">
+ <title>Sheet.24</title>
+ <desc>Key1: Position 4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="51.7083" cy="196.509" width="103.42" height="14.3829"/>
+ <path d="M103.42 189.32 L0 189.32 L0 203.7 L103.42 203.7 L103.42 189.32" class="st3"/>
+ <text x="8.41" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1: Position 4</text> </g>
+ <g id="shape25-63" v:mID="25" v:groupContext="shape" transform="translate(351.453,-68.9879)">
+ <title>Sheet.25</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape26-65" v:mID="26" v:groupContext="shape" transform="translate(351.453,-68.9879)">
+ <title>Sheet.26</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape27-67" v:mID="27" v:groupContext="shape" transform="translate(355.798,-72.4921)">
+ <title>Sheet.27</title>
+ <desc>Key3: Position 6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="51.7083" cy="196.509" width="103.42" height="14.3829"/>
+ <path d="M103.42 189.32 L0 189.32 L0 203.7 L103.42 203.7 L103.42 189.32" class="st3"/>
+ <text x="8.41" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3: Position 6</text> </g>
+ <g id="shape28-71" v:mID="28" v:groupContext="shape" transform="translate(351.453,-44.0636)">
+ <title>Sheet.28</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape29-73" v:mID="29" v:groupContext="shape" transform="translate(351.453,-44.0636)">
+ <title>Sheet.29</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape30-75" v:mID="30" v:groupContext="shape" transform="translate(351.215,-47.5198)">
+ <title>Sheet.30</title>
+ <desc>Key4: Position 14</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="55.5403" cy="196.509" width="111.09" height="14.3829"/>
+ <path d="M111.08 189.32 L0 189.32 L0 203.7 L111.08 203.7 L111.08 189.32" class="st3"/>
+ <text x="8.91" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4: Position 14</text> </g>
+ <g id="shape31-79" v:mID="31" v:groupContext="shape" transform="translate(351.453,-19.4988)">
+ <title>Sheet.31</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape32-81" v:mID="32" v:groupContext="shape" transform="translate(351.453,-19.4988)">
+ <title>Sheet.32</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape33-83" v:mID="33" v:groupContext="shape" transform="translate(351.215,-22.9565)">
+ <title>Sheet.33</title>
+ <desc>Key7: Position 14</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="55.5403" cy="196.509" width="111.09" height="14.3829"/>
+ <path d="M111.08 189.32 L0 189.32 L0 203.7 L111.08 203.7 L111.08 189.32" class="st3"/>
+ <text x="8.91" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7: Position 14</text> </g>
+ <g id="shape34-87" v:mID="34" v:groupContext="shape" transform="translate(299.89,-46.0408)">
+ <title>Sheet.34</title>
+ <path d="M0 169.01 L22.75 169.01 L22.75 157.45 L45.5 180.57 L22.75 203.7 L22.75 192.14 L0 192.14 L0 169.01 Z"
+ class="st5"/>
+ </g>
+ <g id="shape35-89" v:mID="35" v:groupContext="shape" transform="translate(528.896,-117.518)">
+ <title>Sheet.35</title>
+ <path d="M0 182.94 C0 180.66 1.89 178.78 4.17 178.78 L137.64 178.78 C139.92 178.78 141.79 180.66 141.79 182.94 L141.79
+ 199.57 C141.79 201.84 139.92 203.7 137.64 203.7 L4.17 203.7 C1.89 203.7 0 201.84 0 199.57 L0 182.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape36-91" v:mID="36" v:groupContext="shape" transform="translate(528.896,-117.518)">
+ <title>Sheet.36</title>
+ <path d="M0 182.94 C0 180.66 1.89 178.78 4.17 178.78 L137.64 178.78 C139.92 178.78 141.79 180.66 141.79 182.94 L141.79
+ 199.57 C141.79 201.84 139.92 203.7 137.64 203.7 L4.17 203.7 C1.89 203.7 0 201.84 0 199.57 L0 182.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape37-93" v:mID="37" v:groupContext="shape" transform="translate(530.056,-121.017)">
+ <title>Sheet.37</title>
+ <desc>0000</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="19.1585" cy="196.509" width="38.32" height="14.3829"/>
+ <path d="M38.32 189.32 L0 189.32 L0 203.7 L38.32 203.7 L38.32 189.32" class="st3"/>
+ <text x="5.83" y="200.1" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0000 </text> </g>
+ <g id="shape38-97" v:mID="38" v:groupContext="shape" transform="translate(567.215,-121.017)">
+ <title>Sheet.38</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.7483" cy="196.509" width="11.5" height="14.3829"/>
+ <path d="M11.5 189.32 L0 189.32 L0 203.7 L11.5 203.7 L11.5 189.32" class="st3"/>
+ <text x="2.42" y="200.1" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape39-101" v:mID="39" v:groupContext="shape" transform="translate(576.215,-121.017)">
+ <title>Sheet.39</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.7483" cy="196.509" width="11.5" height="14.3829"/>
+ <path d="M11.5 189.32 L0 189.32 L0 203.7 L11.5 203.7 L11.5 189.32" class="st3"/>
+ <text x="2.42" y="200.1" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape40-105" v:mID="40" v:groupContext="shape" transform="translate(584.486,-121.017)">
+ <title>Sheet.40</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.7483" cy="196.509" width="11.5" height="14.3829"/>
+ <path d="M11.5 189.32 L0 189.32 L0 203.7 L11.5 203.7 L11.5 189.32" class="st3"/>
+ <text x="2.42" y="200.1" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape41-109" v:mID="41" v:groupContext="shape" transform="translate(588.646,-121.017)">
+ <title>Sheet.41</title>
+ <desc>0 0000 00</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="32.5687" cy="196.509" width="65.14" height="14.3829"/>
+ <path d="M65.14 189.32 L0 189.32 L0 203.7 L65.14 203.7 L65.14 189.32" class="st3"/>
+ <text x="5.91" y="200.1" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0 0000 00</text> </g>
+ <g id="shape42-113" v:mID="42" v:groupContext="shape" transform="translate(644.965,-121.017)">
+ <title>Sheet.42</title>
+ <desc>?</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="6.12511" cy="196.509" width="12.26" height="14.3829"/>
+ <path d="M12.25 189.32 L0 189.32 L0 203.7 L12.25 203.7 L12.25 189.32" class="st3"/>
+ <text x="2.47" y="200.1" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>?</text> </g>
+ <g id="shape43-117" v:mID="43" v:groupContext="shape" transform="translate(654.718,-121.017)">
+ <title>Sheet.43</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.7483" cy="196.509" width="11.5" height="14.3829"/>
+ <path d="M11.5 189.32 L0 189.32 L0 203.7 L11.5 203.7 L11.5 189.32" class="st3"/>
+ <text x="2.42" y="200.1" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape44-121" v:mID="44" v:groupContext="shape" transform="translate(464.786,-105.296)">
+ <title>Sheet.44</title>
+ <path d="M0 203.7 L108.29 203.7 C108.86 203.7 109.31 203.22 109.31 202.68 L109.31 189.5 L107.27 189.5 L107.27 202.68
+ L108.29 201.66 L0 201.66 L0 203.7 ZM111.35 190.52 L108.29 184.41 L105.23 190.55 L111.35 190.52 Z"
+ class="st11"/>
+ </g>
+ <g id="shape45-123" v:mID="45" v:groupContext="shape" transform="translate(464.786,-80.4315)">
+ <title>Sheet.45</title>
+ <path d="M0 203.7 L123.63 203.7 C124.2 203.7 124.65 203.25 124.65 202.68 L124.65 164.28 L122.61 164.28 L122.61 202.68
+ L123.63 201.66 L0 201.66 L0 203.7 ZM126.69 165.3 L123.6 159.18 L120.57 165.33 L126.69 165.3 Z"
+ class="st11"/>
+ </g>
+ <g id="shape46-125" v:mID="46" v:groupContext="shape" transform="translate(464.786,-55.4772)">
+ <title>Sheet.46</title>
+ <path d="M0 203.7 L186.48 203.7 C186.75 203.7 186.99 203.61 187.2 203.4 C187.38 203.22 187.5 202.95 187.5 202.68 L187.41
+ 139.32 L185.37 139.32 L185.46 202.68 L186.48 201.66 L0 201.66 L0 203.7 ZM189.51 140.07 L185.94 134.23 L183.41
+ 140.61 L189.51 140.07 Z" class="st11"/>
+ </g>
+ <g id="shape47-127" v:mID="47" v:groupContext="shape" transform="translate(464.786,-30.9125)">
+ <title>Sheet.47</title>
+ <path d="M0 203.7 L186.48 203.7 C186.75 203.7 186.99 203.61 187.2 203.4 C187.38 203.22 187.5 202.95 187.5 202.68 L187.41
+ 114.76 L185.37 114.76 L185.46 202.68 L186.48 201.66 L0 201.66 L0 203.7 ZM189.51 115.51 L185.94 109.67 L183.41
+ 116.05 L189.51 115.51 Z" class="st11"/>
+ </g>
+ <g id="shape48-129" v:mID="48" v:groupContext="shape" transform="translate(442.996,-151.106)">
+ <title>Sheet.48</title>
+ <path d="M0 179.56 C0 176.89 2.19 174.7 4.86 174.7 L70.8 174.7 C73.47 174.7 75.64 176.89 75.64 179.56 L75.64 198.88 C75.64
+ 201.54 73.47 203.7 70.8 203.7 L4.86 203.7 C2.19 203.7 0 201.54 0 198.88 L0 179.56 Z" class="st5"/>
+ </g>
+ <g id="shape49-131" v:mID="49" v:groupContext="shape" transform="translate(443.529,-155.018)">
+ <title>Sheet.49</title>
+ <desc>Values</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="37.8175" cy="192.914" width="75.64" height="21.5726"/>
+ <path d="M75.64 182.13 L0 182.13 L0 203.7 L75.64 203.7 L75.64 182.13" class="st3"/>
+ <text x="10.34" y="198.31" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Values</text> </g>
+ <g id="shape50-135" v:mID="50" v:groupContext="shape" transform="translate(102.458,-122.192)">
+ <title>Sheet.50</title>
+ <path d="M0 203.7 C-0 199.21 0.62 195.55 1.37 195.55 L11.67 195.55 C12.42 195.55 13.03 191.9 13.03 187.4 C13.03 191.9
+ 13.64 195.55 14.39 195.55 L24.69 195.55 C25.44 195.55 26.05 199.21 26.05 203.7" class="st13"/>
+ </g>
+ <g id="shape51-138" v:mID="51" v:groupContext="shape" transform="translate(115.454,-137.5)">
+ <title>Sheet.51</title>
+ <path d="M0.2 203.7 L322.66 174.12 L322.48 172.1 L0 201.68 L0.2 203.7 L0.2 203.7 ZM321.84 176.24 L327.66 172.64 L321.28
+ 170.16 L321.84 176.24 L321.84 176.24 Z" class="st14"/>
+ </g>
+ <g id="shape52-140" v:mID="52" v:groupContext="shape" transform="translate(518.211,-142.473)">
+ <title>Sheet.52</title>
+ <path d="M0.99 176.74 L44.78 200.38 L43.82 202.17 L0 178.51 L0.99 176.74 L0.99 176.74 ZM44.87 198.1 L48.8 203.7 L41.96
+ 203.46 L44.87 198.1 L44.87 198.1 Z" class="st11"/>
+ </g>
+ <g id="shape53-142" v:mID="53" v:groupContext="shape" transform="translate(518.331,-141.963)">
+ <title>Sheet.53</title>
+ <path d="M0.75 176.17 L60.09 200.32 L59.34 202.2 L0 178.06 L0.75 176.17 L0.75 176.17 ZM59.91 198.04 L64.44 203.19 L57.6
+ 203.7 L59.91 198.04 L59.91 198.04 Z" class="st11"/>
+ </g>
+ <g id="shape54-144" v:mID="54" v:groupContext="shape" transform="translate(576.558,-153.706)">
+ <title>Sheet.54</title>
+ <path d="M0 177.04 C0 174.1 2.4 171.71 5.34 171.71 L101.51 171.71 C104.48 171.71 106.85 174.1 106.85 177.04 L106.85 198.37
+ C106.85 201.33 104.48 203.7 101.51 203.7 L5.34 203.7 C2.4 203.7 0 201.33 0 198.37 L0 177.04 Z" class="st1"/>
+ </g>
+ <g id="shape55-146" v:mID="55" v:groupContext="shape" transform="translate(577.365,-151.611)">
+ <title>Sheet.55</title>
+ <path d="M0 177.04 C0 174.1 2.4 171.71 5.34 171.71 L101.51 171.71 C104.48 171.71 106.85 174.1 106.85 177.04 L106.85 198.37
+ C106.85 201.33 104.48 203.7 101.51 203.7 L5.34 203.7 C2.4 203.7 0 201.33 0 198.37 L0 177.04 Z" class="st2"/>
+ </g>
+ <g id="shape56-148" v:mID="56" v:groupContext="shape" transform="translate(593.952,-167.894)">
+ <title>Sheet.56</title>
+ <desc>Lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.2942" cy="196.509" width="86.59" height="14.3829"/>
+ <path d="M86.59 189.32 L0 189.32 L0 203.7 L86.59 203.7 L86.59 189.32" class="st3"/>
+ <text x="7.31" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Lookup_table</text> </g>
+ <g id="shape57-152" v:mID="57" v:groupContext="shape" transform="translate(608.239,-153.515)">
+ <title>Sheet.57</title>
+ <desc>(16 bits)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="26.8054" cy="196.509" width="53.62" height="14.3829"/>
+ <path d="M53.61 189.32 L0 189.32 L0 203.7 L53.61 203.7 L53.61 189.32" class="st3"/>
+ <text x="5.16" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(16 bits)</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i11.svg b/doc/guides/prog_guide/img/efd_i11.svg
new file mode 100644
index 0000000..f2cc656
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i11.svg
@@ -0,0 +1,319 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i12.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="10.2783in" height="4.28958in"
+ viewBox="0 0 740.039 308.85" xml:space="preserve" color-interpolation-filters="sRGB" class="st21">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st4 {fill:#000000;font-family:Arial;font-size:0.998566em}
+ .st5 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st6 {stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st7 {stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st8 {fill:#000000;font-family:Arial;font-size:0.918686em;font-weight:bold}
+ .st9 {fill:#00b050;font-size:1em}
+ .st10 {fill:#c00000;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st11 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.00749637}
+ .st12 {fill:#ffffff;font-family:Arial;font-size:1.16833em}
+ .st13 {fill:#2e75b5;stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st14 {fill:#ffffff;font-family:Arial;font-size:1.16666em}
+ .st15 {font-size:1em}
+ .st16 {fill:none;stroke:none;stroke-width:0.25}
+ .st17 {fill:#000000;font-family:Calibri;font-size:1.00001em}
+ .st18 {marker-end:url(#mrkr5-121);stroke:#5b9bd5;stroke-dasharray:1.5,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st19 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st20 {fill:#000000;font-family:Calibri;font-size:1.16666em}
+ .st21 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-121" class="st19" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape5-1" v:mID="5" v:groupContext="shape" transform="translate(36.0674,-256.878)">
+ <title>Sheet.5</title>
+ <path d="M0 291.88 C0 290 1.52 288.48 3.41 288.48 L68.51 288.48 C70.4 288.48 71.91 290 71.91 291.88 L71.91 305.46 C71.91
+ 307.33 70.4 308.85 68.51 308.85 L3.41 308.85 C1.52 308.85 0 307.33 0 305.46 L0 291.88 Z" class="st1"/>
+ </g>
+ <g id="shape6-3" v:mID="6" v:groupContext="shape" transform="translate(36.0674,-256.878)">
+ <title>Sheet.6</title>
+ <path d="M0 291.88 C0 290 1.52 288.48 3.41 288.48 L68.51 288.48 C70.4 288.48 71.91 290 71.91 291.88 L71.91 305.46 C71.91
+ 307.33 70.4 308.85 68.51 308.85 L3.41 308.85 C1.52 308.85 0 307.33 0 305.46 L0 291.88 Z" class="st2"/>
+ </g>
+ <g id="shape7-5" v:mID="7" v:groupContext="shape" transform="translate(61.6502,-258.089)">
+ <title>Sheet.7</title>
+ <desc>Key</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="13.7891" cy="301.658" width="27.58" height="14.3829"/>
+ <path d="M27.58 294.47 L0 294.47 L0 308.85 L27.58 308.85 L27.58 294.47" class="st3"/>
+ <text x="3.46" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key</text> </g>
+ <g id="shape8-9" v:mID="8" v:groupContext="shape" transform="translate(51.9748,-236.328)">
+ <title>Sheet.8</title>
+ <path d="M0 298.54 L9.81 298.54 L9.81 288.24 L29.44 288.24 L29.44 298.54 L39.26 298.54 L19.63 308.85 L0 298.54 Z"
+ class="st5"/>
+ </g>
+ <g id="shape9-11" v:mID="9" v:groupContext="shape" transform="translate(36.0674,-215.298)">
+ <title>Sheet.9</title>
+ <path d="M0 291.77 C0 289.89 1.54 288.36 3.42 288.36 L68.49 288.36 C70.38 288.36 71.91 289.89 71.91 291.77 L71.91 305.43
+ C71.91 307.32 70.38 308.85 68.49 308.85 L3.42 308.85 C1.54 308.85 0 307.32 0 305.43 L0 291.77 Z"
+ class="st1"/>
+ </g>
+ <g id="shape10-13" v:mID="10" v:groupContext="shape" transform="translate(36.0674,-215.298)">
+ <title>Sheet.10</title>
+ <path d="M0 291.77 C0 289.89 1.54 288.36 3.42 288.36 L68.49 288.36 C70.38 288.36 71.91 289.89 71.91 291.77 L71.91 305.43
+ C71.91 307.32 70.38 308.85 68.49 308.85 L3.42 308.85 C1.54 308.85 0 307.32 0 305.43 L0 291.77 Z"
+ class="st2"/>
+ </g>
+ <g id="shape11-15" v:mID="11" v:groupContext="shape" transform="translate(58.8889,-216.57)">
+ <title>Sheet.11</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.8573" cy="301.658" width="33.72" height="14.3829"/>
+ <path d="M33.71 294.47 L0 294.47 L0 308.85 L33.71 308.85 L33.71 294.47" class="st3"/>
+ <text x="3.86" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape12-19" v:mID="12" v:groupContext="shape" transform="translate(27.3033,-174.437)">
+ <title>Sheet.12</title>
+ <path d="M0 292.58 C0 290.78 1.46 289.32 3.26 289.32 L87.15 289.32 C88.95 289.32 90.4 290.78 90.4 292.58 L90.4 305.6
+ C90.4 307.4 88.95 308.85 87.15 308.85 L3.26 308.85 C1.46 308.85 0 307.4 0 305.6 L0 292.58 Z" class="st1"/>
+ </g>
+ <g id="shape13-21" v:mID="13" v:groupContext="shape" transform="translate(27.3033,-174.437)">
+ <title>Sheet.13</title>
+ <path d="M0 292.58 C0 290.78 1.46 289.32 3.26 289.32 L87.15 289.32 C88.95 289.32 90.4 290.78 90.4 292.58 L90.4 305.6
+ C90.4 307.4 88.95 308.85 87.15 308.85 L3.26 308.85 C1.46 308.85 0 307.4 0 305.6 L0 292.58 Z" class="st2"/>
+ </g>
+ <g id="shape14-23" v:mID="14" v:groupContext="shape" transform="translate(36.0515,-175.256)">
+ <title>Sheet.14</title>
+ <desc>0x0102ABCD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.6644" cy="301.658" width="87.33" height="14.3829"/>
+ <path d="M87.33 294.47 L0 294.47 L0 308.85 L87.33 308.85 L87.33 294.47" class="st3"/>
+ <text x="7.36" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102ABCD</text> </g>
+ <g id="shape15-27" v:mID="15" v:groupContext="shape" transform="translate(51.9748,-194.029)">
+ <title>Sheet.15</title>
+ <path d="M0 298.48 L9.81 298.48 L9.81 288.12 L29.44 288.12 L29.44 298.48 L39.26 298.48 L19.63 308.85 L0 298.48 Z"
+ class="st5"/>
+ </g>
+ <g id="shape16-29" v:mID="16" v:groupContext="shape" transform="translate(48.9133,-159.818)">
+ <title>Sheet.16</title>
+ <path d="M26.41 296.87 C26.41 300.18 25.97 302.86 25.41 302.86 L14.21 302.86 C13.66 302.86 13.21 305.55 13.21 308.85
+ C13.21 305.55 12.76 302.86 12.21 302.86 L1.01 302.86 C0.45 302.86 0 300.18 0 296.87" class="st6"/>
+ </g>
+ <g id="shape17-32" v:mID="17" v:groupContext="shape" transform="translate(19.0195,-19.0195)">
+ <title>Sheet.17</title>
+ <path d="M0 196.93 L0 308.85 L145.15 308.85 L145.15 196.93 L0 196.93 L0 196.93 Z" class="st1"/>
+ </g>
+ <g id="shape18-34" v:mID="18" v:groupContext="shape" transform="translate(19.0195,-19.0195)">
+ <title>Sheet.18</title>
+ <path d="M0 196.93 L145.15 196.93 L145.15 308.85 L0 308.85 L0 196.93" class="st7"/>
+ </g>
+ <g id="shape19-37" v:mID="19" v:groupContext="shape" transform="translate(28.2638,-70.6655)">
+ <title>Sheet.19</title>
+ <path d="M0 280.69 C0 277.58 2.53 275.06 5.64 275.06 L124.14 275.06 C127.26 275.06 129.78 277.58 129.78 280.69 L129.78
+ 303.22 C129.78 306.33 127.26 308.85 124.14 308.85 L5.64 308.85 C2.53 308.85 0 306.33 0 303.22 L0 280.69
+ Z" class="st1"/>
+ </g>
+ <g id="shape20-39" v:mID="20" v:groupContext="shape" transform="translate(28.2638,-70.6655)">
+ <title>Sheet.20</title>
+ <path d="M0 280.69 C0 277.58 2.53 275.06 5.64 275.06 L124.14 275.06 C127.26 275.06 129.78 277.58 129.78 280.69 L129.78
+ 303.22 C129.78 306.33 127.26 308.85 124.14 308.85 L5.64 308.85 C2.53 308.85 0 306.33 0 303.22 L0 280.69
+ Z" class="st2"/>
+ </g>
+ <g id="shape21-41" v:mID="21" v:groupContext="shape" transform="translate(57.4514,-85.7513)">
+ <title>Sheet.21</title>
+ <desc>hash_index =</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="45.0133" cy="301.658" width="90.03" height="14.3829"/>
+ <path d="M90.03 294.47 L0 294.47 L0 308.85 L90.03 308.85 L90.03 294.47" class="st3"/>
+ <text x="9.2" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash_index = </text> </g>
+ <g id="shape22-45" v:mID="22" v:groupContext="shape" transform="translate(76.3001,-71.3719)">
+ <title>Sheet.22</title>
+ <desc>38123</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.0762" cy="301.658" width="42.16" height="14.3829"/>
+ <path d="M42.15 294.47 L0 294.47 L0 308.85 L42.15 308.85 L42.15 294.47" class="st3"/>
+ <text x="4.42" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>38123</text> </g>
+ <g id="shape23-49" v:mID="23" v:groupContext="shape" transform="translate(28.2638,-27.048)">
+ <title>Sheet.23</title>
+ <path d="M0 280.69 C0 277.59 2.54 275.06 5.64 275.06 L124.14 275.06 C127.26 275.06 129.78 277.59 129.78 280.69 L129.78
+ 303.22 C129.78 306.33 127.26 308.85 124.14 308.85 L5.64 308.85 C2.54 308.85 0 306.33 0 303.22 L0 280.69
+ Z" class="st1"/>
+ </g>
+ <g id="shape24-51" v:mID="24" v:groupContext="shape" transform="translate(28.2638,-27.048)">
+ <title>Sheet.24</title>
+ <path d="M0 280.69 C0 277.59 2.54 275.06 5.64 275.06 L124.14 275.06 C127.26 275.06 129.78 277.59 129.78 280.69 L129.78
+ 303.22 C129.78 306.33 127.26 308.85 124.14 308.85 L5.64 308.85 C2.54 308.85 0 306.33 0 303.22 L0 280.69
+ Z" class="st2"/>
+ </g>
+ <g id="shape25-53" v:mID="25" v:groupContext="shape" transform="translate(54.0924,-41.564)">
+ <title>Sheet.25</title>
+ <desc>lookup_table =</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="46.931" cy="301.658" width="93.87" height="14.3829"/>
+ <path d="M93.86 294.47 L0 294.47 L0 308.85 L93.86 308.85 L93.86 294.47" class="st3"/>
+ <text x="7.79" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table =</text> </g>
+ <g id="shape26-57" v:mID="26" v:groupContext="shape" transform="translate(28.0195,-28.5506)">
+ <title>Sheet.26</title>
+ <desc>0110 1100 0101 1101</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="64.89" cy="302.233" width="129.79" height="13.2327"/>
+ <path d="M129.78 295.62 L0 295.62 L0 308.85 L129.78 308.85 L129.78 295.62" class="st3"/>
+ <text x="11.25" y="305.54" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0110 11<tspan
+ class="st9">0</tspan>0 0101 1101</text> </g>
+ <g id="shape27-62" v:mID="27" v:groupContext="shape" transform="translate(26.2461,-113.863)">
+ <title>Sheet.27</title>
+ <desc>Group ID: 0x0102</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="48.6286" cy="302.881" width="97.26" height="11.9384"/>
+ <path d="M97.26 296.91 L0 296.91 L0 308.85 L97.26 308.85 L97.26 296.91" class="st3"/>
+ <text x="7.73" y="305.86" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group ID: 0x0102</text> </g>
+ <g id="shape28-66" v:mID="28" v:groupContext="shape" transform="translate(42.3703,-135.313)">
+ <title>Sheet.28</title>
+ <path d="M0 298.48 L9.84 298.48 L9.84 288.12 L29.53 288.12 L29.53 298.48 L39.38 298.48 L19.69 308.85 L0 298.48 Z"
+ class="st5"/>
+ </g>
+ <g id="shape29-68" v:mID="29" v:groupContext="shape" transform="translate(117.645,-244.476)">
+ <title>Sheet.29</title>
+ <path d="M0 274.07 L22.75 274.07 L22.75 262.48 L45.5 285.66 L22.75 308.85 L22.75 297.26 L0 297.26 L0 274.07 Z"
+ class="st5"/>
+ </g>
+ <g id="shape30-70" v:mID="30" v:groupContext="shape" transform="translate(169.209,-251.966)">
+ <title>Sheet.30</title>
+ <path d="M0 283.69 C0 280.91 2.27 278.65 5.04 278.65 L111.77 278.65 C114.56 278.65 116.81 280.91 116.81 283.69 L116.81
+ 303.82 C116.81 306.6 114.56 308.85 111.77 308.85 L5.04 308.85 C2.27 308.85 0 306.6 0 303.82 L0 283.69 Z"
+ class="st1"/>
+ </g>
+ <g id="shape31-72" v:mID="31" v:groupContext="shape" transform="translate(169.209,-251.966)">
+ <title>Sheet.31</title>
+ <path d="M0 283.69 C0 280.91 2.27 278.65 5.04 278.65 L111.77 278.65 C114.56 278.65 116.81 280.91 116.81 283.69 L116.81
+ 303.82 C116.81 306.6 114.56 308.85 111.77 308.85 L5.04 308.85 C2.27 308.85 0 306.6 0 303.82 L0 283.69 Z"
+ class="st2"/>
+ </g>
+ <g id="shape35-74" v:mID="35" v:groupContext="shape" transform="translate(291.966,-244.476)">
+ <title>Sheet.35</title>
+ <path d="M0 274.07 L22.69 274.07 L22.69 262.48 L45.38 285.66 L22.69 308.85 L22.69 297.26 L0 297.26 L0 274.07 Z"
+ class="st5"/>
+ </g>
+ <g id="shape36-76" v:mID="36" v:groupContext="shape" transform="translate(343.17,-254.482)">
+ <title>Sheet.36</title>
+ <path d="M0 288.09 C0 285.8 1.88 283.93 4.17 283.93 L109.18 283.93 C111.47 283.93 113.33 285.8 113.33 288.09 L113.33
+ 304.7 C113.33 306.99 111.47 308.85 109.18 308.85 L4.17 308.85 C1.88 308.85 0 306.99 0 304.7 L0 288.09 Z"
+ class="st1"/>
+ </g>
+ <g id="shape37-78" v:mID="37" v:groupContext="shape" transform="translate(343.17,-254.482)">
+ <title>Sheet.37</title>
+ <path d="M0 288.09 C0 285.8 1.88 283.93 4.17 283.93 L109.18 283.93 C111.47 283.93 113.33 285.8 113.33 288.09 L113.33
+ 304.7 C113.33 306.99 111.47 308.85 109.18 308.85 L4.17 308.85 C1.88 308.85 0 306.99 0 304.7 L0 288.09 Z"
+ class="st2"/>
+ </g>
+ <g id="shape38-80" v:mID="38" v:groupContext="shape" transform="translate(368.337,-257.958)">
+ <title>Sheet.38</title>
+ <desc>Position = 6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="38.1131" cy="301.658" width="76.23" height="14.3829"/>
+ <path d="M76.23 294.47 L0 294.47 L0 308.85 L76.23 308.85 L76.23 294.47" class="st3"/>
+ <text x="6.64" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Position = 6</text> </g>
+ <g id="shape39-84" v:mID="39" v:groupContext="shape" transform="translate(158.044,-86.5202)">
+ <title>Sheet.39</title>
+ <path d="M0 308.85 L69.59 308.85 C70.16 308.85 70.62 308.39 70.62 307.83 L70.62 148.5 L68.57 148.5 L68.57 307.83 L69.59
+ 306.81 L0 306.81 L0 308.85 ZM72.66 149.52 L69.59 143.4 L66.53 149.52 L72.66 149.52 Z" class="st11"/>
+ </g>
+ <g id="shape41-86" v:mID="41" v:groupContext="shape" transform="translate(335.112,-199.647)">
+ <title>Sheet.41</title>
+ <desc>Apply the equation</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="71.2648" cy="300.436" width="142.53" height="16.8275"/>
+ <path d="M142.53 292.02 L0 292.02 L0 308.85 L142.53 308.85 L142.53 292.02" class="st3"/>
+ <text x="13.19" y="304.64" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Apply the equation </text> </g>
+ <g id="shape42-90" v:mID="42" v:groupContext="shape" transform="translate(341.115,-182.871)">
+ <title>Sheet.42</title>
+ <desc>to retrieve the bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="64.5256" cy="300.436" width="129.06" height="16.8275"/>
+ <path d="M129.05 292.02 L0 292.02 L0 308.85 L129.05 308.85 L129.05 292.02" class="st3"/>
+ <text x="12.31" y="304.64" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>to retrieve the bit </text> </g>
+ <g id="shape43-94" v:mID="43" v:groupContext="shape" transform="translate(349.999,-166.095)">
+ <title>Sheet.43</title>
+ <desc>position in the</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="54.2285" cy="300.436" width="108.46" height="16.8275"/>
+ <path d="M108.46 292.02 L0 292.02 L0 308.85 L108.46 308.85 L108.46 292.02" class="st3"/>
+ <text x="10.97" y="304.64" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>position in the </text> </g>
+ <g id="shape44-98" v:mID="44" v:groupContext="shape" transform="translate(353.361,-149.319)">
+ <title>Sheet.44</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="47.9619" cy="300.436" width="95.93" height="16.8275"/>
+ <path d="M95.92 292.02 L0 292.02 L0 308.85 L95.92 308.85 L95.92 292.02" class="st3"/>
+ <text x="8.21" y="304.64" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape47-102" v:mID="47" v:groupContext="shape" transform="translate(115.17,255.2) rotate(-90)">
+ <title>1-D word balloon</title>
+ <desc>Retrieve the value “0' from the specified location in the loo...</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ <v:ud v:nameU="Scale" v:val="VT0(1):26"/>
+ <v:ud v:nameU="AntiScale" v:val="VT0(1):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="29.2016" cy="218.85" width="180" height="58.4032" transform="rotate(90)"/>
+ <path d="M0 308.85 L58.4 308.85 L58.4 128.85 L0 128.85 L0 204.67 L-11.87 38.85 L-7.09 233.03 L0 233.03 L0 308.85 Z"
+ class="st13"/>
+ <text x="136.98" y="-41.8" transform="rotate(90)" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Retrieve the value “0' from <tspan
+ x="134.41" dy="1.2em" class="st15">the specified location in the </tspan><tspan x="181.1" dy="1.2em"
+ class="st15">lookup table</tspan></text> </g>
+ <g id="shape48-107" v:mID="48" v:groupContext="shape" transform="translate(169.209,-251.966)">
+ <title>Sheet.48</title>
+ <desc>F(Key, hash_index = 38123</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54.2285" cy="295.35" width="108.46" height="27"/>
+ <rect x="0" y="281.85" width="108.457" height="27" class="st16"/>
+ <text x="5.86" y="291.75" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>F(Key, hash_index = <tspan
+ x="39.02" dy="1.2em" class="st15">38123</tspan></text> </g>
+ <g id="shape49-111" v:mID="49" v:groupContext="shape" transform="translate(553.962,99) rotate(90)">
+ <title>1-D word balloon.49</title>
+ <desc>Apply the equation to retrieve the bit position in the lookup...</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ <v:ud v:nameU="Scale" v:val="VT0(1):26"/>
+ <v:ud v:nameU="AntiScale" v:val="VT0(1):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="29.2016" cy="218.85" width="180" height="58.4032" transform="rotate(-90)"/>
+ <path d="M0 308.85 L58.4 308.85 L58.4 128.85 L0 128.85 L0 204.67 L-51.13 299.85 L0 233.03 L0 308.85 Z" class="st13"/>
+ <text x="-284.62" y="16.6" transform="rotate(-90)" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Apply the equation to <tspan
+ x="-296.67" dy="1.2em" class="st15">retrieve the bit position in </tspan><tspan x="-270.22" dy="1.2em"
+ class="st15">the lookup</tspan>_table</text> </g>
+ <g id="shape50-116" v:mID="50" v:groupContext="shape" transform="translate(640.132,-104.709) rotate(44.1224)">
+ <title>Sheet.50</title>
+ <path d="M0 308.85 L54.13 308.85" class="st18"/>
+ </g>
+ <g id="shape51-122" v:mID="51" v:groupContext="shape" transform="translate(433.02,-122.267)">
+ <title>Sheet.51</title>
+ <desc>(Hash(key,seed1)+38123*hash(key,seed2))%16</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="144" cy="295.35" width="288" height="27"/>
+ <rect x="0" y="281.85" width="288" height="27" class="st2"/>
+ <text x="9.86" y="299.55" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(Hash(key,seed1)+38123*hash(key,seed2))%16</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i12.svg b/doc/guides/prog_guide/img/efd_i12.svg
new file mode 100644
index 0000000..a309d58
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i12.svg
@@ -0,0 +1,1008 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i13.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="10.2932in" height="5.27505in"
+ viewBox="0 0 741.108 379.804" xml:space="preserve" color-interpolation-filters="sRGB" class="st30">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st2 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st3 {fill:#004280;font-family:Arial;font-size:0.828804em}
+ .st4 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st5 {stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st6 {fill:#7030a0;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st7 {fill:#d0d6d9;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st8 {fill:#006fc5;stroke:#006fc5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.00749637}
+ .st9 {fill:#006fc5;stroke:#006fc5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st10 {fill:#d0d6d9;stroke:#d0d6d9;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st11 {fill:#d0d6d9;stroke:#d0d6d9;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st12 {fill:#004280;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st13 {fill:#00b050;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st14 {fill:#ff0000;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st15 {fill:#00b050;stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st16 {fill:#c00000;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st17 {fill:#000000;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st18 {fill:#7f6d00;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st19 {fill:#ff0000;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st20 {fill:#7e8d96;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st21 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st22 {fill:#000000;font-family:Arial;font-size:0.998566em}
+ .st23 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st24 {stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st25 {fill:#ffffff;font-family:Arial;font-size:0.998566em}
+ .st26 {fill:#ff6600;stroke:#ff6600;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.00749637}
+ .st27 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st28 {fill:#ff0000;stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st29 {fill:#ffffff;font-family:Arial;font-size:1.49785em}
+ .st30 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(304.703,-329.32)">
+ <title>Sheet.3</title>
+ <path d="M0 379.8 C-0 375.37 0.6 371.78 1.35 371.78 L205.05 371.78 C205.78 371.78 206.38 368.18 206.38 363.75 C206.38
+ 368.18 206.98 371.78 207.73 371.78 L411.43 371.78 C412.15 371.78 412.75 375.37 412.75 379.8" class="st1"/>
+ </g>
+ <g id="shape4-4" v:mID="4" v:groupContext="shape" transform="translate(219.943,-329.32)">
+ <title>Sheet.4</title>
+ <path d="M0 379.8 C0 375.64 0.57 372.25 1.26 372.25 L29.77 372.25 C30.48 372.25 31.03 368.88 31.03 364.71 C31.03 368.88
+ 31.6 372.25 32.29 372.25 L60.81 372.25 C61.51 372.25 62.07 375.64 62.07 379.8" class="st1"/>
+ </g>
+ <g id="shape5-7" v:mID="5" v:groupContext="shape" transform="translate(241.175,-343.9)">
+ <title>Sheet.5</title>
+ <desc>Bins</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="12.7158" cy="373.835" width="25.44" height="11.9384"/>
+ <path d="M25.43 367.87 L0 367.87 L0 379.8 L25.43 379.8 L25.43 367.87" class="st2"/>
+ <text x="3.04" y="376.82" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Bins</text> </g>
+ <g id="shape6-11" v:mID="6" v:groupContext="shape" transform="translate(496.212,-344.504)">
+ <title>Sheet.6</title>
+ <desc>Groups</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="20.3447" cy="373.835" width="40.69" height="11.9384"/>
+ <path d="M40.69 367.87 L0 367.87 L0 379.8 L40.69 379.8 L40.69 367.87" class="st2"/>
+ <text x="4.04" y="376.82" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Groups</text> </g>
+ <g id="shape7-15" v:mID="7" v:groupContext="shape" transform="translate(131.823,-260.299)">
+ <title>Sheet.7</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape8-17" v:mID="8" v:groupContext="shape" transform="translate(131.823,-260.299)">
+ <title>Sheet.8</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape9-20" v:mID="9" v:groupContext="shape" transform="translate(134.706,-310.738)">
+ <title>Sheet.9</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape10-24" v:mID="10" v:groupContext="shape" transform="translate(122.218,-329.32)">
+ <title>Sheet.10</title>
+ <path d="M0 379.8 C-0 375.64 0.57 372.25 1.26 372.25 L29.77 372.25 C30.47 372.25 31.03 368.88 31.03 364.71 C31.03 368.88
+ 31.6 372.25 32.29 372.25 L60.81 372.25 C61.51 372.25 62.07 375.64 62.07 379.8" class="st1"/>
+ </g>
+ <g id="shape11-27" v:mID="11" v:groupContext="shape" transform="translate(137.598,-343.9)">
+ <title>Sheet.11</title>
+ <desc>Chunks</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="20.9813" cy="373.835" width="41.97" height="11.9384"/>
+ <path d="M41.96 367.87 L0 367.87 L0 379.8 L41.96 379.8 L41.96 367.87" class="st2"/>
+ <text x="4.12" y="376.82" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Chunks</text> </g>
+ <g id="shape12-31" v:mID="12" v:groupContext="shape" transform="translate(131.823,-195.232)">
+ <title>Sheet.12</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape13-33" v:mID="13" v:groupContext="shape" transform="translate(131.823,-195.232)">
+ <title>Sheet.13</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape14-36" v:mID="14" v:groupContext="shape" transform="translate(134.706,-245.682)">
+ <title>Sheet.14</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape15-40" v:mID="15" v:groupContext="shape" transform="translate(131.823,-130.525)">
+ <title>Sheet.15</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st7"/>
+ </g>
+ <g id="shape16-42" v:mID="16" v:groupContext="shape" transform="translate(131.823,-130.525)">
+ <title>Sheet.16</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape17-45" v:mID="17" v:groupContext="shape" transform="translate(134.706,-180.952)">
+ <title>Sheet.17</title>
+ <desc>…</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.30974" cy="373.835" width="14.62" height="11.9384"/>
+ <path d="M14.62 367.87 L0 367.87 L0 379.8 L14.62 379.8 L14.62 367.87" class="st2"/>
+ <text x="2.34" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>…</text> </g>
+ <g id="shape18-49" v:mID="18" v:groupContext="shape" transform="translate(131.823,-65.4584)">
+ <title>Sheet.18</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape19-51" v:mID="19" v:groupContext="shape" transform="translate(131.823,-65.4584)">
+ <title>Sheet.19</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape20-54" v:mID="20" v:groupContext="shape" transform="translate(130.403,-115.896)">
+ <title>Sheet.20</title>
+ <desc>variable</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="24.7986" cy="373.835" width="49.6" height="11.9384"/>
+ <path d="M49.6 367.87 L0 367.87 L0 379.8 L49.6 379.8 L49.6 367.87" class="st2"/>
+ <text x="6" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>variable </text> </g>
+ <g id="shape21-58" v:mID="21" v:groupContext="shape" transform="translate(130.403,-103.913)">
+ <title>Sheet.21</title>
+ <desc># of</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="13.347" cy="373.835" width="26.7" height="11.9384"/>
+ <path d="M26.69 367.87 L0 367.87 L0 379.8 L26.69 379.8 L26.69 367.87" class="st2"/>
+ <text x="4.51" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/># of </text> </g>
+ <g id="shape22-62" v:mID="22" v:groupContext="shape" transform="translate(130.403,-91.93)">
+ <title>Sheet.22</title>
+ <desc>chunks</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.6122" cy="373.835" width="43.23" height="11.9384"/>
+ <path d="M43.22 367.87 L0 367.87 L0 379.8 L43.22 379.8 L43.22 367.87" class="st2"/>
+ <text x="4.2" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>chunks</text> </g>
+ <g id="shape23-66" v:mID="23" v:groupContext="shape" transform="translate(130.403,-79.9472)">
+ <title>Sheet.23</title>
+ <desc>(power</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.9251" cy="373.835" width="43.86" height="11.9384"/>
+ <path d="M43.85 367.87 L0 367.87 L0 379.8 L43.85 379.8 L43.85 367.87" class="st2"/>
+ <text x="5.62" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(power </text> </g>
+ <g id="shape24-70" v:mID="24" v:groupContext="shape" transform="translate(130.403,-67.9643)">
+ <title>Sheet.24</title>
+ <desc>of 2)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="13.6626" cy="373.835" width="27.33" height="11.9384"/>
+ <path d="M27.33 367.87 L0 367.87 L0 379.8 L27.33 379.8 L27.33 367.87" class="st2"/>
+ <text x="3.17" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>of 2)</text> </g>
+ <g id="shape25-74" v:mID="25" v:groupContext="shape" transform="translate(172.289,-260.838)">
+ <title>Sheet.25</title>
+ <path d="M1.43 379.8 L3.29 375.51 L1.85 374.9 L0 379.19 L1.43 379.8 L1.43 379.8 ZM3.9 374.08 L5.76 369.79 L4.32 369.18
+ L2.47 373.47 L3.9 374.08 L3.9 374.08 ZM6.37 368.36 L8.22 364.07 L6.79 363.45 L4.94 367.75 L6.37 368.36 L6.37
+ 368.36 ZM8.84 362.64 L10.69 358.35 L9.26 357.73 L7.41 362.02 L8.84 362.64 L8.84 362.64 ZM11.31 356.92 L13.16
+ 352.62 L11.73 352 L9.87 356.3 L11.31 356.92 L11.31 356.92 ZM13.78 351.19 L15.63 346.9 L14.2 346.28 L12.34
+ 350.57 L13.78 351.19 L13.78 351.19 ZM16.25 345.47 L18.1 341.17 L16.67 340.56 L14.81 344.85 L16.25 345.47
+ L16.25 345.47 ZM18.71 339.74 L20.57 335.45 L19.13 334.84 L17.28 339.13 L18.71 339.74 L18.71 339.74 ZM21.18
+ 334.02 L23.04 329.73 L21.6 329.12 L19.75 333.41 L21.18 334.02 L21.18 334.02 ZM23.65 328.3 L25.5 324.01 L24.07
+ 323.39 L22.22 327.68 L23.65 328.3 L23.65 328.3 ZM26.12 322.58 L27.97 318.28 L26.54 317.67 L24.69 321.96
+ L26.12 322.58 L26.12 322.58 ZM28.59 316.85 L29.44 314.87 L28.01 314.25 L27.16 316.24 L28.59 316.85 L28.59
+ 316.85 Z" class="st8"/>
+ </g>
+ <g id="shape26-76" v:mID="26" v:groupContext="shape" transform="translate(172.476,-20.463)">
+ <title>Sheet.26</title>
+ <path d="M1.55 203.84 L2.28 208.45 L0.74 208.7 L0 204.09 L1.55 203.84 L1.55 203.84 ZM2.52 209.99 L3.27 214.61 L1.73 214.86
+ L0.99 210.23 L2.52 209.99 L2.52 209.99 ZM3.51 216.15 L4.25 220.76 L2.7 221 L1.97 216.39 L3.51 216.15 L3.51
+ 216.15 ZM4.49 222.3 L5.24 226.92 L3.69 227.16 L2.96 222.54 L4.49 222.3 L4.49 222.3 ZM5.48 228.45 L6.21 233.07
+ L4.67 233.31 L3.93 228.7 L5.48 228.45 L5.48 228.45 ZM6.47 234.6 L7.2 239.22 L5.66 239.47 L4.92 234.86 L6.47
+ 234.6 L6.47 234.6 ZM7.44 240.76 L8.18 245.37 L6.65 245.63 L5.9 241 L7.44 240.76 L7.44 240.76 ZM8.43 246.91
+ L9.17 251.53 L7.62 251.77 L6.89 247.15 L8.43 246.91 L8.43 246.91 ZM9.41 253.07 L10.14 257.68 L8.61 257.92
+ L7.88 253.31 L9.41 253.07 L9.41 253.07 ZM10.4 259.21 L11.14 263.84 L9.59 264.08 L8.85 259.47 L10.4 259.21
+ L10.4 259.21 ZM11.38 265.37 L12.13 269.98 L10.58 270.24 L9.84 265.62 L11.38 265.37 L11.38 265.37 ZM12.37
+ 271.52 L13.1 276.14 L11.56 276.39 L10.82 271.76 L12.37 271.52 L12.37 271.52 ZM13.34 277.68 L14.09 282.29
+ L12.55 282.53 L11.81 277.92 L13.34 277.68 L13.34 277.68 ZM14.33 283.84 L15.07 288.45 L13.52 288.69 L12.79
+ 284.08 L14.33 283.84 L14.33 283.84 ZM15.32 289.99 L16.06 294.61 L14.51 294.85 L13.78 290.23 L15.32 289.99
+ L15.32 289.99 ZM16.3 296.13 L17.03 300.75 L15.5 301 L14.75 296.39 L16.3 296.13 L16.3 296.13 ZM17.29 302.29
+ L18.02 306.9 L16.48 307.16 L15.74 302.53 L17.29 302.29 L17.29 302.29 ZM18.26 308.45 L19 313.06 L17.47 313.3
+ L16.73 308.69 L18.26 308.45 L18.26 308.45 ZM19.25 314.6 L19.99 319.22 L18.44 319.46 L17.71 314.84 L19.25
+ 314.6 L19.25 314.6 ZM20.23 320.76 L20.96 325.37 L19.43 325.61 L18.7 321 L20.23 320.76 L20.23 320.76 ZM21.22
+ 326.9 L21.96 331.51 L20.41 331.77 L19.67 327.15 L21.22 326.9 L21.22 326.9 ZM22.2 333.06 L22.95 337.67 L21.4
+ 337.92 L20.66 333.31 L22.2 333.06 L22.2 333.06 ZM23.19 339.21 L23.92 343.83 L22.38 344.07 L21.64 339.45
+ L23.19 339.21 L23.19 339.21 ZM24.18 345.37 L24.91 349.98 L23.37 350.22 L22.63 345.61 L24.18 345.37 L24.18
+ 345.37 ZM25.15 351.52 L25.89 356.14 L24.36 356.38 L23.61 351.76 L25.15 351.52 L25.15 351.52 ZM26.14 357.67
+ L26.88 362.28 L25.33 362.53 L24.6 357.92 L26.14 357.67 L26.14 357.67 ZM27.12 363.82 L27.85 368.44 L26.32
+ 368.69 L25.59 364.08 L27.12 363.82 L27.12 363.82 ZM28.11 369.98 L28.84 374.59 L27.3 374.83 L26.56 370.22
+ L28.11 369.98 L28.11 369.98 ZM29.08 376.13 L29.64 379.55 L28.09 379.8 L27.55 376.37 L29.08 376.13 L29.08
+ 376.13 Z" class="st9"/>
+ </g>
+ <g id="shape27-78" v:mID="27" v:groupContext="shape" transform="translate(276.159,-233.368)">
+ <title>Sheet.27</title>
+ <path d="M0.45 294.85 L354.04 376.06 L353.59 378.04 L0 296.85 L0.45 294.85 L0.45 294.85 ZM353.5 373.84 L358.79 378.19
+ L352.12 379.8 L353.5 373.84 L353.5 373.84 Z" class="st10"/>
+ </g>
+ <g id="shape28-80" v:mID="28" v:groupContext="shape" transform="translate(275.859,-178.726)">
+ <title>Sheet.28</title>
+ <path d="M1.05 240.32 L231.44 376.33 L230.39 378.1 L0 242.09 L1.05 240.32 L1.05 240.32 ZM231.59 374.05 L235.31 379.8
+ L228.47 379.32 L231.59 374.05 L231.59 374.05 Z" class="st10"/>
+ </g>
+ <g id="shape29-82" v:mID="29" v:groupContext="shape" transform="translate(275.379,-87.6866)">
+ <title>Sheet.29</title>
+ <path d="M2 149.94 L50.05 374.61 L48.05 375.04 L0 150.38 L2 149.94 L2 149.94 ZM51.83 373.18 L50.12 379.8 L45.85 374.47
+ L51.83 373.18 L51.83 373.18 Z" class="st11"/>
+ </g>
+ <g id="shape30-84" v:mID="30" v:groupContext="shape" transform="translate(276.279,-177.108)">
+ <title>Sheet.30</title>
+ <path d="M0.21 353.74 L229.55 375.85 L229.34 377.89 L0 355.75 L0.21 353.74 L0.21 353.74 ZM228.71 373.72 L234.53 377.35
+ L228.14 379.8 L228.71 373.72 L228.71 373.72 Z" class="st10"/>
+ </g>
+ <g id="shape31-86" v:mID="31" v:groupContext="shape" transform="translate(275.919,-213.926)">
+ <title>Sheet.31</title>
+ <path d="M0.45 308.72 L312.65 376.06 L312.2 378.04 L0 310.72 L0.45 308.72 L0.45 308.72 ZM312.08 373.84 L317.43 378.13
+ L310.79 379.8 L312.08 373.84 L312.08 373.84 Z" class="st10"/>
+ </g>
+ <g id="shape32-88" v:mID="32" v:groupContext="shape" transform="translate(275.439,-143.377)">
+ <title>Sheet.32</title>
+ <path d="M1.4 238.41 L150.34 375.59 L148.96 377.09 L0 239.9 L1.4 238.41 L1.4 238.41 ZM150.98 373.41 L153.4 379.8 L146.83
+ 377.9 L150.98 373.41 L150.98 373.41 Z" class="st11"/>
+ </g>
+ <g id="shape33-90" v:mID="33" v:groupContext="shape" transform="translate(275.274,-108.821)">
+ <title>Sheet.33</title>
+ <path d="M1.73 236.53 L90.79 374.97 L89.08 376.07 L0 237.63 L1.73 236.53 L1.73 236.53 ZM91.96 373 L92.7 379.8 L86.82
+ 376.31 L91.96 373 L91.96 373 Z" class="st11"/>
+ </g>
+ <g id="shape34-92" v:mID="34" v:groupContext="shape" transform="translate(275.364,-124.069)">
+ <title>Sheet.34</title>
+ <path d="M1.55 251.66 L108.22 375.28 L106.67 376.61 L0 253 L1.55 251.66 L1.55 251.66 ZM109.1 373.18 L110.78 379.8 L104.46
+ 377.17 L109.1 373.18 L109.1 373.18 Z" class="st11"/>
+ </g>
+ <g id="shape35-94" v:mID="35" v:groupContext="shape" transform="translate(275.154,-87.7165)">
+ <title>Sheet.35</title>
+ <path d="M1.97 215.68 L49.85 374.64 L47.9 375.22 L0 216.27 L1.97 215.68 L1.97 215.68 ZM51.52 373.08 L50.35 379.8 L45.65
+ 374.83 L51.52 373.08 L51.52 373.08 Z" class="st11"/>
+ </g>
+ <g id="shape36-96" v:mID="36" v:groupContext="shape" transform="translate(276.009,-143.736)">
+ <title>Sheet.36</title>
+ <path d="M0.74 320.41 L147.92 376.36 L147.2 378.26 L0 322.32 L0.74 320.41 L0.74 320.41 ZM147.7 374.08 L152.34 379.11
+ L145.52 379.8 L147.7 374.08 L147.7 374.08 Z" class="st11"/>
+ </g>
+ <g id="shape37-98" v:mID="37" v:groupContext="shape" transform="translate(275.649,-108.821)">
+ <title>Sheet.37</title>
+ <path d="M1.46 285.74 L89.46 375.45 L88 376.87 L0 287.16 L1.46 285.74 L1.46 285.74 ZM90.21 373.29 L92.29 379.8 L85.82
+ 377.57 L90.21 373.29 L90.21 373.29 Z" class="st11"/>
+ </g>
+ <g id="shape38-100" v:mID="38" v:groupContext="shape" transform="translate(275.934,-108.686)">
+ <title>Sheet.38</title>
+ <path d="M0.89 335.24 L87.85 376.57 L86.97 378.41 L0 337.09 L0.89 335.24 L0.89 335.24 ZM87.81 374.29 L92.01 379.67 L85.16
+ 379.8 L87.81 374.29 L87.81 374.29 Z" class="st11"/>
+ </g>
+ <g id="shape39-102" v:mID="39" v:groupContext="shape" transform="translate(275.574,-89.454)">
+ <title>Sheet.39</title>
+ <path d="M1.61 316.29 L48.49 375.18 L46.88 376.45 L0 317.57 L1.61 316.29 L1.61 316.29 ZM49.45 373.11 L50.86 379.8 L44.65
+ 376.91 L49.45 373.11 L49.45 373.11 Z" class="st11"/>
+ </g>
+ <g id="shape40-104" v:mID="40" v:groupContext="shape" transform="translate(276.324,-141.744)">
+ <title>Sheet.40</title>
+ <path d="M0.11 368.21 L146.74 375.79 L146.62 377.83 L0 370.23 L0.11 368.21 L0.11 368.21 ZM145.82 373.71 L151.78 377.08
+ L145.51 379.8 L145.82 373.71 L145.82 373.71 Z" class="st11"/>
+ </g>
+ <g id="shape41-106" v:mID="41" v:groupContext="shape" transform="translate(230.508,-309.069)">
+ <title>Sheet.41</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st4"/>
+ </g>
+ <g id="shape42-108" v:mID="42" v:groupContext="shape" transform="translate(230.508,-309.069)">
+ <title>Sheet.42</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape43-111" v:mID="43" v:groupContext="shape" transform="translate(233.39,-309.868)">
+ <title>Sheet.43</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape44-115" v:mID="44" v:groupContext="shape" transform="translate(263.764,-309.869)">
+ <title>Sheet.44</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape45-119" v:mID="45" v:groupContext="shape" transform="translate(230.508,-292.413)">
+ <title>Sheet.45</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape46-121" v:mID="46" v:groupContext="shape" transform="translate(230.508,-292.413)">
+ <title>Sheet.46</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape47-124" v:mID="47" v:groupContext="shape" transform="translate(233.39,-293.221)">
+ <title>Sheet.47</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape48-128" v:mID="48" v:groupContext="shape" transform="translate(230.508,-275.757)">
+ <title>Sheet.48</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st4"/>
+ </g>
+ <g id="shape49-130" v:mID="49" v:groupContext="shape" transform="translate(230.508,-275.757)">
+ <title>Sheet.49</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape50-133" v:mID="50" v:groupContext="shape" transform="translate(233.39,-276.574)">
+ <title>Sheet.50</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape51-137" v:mID="51" v:groupContext="shape" transform="translate(252.478,-276.574)">
+ <title>Sheet.51</title>
+ <desc>3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>3</text> </g>
+ <g id="shape52-141" v:mID="52" v:groupContext="shape" transform="translate(258.001,-276.574)">
+ <title>Sheet.52</title>
+ <desc>+1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="8.11122" cy="373.835" width="16.23" height="11.9384"/>
+ <path d="M16.22 367.87 L0 367.87 L0 379.8 L16.22 379.8 L16.22 367.87" class="st2"/>
+ <text x="2.44" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>+1</text> </g>
+ <g id="shape53-145" v:mID="53" v:groupContext="shape" transform="translate(230.508,-259.7)">
+ <title>Sheet.53</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape54-147" v:mID="54" v:groupContext="shape" transform="translate(230.508,-259.7)">
+ <title>Sheet.54</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape55-150" v:mID="55" v:groupContext="shape" transform="translate(233.39,-260.497)">
+ <title>Sheet.55</title>
+ <desc>3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>3</text> </g>
+ <g id="shape56-154" v:mID="56" v:groupContext="shape" transform="translate(230.508,-243.164)">
+ <title>Sheet.56</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st4"/>
+ </g>
+ <g id="shape57-156" v:mID="57" v:groupContext="shape" transform="translate(230.508,-243.164)">
+ <title>Sheet.57</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape58-159" v:mID="58" v:groupContext="shape" transform="translate(233.39,-244.053)">
+ <title>Sheet.58</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape59-163" v:mID="59" v:groupContext="shape" transform="translate(263.764,-244.053)">
+ <title>Sheet.59</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape60-167" v:mID="60" v:groupContext="shape" transform="translate(230.508,-227.107)">
+ <title>Sheet.60</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape61-169" v:mID="61" v:groupContext="shape" transform="translate(230.508,-227.107)">
+ <title>Sheet.61</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape62-172" v:mID="62" v:groupContext="shape" transform="translate(233.39,-227.976)">
+ <title>Sheet.62</title>
+ <desc>5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>5</text> </g>
+ <g id="shape63-176" v:mID="63" v:groupContext="shape" transform="translate(230.508,-210.211)">
+ <title>Sheet.63</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape64-178" v:mID="64" v:groupContext="shape" transform="translate(230.508,-210.211)">
+ <title>Sheet.64</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape65-181" v:mID="65" v:groupContext="shape" transform="translate(233.39,-211.085)">
+ <title>Sheet.65</title>
+ <desc>6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>6</text> </g>
+ <g id="shape66-185" v:mID="66" v:groupContext="shape" transform="translate(230.508,-193.794)">
+ <title>Sheet.66</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st4"/>
+ </g>
+ <g id="shape67-187" v:mID="67" v:groupContext="shape" transform="translate(230.508,-193.794)">
+ <title>Sheet.67</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape68-190" v:mID="68" v:groupContext="shape" transform="translate(233.39,-194.681)">
+ <title>Sheet.68</title>
+ <desc>7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>7</text> </g>
+ <g id="shape69-194" v:mID="69" v:groupContext="shape" transform="translate(263.764,-194.681)">
+ <title>Sheet.69</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape70-198" v:mID="70" v:groupContext="shape" transform="translate(230.508,-177.258)">
+ <title>Sheet.70</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape71-200" v:mID="71" v:groupContext="shape" transform="translate(230.508,-177.258)">
+ <title>Sheet.71</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape72-203" v:mID="72" v:groupContext="shape" transform="translate(233.39,-178.117)">
+ <title>Sheet.72</title>
+ <desc>8</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>8</text> </g>
+ <g id="shape73-207" v:mID="73" v:groupContext="shape" transform="translate(230.508,-160.602)">
+ <title>Sheet.73</title>
+ <path d="M0 363.15 L0 379.8 L41.18 379.8 L41.18 363.15 L0 363.15 L0 363.15 Z" class="st7"/>
+ </g>
+ <g id="shape74-209" v:mID="74" v:groupContext="shape" transform="translate(230.508,-160.602)">
+ <title>Sheet.74</title>
+ <path d="M0 363.15 L41.18 363.15 L41.18 379.8 L0 379.8 L0 363.15" class="st5"/>
+ </g>
+ <g id="shape75-212" v:mID="75" v:groupContext="shape" transform="translate(233.39,-161.505)">
+ <title>Sheet.75</title>
+ <desc>9</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>9</text> </g>
+ <g id="shape76-216" v:mID="76" v:groupContext="shape" transform="translate(230.508,-143.946)">
+ <title>Sheet.76</title>
+ <path d="M0 363.15 L0 379.8 L41.18 379.8 L41.18 363.15 L0 363.15 L0 363.15 Z" class="st4"/>
+ </g>
+ <g id="shape77-218" v:mID="77" v:groupContext="shape" transform="translate(230.508,-143.946)">
+ <title>Sheet.77</title>
+ <path d="M0 363.15 L41.18 363.15 L41.18 379.8 L0 379.8 L0 363.15" class="st5"/>
+ </g>
+ <g id="shape78-221" v:mID="78" v:groupContext="shape" transform="translate(233.39,-144.841)">
+ <title>Sheet.78</title>
+ <desc>10</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>10</text> </g>
+ <g id="shape79-225" v:mID="79" v:groupContext="shape" transform="translate(263.764,-144.841)">
+ <title>Sheet.79</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape80-229" v:mID="80" v:groupContext="shape" transform="translate(230.508,-127.529)">
+ <title>Sheet.80</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape81-231" v:mID="81" v:groupContext="shape" transform="translate(230.508,-127.529)">
+ <title>Sheet.81</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape82-234" v:mID="82" v:groupContext="shape" transform="translate(233.39,-128.329)">
+ <title>Sheet.82</title>
+ <desc>11</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>11</text> </g>
+ <g id="shape83-238" v:mID="83" v:groupContext="shape" transform="translate(230.508,-110.754)">
+ <title>Sheet.83</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape84-240" v:mID="84" v:groupContext="shape" transform="translate(230.508,-110.754)">
+ <title>Sheet.84</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape85-243" v:mID="85" v:groupContext="shape" transform="translate(233.39,-111.64)">
+ <title>Sheet.85</title>
+ <desc>12</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>12</text> </g>
+ <g id="shape86-247" v:mID="86" v:groupContext="shape" transform="translate(230.508,-94.9362)">
+ <title>Sheet.86</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape87-249" v:mID="87" v:groupContext="shape" transform="translate(230.508,-94.9362)">
+ <title>Sheet.87</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape88-252" v:mID="88" v:groupContext="shape" transform="translate(233.39,-95.7375)">
+ <title>Sheet.88</title>
+ <desc>…</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.30974" cy="373.835" width="14.62" height="11.9384"/>
+ <path d="M14.62 367.87 L0 367.87 L0 379.8 L14.62 379.8 L14.62 367.87" class="st2"/>
+ <text x="2.34" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>…</text> </g>
+ <g id="shape89-256" v:mID="89" v:groupContext="shape" transform="translate(230.508,-78.999)">
+ <title>Sheet.89</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape90-258" v:mID="90" v:groupContext="shape" transform="translate(230.508,-78.999)">
+ <title>Sheet.90</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape91-261" v:mID="91" v:groupContext="shape" transform="translate(233.39,-79.8525)">
+ <title>Sheet.91</title>
+ <desc>255</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1326" cy="373.835" width="22.27" height="11.9384"/>
+ <path d="M22.27 367.87 L0 367.87 L0 379.8 L22.27 379.8 L22.27 367.87" class="st2"/>
+ <text x="2.84" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>255</text> </g>
+ <g id="shape92-265" v:mID="92" v:groupContext="shape" transform="translate(276.219,-250.503)">
+ <title>Sheet.92</title>
+ <path d="M0.33 311.98 L396.81 375.94 L396.48 377.95 L0 313.99 L0.33 311.98 L0.33 311.98 ZM396.12 373.75 L401.68 377.74
+ L395.16 379.8 L396.12 373.75 L396.12 373.75 Z" class="st15"/>
+ </g>
+ <g id="shape93-267" v:mID="93" v:groupContext="shape" transform="translate(275.859,-178.426)">
+ <title>Sheet.93</title>
+ <path d="M0.57 305.72 L230.93 376.21 L230.33 378.16 L0 307.67 L0.57 305.72 L0.57 305.72 ZM230.57 373.96 L235.52 378.67
+ L228.77 379.8 L230.57 373.96 L230.57 373.96 Z" class="st15"/>
+ </g>
+ <g id="shape94-269" v:mID="94" v:groupContext="shape" transform="translate(276.279,-151.285)">
+ <title>Sheet.94</title>
+ <path d="M0.21 379.8 L230.12 353.17 L229.88 351.14 L0 377.8 L0.21 379.8 L0.21 379.8 ZM229.34 355.3 L235.07 351.55 L228.65
+ 349.25 L229.34 355.3 L229.34 355.3 Z" class="st15"/>
+ </g>
+ <g id="shape95-271" v:mID="95" v:groupContext="shape" transform="translate(276.009,-232.679)">
+ <title>Sheet.95</title>
+ <path d="M0.27 327.47 L354.22 375.91 L353.95 377.92 L0 329.48 L0.27 327.47 L0.27 327.47 ZM353.5 373.75 L359.15 377.62
+ L352.66 379.8 L353.5 373.75 L353.5 373.75 Z" class="st10"/>
+ </g>
+ <g id="shape96-273" v:mID="96" v:groupContext="shape" transform="translate(276.279,-201.134)">
+ <title>Sheet.96</title>
+ <path d="M0.21 379.8 L353.86 348.14 L353.68 346.1 L0 377.77 L0.21 379.8 L0.21 379.8 ZM353.05 350.24 L358.88 346.64 L352.48
+ 344.16 L353.05 350.24 L353.05 350.24 Z" class="st15"/>
+ </g>
+ <g id="shape97-275" v:mID="97" v:groupContext="shape" transform="translate(346.482,-41.2531)">
+ <title>Sheet.97</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st7"/>
+ </g>
+ <g id="shape98-277" v:mID="98" v:groupContext="shape" transform="translate(346.482,-41.2531)">
+ <title>Sheet.98</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape99-280" v:mID="99" v:groupContext="shape" transform="translate(349.371,-91.6514)">
+ <title>Sheet.99</title>
+ <desc>…</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.30974" cy="373.835" width="14.62" height="11.9384"/>
+ <path d="M14.62 367.87 L0 367.87 L0 379.8 L14.62 379.8 L14.62 367.87" class="st2"/>
+ <text x="2.34" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>…</text> </g>
+ <g id="shape100-284" v:mID="100" v:groupContext="shape" transform="translate(470.019,-94.337)">
+ <title>Sheet.100</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st7"/>
+ </g>
+ <g id="shape101-286" v:mID="101" v:groupContext="shape" transform="translate(470.019,-94.337)">
+ <title>Sheet.101</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape102-289" v:mID="102" v:groupContext="shape" transform="translate(472.925,-144.778)">
+ <title>Sheet.102</title>
+ <desc>5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>5</text> </g>
+ <g id="shape103-293" v:mID="103" v:groupContext="shape" transform="translate(511.558,-113.749)">
+ <title>Sheet.103</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape104-295" v:mID="104" v:groupContext="shape" transform="translate(511.558,-113.749)">
+ <title>Sheet.104</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape105-298" v:mID="105" v:groupContext="shape" transform="translate(514.441,-164.138)">
+ <title>Sheet.105</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape106-302" v:mID="106" v:groupContext="shape" transform="translate(542.148,-164.138)">
+ <title>Sheet.106</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape107-306" v:mID="107" v:groupContext="shape" transform="translate(542.148,-152.155)">
+ <title>Sheet.107</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape108-310" v:mID="108" v:groupContext="shape" transform="translate(536.626,-140.172)">
+ <title>Sheet.108</title>
+ <desc>10</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>10</text> </g>
+ <g id="shape109-314" v:mID="109" v:groupContext="shape" transform="translate(514.201,-114.441)">
+ <title>Sheet.109</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape110-318" v:mID="110" v:groupContext="shape" transform="translate(519.723,-114.441)">
+ <title>Sheet.110</title>
+ <desc>+4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="8.11122" cy="373.835" width="16.23" height="11.9384"/>
+ <path d="M16.22 367.87 L0 367.87 L0 379.8 L16.22 379.8 L16.22 367.87" class="st2"/>
+ <text x="2.44" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>+4</text> </g>
+ <g id="shape111-322" v:mID="111" v:groupContext="shape" transform="translate(552.257,-130.525)">
+ <title>Sheet.111</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st7"/>
+ </g>
+ <g id="shape112-324" v:mID="112" v:groupContext="shape" transform="translate(552.257,-130.525)">
+ <title>Sheet.112</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape113-327" v:mID="113" v:groupContext="shape" transform="translate(555.203,-180.952)">
+ <title>Sheet.113</title>
+ <desc>3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>3</text> </g>
+ <g id="shape114-331" v:mID="114" v:groupContext="shape" transform="translate(634.615,-169.11)">
+ <title>Sheet.114</title>
+ <path d="M0 313.9 L0 379.8 L41.18 379.8 L41.18 313.9 L0 313.9 L0 313.9 Z" class="st4"/>
+ </g>
+ <g id="shape115-333" v:mID="115" v:groupContext="shape" transform="translate(634.615,-169.11)">
+ <title>Sheet.115</title>
+ <path d="M0 313.9 L41.18 313.9 L41.18 379.8 L0 379.8 L0 313.9" class="st5"/>
+ </g>
+ <g id="shape116-336" v:mID="116" v:groupContext="shape" transform="translate(637.526,-219.595)">
+ <title>Sheet.116</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape117-340" v:mID="117" v:groupContext="shape" transform="translate(665.234,-219.595)">
+ <title>Sheet.117</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape118-344" v:mID="118" v:groupContext="shape" transform="translate(665.2,-225.489)">
+ <title>Sheet.118</title>
+ <path d="M0 379.32 L0 379.8 L5.52 379.8 L5.52 379.32 L0 379.32 L0 379.32 Z" class="st19"/>
+ </g>
+ <g id="shape119-346" v:mID="119" v:groupContext="shape" transform="translate(665.234,-207.612)">
+ <title>Sheet.119</title>
+ <desc>7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>7</text> </g>
+ <g id="shape120-350" v:mID="120" v:groupContext="shape" transform="translate(637.286,-169.898)">
+ <title>Sheet.120</title>
+ <desc>5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>5</text> </g>
+ <g id="shape121-354" v:mID="121" v:groupContext="shape" transform="translate(642.809,-169.898)">
+ <title>Sheet.121</title>
+ <desc>-</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="3.49545" cy="373.835" width="7" height="11.9384"/>
+ <path d="M6.99 367.87 L0 367.87 L0 379.8 L6.99 379.8 L6.99 367.87" class="st2"/>
+ <text x="1.84" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>-</text> </g>
+ <g id="shape122-358" v:mID="122" v:groupContext="shape" transform="translate(646.17,-169.898)">
+ <title>Sheet.122</title>
+ <desc>3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>3</text> </g>
+ <g id="shape123-362" v:mID="123" v:groupContext="shape" transform="translate(676.275,-186.725)">
+ <title>Sheet.123</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape124-364" v:mID="124" v:groupContext="shape" transform="translate(676.275,-186.725)">
+ <title>Sheet.124</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape125-367" v:mID="125" v:groupContext="shape" transform="translate(679.141,-237.17)">
+ <title>Sheet.125</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape126-371" v:mID="126" v:groupContext="shape" transform="translate(706.849,-237.17)">
+ <title>Sheet.126</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape127-375" v:mID="127" v:groupContext="shape" transform="translate(678.901,-187.474)">
+ <title>Sheet.127</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape128-379" v:mID="128" v:groupContext="shape" transform="translate(304.943,-21.841)">
+ <title>Sheet.128</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape129-381" v:mID="129" v:groupContext="shape" transform="translate(304.943,-21.841)">
+ <title>Sheet.129</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape130-384" v:mID="130" v:groupContext="shape" transform="translate(307.855,-72.2917)">
+ <title>Sheet.130</title>
+ <desc>64</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>64</text> </g>
+ <g id="shape131-388" v:mID="131" v:groupContext="shape" transform="translate(330.041,-72.2917)">
+ <title>Sheet.131</title>
+ <desc>96</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>96</text> </g>
+ <g id="shape132-392" v:mID="132" v:groupContext="shape" transform="translate(307.616,-22.5952)">
+ <title>Sheet.132</title>
+ <desc>7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>7</text> </g>
+ <g id="shape133-396" v:mID="133" v:groupContext="shape" transform="translate(428.72,-77.4413)">
+ <title>Sheet.133</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape134-398" v:mID="134" v:groupContext="shape" transform="translate(428.72,-77.4413)">
+ <title>Sheet.134</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape135-401" v:mID="135" v:groupContext="shape" transform="translate(431.648,-127.825)">
+ <title>Sheet.135</title>
+ <desc>6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>6</text> </g>
+ <g id="shape136-405" v:mID="136" v:groupContext="shape" transform="translate(453.834,-127.825)">
+ <title>Sheet.136</title>
+ <desc>98</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>98</text> </g>
+ <g id="shape137-409" v:mID="137" v:groupContext="shape" transform="translate(431.409,-78.1289)">
+ <title>Sheet.137</title>
+ <desc>5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>5</text> </g>
+ <g id="shape138-413" v:mID="138" v:groupContext="shape" transform="translate(593.796,-149.818)">
+ <title>Sheet.138</title>
+ <path d="M0 313.9 L0 379.8 L41.18 379.8 L41.18 313.9 L0 313.9 L0 313.9 Z" class="st4"/>
+ </g>
+ <g id="shape139-415" v:mID="139" v:groupContext="shape" transform="translate(593.796,-149.818)">
+ <title>Sheet.139</title>
+ <path d="M0 313.9 L41.18 313.9 L41.18 379.8 L0 379.8 L0 313.9" class="st5"/>
+ </g>
+ <g id="shape140-418" v:mID="140" v:groupContext="shape" transform="translate(596.718,-200.312)">
+ <title>Sheet.140</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape141-422" v:mID="141" v:groupContext="shape" transform="translate(618.904,-200.312)">
+ <title>Sheet.141</title>
+ <desc>99</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>99</text> </g>
+ <g id="shape142-426" v:mID="142" v:groupContext="shape" transform="translate(596.478,-150.615)">
+ <title>Sheet.142</title>
+ <desc>9</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>9</text> </g>
+ <g id="shape143-430" v:mID="143" v:groupContext="shape" transform="translate(387.181,-58.0291)">
+ <title>Sheet.143</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape144-432" v:mID="144" v:groupContext="shape" transform="translate(387.181,-58.0291)">
+ <title>Sheet.144</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape145-435" v:mID="145" v:groupContext="shape" transform="translate(390.133,-108.466)">
+ <title>Sheet.145</title>
+ <desc>7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>7</text> </g>
+ <g id="shape146-439" v:mID="146" v:groupContext="shape" transform="translate(412.318,-108.466)">
+ <title>Sheet.146</title>
+ <desc>97</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>97</text> </g>
+ <g id="shape147-443" v:mID="147" v:groupContext="shape" transform="translate(389.893,-58.7692)">
+ <title>Sheet.147</title>
+ <desc>6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>6</text> </g>
+ <g id="shape148-447" v:mID="148" v:groupContext="shape" transform="translate(31.8163,-277.674)">
+ <title>Sheet.148</title>
+ <path d="M0 362.83 C0 360.95 1.52 359.43 3.41 359.43 L68.51 359.43 C70.4 359.43 71.91 360.95 71.91 362.83 L71.91 376.41
+ C71.91 378.28 70.4 379.8 68.51 379.8 L3.41 379.8 C1.52 379.8 0 378.28 0 376.41 L0 362.83 Z" class="st4"/>
+ </g>
+ <g id="shape149-449" v:mID="149" v:groupContext="shape" transform="translate(31.8163,-277.674)">
+ <title>Sheet.149</title>
+ <path d="M0 362.83 C0 360.95 1.52 359.43 3.41 359.43 L68.51 359.43 C70.4 359.43 71.91 360.95 71.91 362.83 L71.91 376.41
+ C71.91 378.28 70.4 379.8 68.51 379.8 L3.41 379.8 C1.52 379.8 0 378.28 0 376.41 L0 362.83 Z" class="st21"/>
+ </g>
+ <g id="shape150-451" v:mID="150" v:groupContext="shape" transform="translate(36,-278.851)">
+ <title>Sheet.150</title>
+ <desc>Insert key</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="35.613" cy="372.612" width="71.23" height="14.3829"/>
+ <path d="M71.23 365.42 L0 365.42 L0 379.8 L71.23 379.8 L71.23 365.42" class="st2"/>
+ <text x="9.64" y="376.21" class="st22" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Insert key </text> </g>
+ <g id="shape151-455" v:mID="151" v:groupContext="shape" transform="translate(47.7236,-257.004)">
+ <title>Sheet.151</title>
+ <path d="M0 369.44 L9.81 369.44 L9.81 359.07 L29.44 359.07 L29.44 369.44 L39.26 369.44 L19.63 379.8 L0 369.44 Z"
+ class="st23"/>
+ </g>
+ <g id="shape152-457" v:mID="152" v:groupContext="shape" transform="translate(31.8163,-236.094)">
+ <title>Sheet.152</title>
+ <path d="M0 362.73 C0 360.85 1.54 359.31 3.42 359.31 L68.49 359.31 C70.38 359.31 71.91 360.85 71.91 362.73 L71.91 376.39
+ C71.91 378.28 70.38 379.8 68.49 379.8 L3.42 379.8 C1.54 379.8 0 378.28 0 376.39 L0 362.73 Z" class="st4"/>
+ </g>
+ <g id="shape153-459" v:mID="153" v:groupContext="shape" transform="translate(31.8163,-236.094)">
+ <title>Sheet.153</title>
+ <path d="M0 362.73 C0 360.85 1.54 359.31 3.42 359.31 L68.49 359.31 C70.38 359.31 71.91 360.85 71.91 362.73 L71.91 376.39
+ C71.91 378.28 70.38 379.8 68.49 379.8 L3.42 379.8 C1.54 379.8 0 378.28 0 376.39 L0 362.73 Z" class="st21"/>
+ </g>
+ <g id="shape154-461" v:mID="154" v:groupContext="shape" transform="translate(54.6845,-237.332)">
+ <title>Sheet.154</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.8573" cy="372.612" width="33.72" height="14.3829"/>
+ <path d="M33.71 365.42 L0 365.42 L0 379.8 L33.71 379.8 L33.71 365.42" class="st2"/>
+ <text x="3.86" y="376.21" class="st22" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape155-465" v:mID="155" v:groupContext="shape" transform="translate(23.0522,-195.232)">
+ <title>Sheet.155</title>
+ <path d="M0 363.53 C0 361.73 1.46 360.27 3.26 360.27 L87.15 360.27 C88.95 360.27 90.4 361.73 90.4 363.53 L90.4 376.55
+ C90.4 378.35 88.95 379.8 87.15 379.8 L3.26 379.8 C1.46 379.8 0 378.35 0 376.55 L0 363.53 Z" class="st4"/>
+ </g>
+ <g id="shape156-467" v:mID="156" v:groupContext="shape" transform="translate(23.0522,-195.232)">
+ <title>Sheet.156</title>
+ <path d="M0 363.53 C0 361.73 1.46 360.27 3.26 360.27 L87.15 360.27 C88.95 360.27 90.4 361.73 90.4 363.53 L90.4 376.55
+ C90.4 378.35 88.95 379.8 87.15 379.8 L3.26 379.8 C1.46 379.8 0 378.35 0 376.55 L0 363.53 Z" class="st21"/>
+ </g>
+ <g id="shape157-469" v:mID="157" v:groupContext="shape" transform="translate(27,-196.017)">
+ <title>Sheet.157</title>
+ <desc>0x0102ABCD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.6644" cy="372.612" width="87.33" height="14.3829"/>
+ <path d="M87.33 365.42 L0 365.42 L0 379.8 L87.33 379.8 L87.33 365.42" class="st2"/>
+ <text x="7.36" y="376.21" class="st22" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102ABCD</text> </g>
+ <g id="shape158-473" v:mID="158" v:groupContext="shape" transform="translate(47.7236,-214.824)">
+ <title>Sheet.158</title>
+ <path d="M0 369.5 L9.81 369.5 L9.81 359.19 L29.44 359.19 L29.44 369.5 L39.26 369.5 L19.63 379.8 L0 369.5 Z"
+ class="st23"/>
+ </g>
+ <g id="shape159-475" v:mID="159" v:groupContext="shape" transform="translate(49.8539,-181.212)">
+ <title>Sheet.159</title>
+ <path d="M11.89 368.42 C11.89 371.57 11.47 374.11 10.94 374.11 L6.9 374.11 C6.37 374.11 5.94 376.67 5.94 379.8 C5.94
+ 376.67 5.52 374.11 5 374.11 L0.95 374.11 C0.43 374.11 0 371.57 0 368.42" class="st24"/>
+ </g>
+ <g id="shape160-478" v:mID="160" v:groupContext="shape" transform="translate(64.2606,-180.973)">
+ <title>Sheet.160</title>
+ <path d="M9.54 368.54 C9.54 371.66 9.21 374.17 8.79 374.17 L5.53 374.17 C5.11 374.17 4.77 376.7 4.77 379.8 C4.77 376.7
+ 4.43 374.17 4.02 374.17 L0.76 374.17 C0.34 374.17 0 371.66 0 368.54" class="st24"/>
+ </g>
+ <g id="shape161-481" v:mID="161" v:groupContext="shape" transform="translate(18.19,-60.9649)">
+ <title>Sheet.161</title>
+ <path d="M0 354.74 C0 351.97 2.25 349.73 5.03 349.73 L10.77 349.73 L30.27 267.14 L26.92 349.73 L59.58 349.73 C62.35 349.73
+ 64.59 351.97 64.59 354.74 L64.59 354.74 L64.59 362.26 L64.59 374.8 C64.59 377.57 62.35 379.8 59.58 379.8
+ L26.92 379.8 L10.77 379.8 L10.77 379.8 L5.03 379.8 C2.25 379.8 0 377.57 0 374.8 L0 362.26 L0 354.74 L0 354.74
+ Z" class="st23"/>
+ </g>
+ <g id="shape162-483" v:mID="162" v:groupContext="shape" transform="translate(28.141,-66.9569)">
+ <title>Sheet.162</title>
+ <desc>chunk id</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="27.5794" cy="372.612" width="55.16" height="14.3829"/>
+ <path d="M55.16 365.42 L0 365.42 L0 379.8 L55.16 379.8 L55.16 365.42" class="st2"/>
+ <text x="5.26" y="376.21" class="st25" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>chunk id</text> </g>
+ <g id="shape163-487" v:mID="163" v:groupContext="shape" transform="translate(50.8451,-112.132)">
+ <title>Sheet.163</title>
+ <path d="M0 354.64 C0 351.87 2.27 349.61 5.04 349.61 L10.74 349.61 L16.27 313.66 L26.86 349.61 L59.43 349.61 C62.22 349.61
+ 64.47 351.87 64.47 354.64 L64.47 354.64 L64.47 362.19 L64.47 374.77 C64.47 377.56 62.22 379.8 59.43 379.8
+ L26.86 379.8 L10.74 379.8 L10.74 379.8 L5.04 379.8 C2.27 379.8 0 377.56 0 374.77 L0 362.19 L0 354.64 L0
+ 354.64 Z" class="st23"/>
+ </g>
+ <g id="shape164-489" v:mID="164" v:groupContext="shape" transform="translate(68.8168,-118.181)">
+ <title>Sheet.164</title>
+ <desc>bin id</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="18.3881" cy="372.612" width="36.78" height="14.3829"/>
+ <path d="M36.78 365.42 L0 365.42 L0 379.8 L36.78 379.8 L36.78 365.42" class="st2"/>
+ <text x="4.06" y="376.21" class="st25" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bin id</text> </g>
+ <g id="shape165-493" v:mID="165" v:groupContext="shape" transform="translate(113.454,-225.085)">
+ <title>Sheet.165</title>
+ <path d="M0.01 375.68 L13.23 375.73 L13.22 377.77 L0 377.72 L0.01 375.68 L0.01 375.68 ZM12.22 373.69 L18.33 376.76 L12.2
+ 379.8 L12.22 373.69 L12.22 373.69 Z" class="st26"/>
+ </g>
+ <g id="shape166-495" v:mID="166" v:groupContext="shape" transform="translate(200.975,-280.969)">
+ <title>Sheet.166</title>
+ <path d="M0 375.73 L20.11 375.73 L20.11 377.77 L0 377.77 L0 375.73 L0 375.73 ZM19.09 373.69 L25.21 376.75 L19.09 379.8
+ L19.09 373.69 L19.09 373.69 Z" class="st26"/>
+ </g>
+ <g id="shape167-497" v:mID="167" v:groupContext="shape" transform="translate(275.739,-179.745)">
+ <title>Sheet.167</title>
+ <path d="M0.81 274.59 L231.38 376.48 L230.54 378.37 L0 276.48 L0.81 274.59 L0.81 274.59 ZM231.26 374.2 L235.64 379.47
+ L228.8 379.8 L231.26 374.2 L231.26 374.2 Z" class="st27"/>
+ </g>
+ <g id="shape168-499" v:mID="168" v:groupContext="shape" transform="translate(521.823,-96.8834)">
+ <title>Sheet.168</title>
+ <path d="M127.17 309.02 L127.17 378.79 C127.17 379.35 126.72 379.8 126.15 379.8 L3.06 379.8 C2.52 379.8 2.04 379.35 2.04
+ 378.79 L2.04 369.59 L4.08 369.59 L4.08 378.79 L3.06 377.77 L126.15 377.77 L125.13 378.79 L125.13 309.02
+ L127.17 309.02 ZM0 370.61 L3.06 364.5 L6.12 370.61 L0 370.61 Z" class="st28"/>
+ </g>
+ <g id="shape169-501" v:mID="169" v:groupContext="shape" transform="translate(478.603,-39.7553)">
+ <title>Sheet.169</title>
+ <path d="M0 347.57 C0 344.01 2.91 341.1 6.48 341.1 L237.86 341.1 C241.43 341.1 244.31 344.01 244.31 347.57 L244.31 373.36
+ C244.31 376.93 241.43 379.8 237.86 379.8 L6.48 379.8 C2.91 379.8 0 376.93 0 373.36 L0 347.57 Z"
+ class="st23"/>
+ </g>
+ <g id="shape170-503" v:mID="170" v:groupContext="shape" transform="translate(487.717,-45.5378)">
+ <title>Sheet.170</title>
+ <desc>Move bin from group 1 to 4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="126.387" cy="369.018" width="252.78" height="21.5726"/>
+ <path d="M252.77 358.23 L0 358.23 L0 379.8 L252.77 379.8 L252.77 358.23" class="st2"/>
+ <text x="18.98" y="374.41" class="st29" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Move bin from group 1 to 4</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i2.svg b/doc/guides/prog_guide/img/efd_i2.svg
new file mode 100644
index 0000000..a5f43f9
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i2.svg
@@ -0,0 +1,280 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i2.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="2.85156in" height="2.98777in"
+ viewBox="0 0 205.313 215.12" xml:space="preserve" color-interpolation-filters="sRGB" class="st18">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#deebf6;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st5 {fill:#ff0000;stroke:#c7c8c8;stroke-width:0.25}
+ .st6 {fill:none;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st7 {fill:none;stroke:#0070c0;stroke-width:1.5}
+ .st8 {marker-end:url(#mrkr5-91);stroke:#0070c0;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st9 {fill:#0070c0;fill-opacity:1;stroke:#0070c0;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st10 {fill:none;stroke:none;stroke-width:0.25}
+ .st11 {fill:#ff0000;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st12 {font-size:1em}
+ .st13 {marker-end:url(#mrkr5-101);stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st14 {fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st15 {fill:#5b9bd5;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st16 {marker-end:url(#mrkr5-110);stroke:#41719c;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st17 {fill:#41719c;fill-opacity:1;stroke:#41719c;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st18 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-91" class="st9" v:arrowType="5" v:arrowSize="2" v:setback="4.45" refX="-4.45" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ <marker id="mrkr5-101" class="st14" v:arrowType="5" v:arrowSize="2" v:setback="6.16" refX="-6.16" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ <marker id="mrkr5-110" class="st17" v:arrowType="5" v:arrowSize="2" v:setback="6.16" refX="-6.16" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(24.4044,-42.7174)">
+ <title>Circle</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow2-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 138.62 A76.5 76.5 0 0 1 153 138.62 A76.5 76.5 0 1 1 0 138.62 Z" class="st2"/>
+ </g>
+ <path d="M0 138.62 A76.5 76.5 0 0 1 153 138.62 A76.5 76.5 0 1 1 0 138.62 Z" class="st3"/>
+ </g>
+ <g id="shape3-6" v:mID="3" v:groupContext="shape" transform="translate(24.4044,-144.53)">
+ <title>Circle.3</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow3-7" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape4-11" v:mID="4" v:groupContext="shape" transform="translate(21.0294,-102.342)">
+ <title>Circle.4</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow4-12" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape5-16" v:mID="5" v:groupContext="shape" transform="translate(69.4044,-183.342)">
+ <title>Circle.5</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow5-17" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape6-21" v:mID="6" v:groupContext="shape" transform="translate(117.217,-183.342)">
+ <title>Circle.6</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow6-22" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st5"/>
+ </g>
+ <g id="shape7-26" v:mID="7" v:groupContext="shape" transform="translate(171.217,-104.03)">
+ <title>Circle.7</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow7-27" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st5"/>
+ </g>
+ <g id="shape8-31" v:mID="8" v:groupContext="shape" transform="translate(109.904,-38.2174)">
+ <title>Circle.8</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow8-32" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st5"/>
+ </g>
+ <g id="shape9-36" v:mID="9" v:groupContext="shape" transform="translate(21.0294,-124.842)">
+ <title>Circle.9</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow9-37" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st5"/>
+ </g>
+ <g id="shape10-41" v:mID="10" v:groupContext="shape" transform="translate(147.029,-168.717)">
+ <title>Circle.10</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow10-42" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape11-46" v:mID="11" v:groupContext="shape" transform="translate(138.029,-48.3424)">
+ <title>Circle.11</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow11-47" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape12-51" v:mID="12" v:groupContext="shape" transform="translate(160.529,-74.2174)">
+ <title>Circle.12</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow12-52" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape13-56" v:mID="13" v:groupContext="shape" transform="translate(40.7169,-57.3424)">
+ <title>Circle.13</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow13-57" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape14-61" v:mID="14" v:groupContext="shape" transform="translate(42.4044,-168.717)">
+ <title>Circle.14</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow14-62" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape15-66" v:mID="15" v:groupContext="shape" transform="translate(66.0294,-42.7174)">
+ <title>Circle.15</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow15-67" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape16-71" v:mID="16" v:groupContext="shape" transform="translate(25.5294,-79.8424)">
+ <title>Circle.16</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow16-72" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape17-76" v:mID="17" v:groupContext="shape" transform="translate(165.029,-143.405)">
+ <title>Circle.17</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow17-77" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape18-81" v:mID="18" v:groupContext="shape" transform="translate(276.618,4.50201) rotate(45)">
+ <title>Ellipse</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow18-82" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,1.63935,1.1506)" class="st1">
+ <path d="M0 187.01 A14.7383 28.1086 0 1 1 29.48 187.01 A14.7383 28.1086 0 1 1 0 187.01 Z" class="st6"/>
+ </g>
+ <path d="M0 187.01 A14.7383 28.1086 0 1 1 29.48 187.01 A14.7383 28.1086 0 1 1 0 187.01 Z" class="st7"/>
+ </g>
+ <g id="shape19-86" v:mID="19" v:groupContext="shape" transform="translate(251.273,355.436) rotate(156.038)">
+ <title>Sheet.19</title>
+ <path d="M-0 215.12 A73.4538 31.2572 85.43 0 1 40.92 208.96 L41.1 209.27" class="st8"/>
+ </g>
+ <g id="shape20-92" v:mID="20" v:groupContext="shape" transform="translate(62.705,-78.7174)">
+ <title>Sheet.20</title>
+ <desc>Target Hashed Value</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="42.6994" cy="203.87" width="85.4" height="22.5"/>
+ <rect x="0" y="192.62" width="85.3987" height="22.5" class="st10"/>
+ <text x="6.73" y="200.27" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target Hashed <tspan
+ x="28.48" dy="1.2em" class="st12">Value</tspan></text> </g>
+ <g id="shape21-96" v:mID="21" v:groupContext="shape" transform="translate(314.101,88.728) rotate(75.9638)">
+ <title>Sheet.21</title>
+ <path d="M0 215.12 L16.92 215.12" class="st13"/>
+ </g>
+ <g id="shape23-102" v:mID="23" v:groupContext="shape" transform="translate(60.4044,-138.342)">
+ <title>Sheet.23</title>
+ <desc>Keys</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="24.75" cy="203.87" width="49.5" height="22.5"/>
+ <rect x="0" y="192.62" width="49.5" height="22.5" class="st10"/>
+ <text x="13.21" y="207.47" class="st15" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Keys</text> </g>
+ <g id="shape24-105" v:mID="24" v:groupContext="shape" transform="translate(-125.293,114.034) rotate(-104.574)">
+ <title>Sheet.24</title>
+ <path d="M0 215.12 L22.9 215.12" class="st16"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i3.svg b/doc/guides/prog_guide/img/efd_i3.svg
new file mode 100644
index 0000000..ae22903
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i3.svg
@@ -0,0 +1,634 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i3.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="6.56036in" height="5.44284in"
+ viewBox="0 0 472.346 391.884" xml:space="preserve" color-interpolation-filters="sRGB" class="st22">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false"/>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st5 {marker-end:url(#mrkr5-24);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st6 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st7 {fill:none;stroke:#2e75b5;stroke-width:1}
+ .st8 {fill:#5b9bd5;font-family:Calibri;font-size:1.00001em}
+ .st9 {font-size:1em}
+ .st10 {fill:none;stroke:none;stroke-width:1}
+ .st11 {fill:#feffff;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st12 {fill:#5b9bd5;fill-opacity:0.25;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.25}
+ .st13 {fill:#4f87bb;stroke:#40709c;stroke-width:0.75}
+ .st14 {fill:#feffff;font-family:Calibri;font-size:0.75em}
+ .st15 {fill:none;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st16 {fill:none;stroke:#2e75b5;stroke-width:2.25}
+ .st17 {stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st18 {fill:#305497;stroke:#2e75b5;stroke-width:1}
+ .st19 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:none}
+ .st20 {fill:#92d050;fill-opacity:0.3;stroke:none;stroke-width:0.25}
+ .st21 {fill:#feffff;font-family:Calibri;font-size:1.16666em}
+ .st22 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-24" class="st6" v:arrowType="5" v:arrowSize="2" v:setback="6.16" refX="-6.16" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <v:layer v:name="Connector" v:index="0"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(111.25,-354.482)">
+ <title>Rectangle</title>
+ <desc>Packet Header</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="42.75" cy="382.884" width="85.5" height="18"/>
+ <g id="shadow2-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="85.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="85.5" height="18" class="st3"/>
+ <text x="13.24" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Packet Header</text> </g>
+ <g id="shape3-7" v:mID="3" v:groupContext="shape" transform="translate(192.25,-354.482)">
+ <title>Rectangle.3</title>
+ <desc>Payload</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="382.884" width="108" height="18"/>
+ <g id="shadow3-8" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="108" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="108" height="18" class="st3"/>
+ <text x="37.95" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Payload</text> </g>
+ <g id="shape4-13" v:mID="4" v:groupContext="shape" transform="translate(136,-311.232)">
+ <title>Rectangle.4</title>
+ <desc>Flow Key</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="382.884" width="54" height="18"/>
+ <g id="shadow4-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="54" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="54" height="18" class="st3"/>
+ <text x="8.87" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Flow Key</text> </g>
+ <g id="shape5-19" v:mID="5" v:groupContext="shape" transform="translate(465.501,-160.057) rotate(59.7436)">
+ <title>Sheet.5</title>
+ <path d="M0 391.88 L25.1 391.88" class="st5"/>
+ </g>
+ <g id="shape8-25" v:mID="8" v:groupContext="shape" transform="translate(219.25,-320.169)">
+ <title>Sheet.8</title>
+ <desc>Fields of the packet are used to form a flow Key</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="377.822" width="135" height="28.125"/>
+ <rect x="0" y="363.759" width="135" height="28.125" class="st7"/>
+ <text x="10.7" y="374.22" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Fields of the packet are <tspan
+ x="9.67" dy="1.2em" class="st9">used to form a flow Key</tspan></text> </g>
+ <g id="group13-29" transform="translate(120.25,-266.897)" v:mID="13" v:groupContext="group">
+ <title>Sheet.13</title>
+ <g id="shape11-30" v:mID="11" v:groupContext="shape" transform="translate(85.5,751.143) rotate(180)">
+ <title>Trapezoid</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow11-31" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,-0.345598,-1.97279)" class="st1">
+ <path d="M0 391.88 L85.5 391.88 L60.19 359.26 L25.31 359.26 L0 391.88 Z" class="st2"/>
+ </g>
+ <path d="M0 391.88 L85.5 391.88 L60.19 359.26 L25.31 359.26 L0 391.88 Z" class="st3"/>
+ </g>
+ <g id="shape12-35" v:mID="12" v:groupContext="shape" transform="translate(13.5,-6.525)">
+ <title>Sheet.12</title>
+ <desc>H(..)</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="381.689" width="54" height="20.3906"/>
+ <rect x="0" y="371.494" width="54" height="20.3906" class="st10"/>
+ <text x="16.27" y="385.29" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H(..)</text> </g>
+ </g>
+ <g id="shape14-38" v:mID="14" v:groupContext="shape" transform="translate(-229.872,96.3648) rotate(-90.0429)">
+ <title>Simple Arrow</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ArrowType" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <g id="shadow14-39" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,-1.97305,0.344122)" class="st1">
+ <path d="M0 391.88 L10.18 387.38 L10.18 389.63 L16.71 389.63 L16.71 391.88 L16.71 394.13 L10.18 394.13 L10.18 396.38
+ L0 391.88 Z" class="st12"/>
+ </g>
+ <path d="M0 391.88 L10.18 387.38 L10.18 389.63 L16.71 389.63 L16.71 391.88 L16.71 394.13 L10.18 394.13 L10.18 396.38
+ L0 391.88 Z" class="st13"/>
+ </g>
+ <g id="shape15-43" v:mID="15" v:groupContext="shape" transform="translate(212.5,-271.46)">
+ <title>Sheet.15</title>
+ <desc>Hash function is used to create a flow table index</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="377.822" width="135" height="28.125"/>
+ <rect x="0" y="363.759" width="135" height="28.125" class="st7"/>
+ <text x="9.05" y="374.22" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Hash function is used to <tspan
+ x="7.39" dy="1.2em" class="st9">create a flow table index</tspan></text> </g>
+ <g id="shape58-47" v:mID="58" v:groupContext="shape" transform="translate(199,-221.397)">
+ <title>Rectangle.58</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow58-48" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape59-53" v:mID="59" v:groupContext="shape" transform="translate(232.75,-221.397)">
+ <title>Rectangle.59</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow59-54" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape60-59" v:mID="60" v:groupContext="shape" transform="translate(280,-221.397)">
+ <title>Rectangle.60</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow60-60" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape61-65" v:mID="61" v:groupContext="shape" transform="translate(313.75,-221.397)">
+ <title>Rectangle.61</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow61-66" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape62-71" v:mID="62" v:groupContext="shape" transform="translate(361,-221.397)">
+ <title>Rectangle.62</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow62-72" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape63-76" v:mID="63" v:groupContext="shape" transform="translate(394.75,-221.397)">
+ <title>Rectangle.63</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow63-77" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape64-81" v:mID="64" v:groupContext="shape" transform="translate(199,-198.897)">
+ <title>Rectangle.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow64-82" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape65-86" v:mID="65" v:groupContext="shape" transform="translate(232.75,-198.897)">
+ <title>Rectangle.65</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow65-87" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape66-91" v:mID="66" v:groupContext="shape" transform="translate(280,-198.897)">
+ <title>Rectangle.66</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow66-92" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape67-96" v:mID="67" v:groupContext="shape" transform="translate(313.75,-198.897)">
+ <title>Rectangle.67</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow67-97" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape68-101" v:mID="68" v:groupContext="shape" transform="translate(361,-198.897)">
+ <title>Rectangle.68</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow68-102" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape69-106" v:mID="69" v:groupContext="shape" transform="translate(394.75,-198.897)">
+ <title>Rectangle.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow69-107" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape70-111" v:mID="70" v:groupContext="shape" transform="translate(199,-162.897)">
+ <title>Rectangle.70</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow70-112" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape71-117" v:mID="71" v:groupContext="shape" transform="translate(232.75,-162.897)">
+ <title>Rectangle.71</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow71-118" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape72-123" v:mID="72" v:groupContext="shape" transform="translate(280,-162.897)">
+ <title>Rectangle.72</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow72-124" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape73-129" v:mID="73" v:groupContext="shape" transform="translate(313.75,-162.897)">
+ <title>Rectangle.73</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow73-130" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape74-135" v:mID="74" v:groupContext="shape" transform="translate(361,-162.897)">
+ <title>Rectangle.74</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow74-136" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape75-141" v:mID="75" v:groupContext="shape" transform="translate(394.75,-162.897)">
+ <title>Rectangle.75</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow75-142" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape76-147" v:mID="76" v:groupContext="shape" transform="translate(199,-126.397)">
+ <title>Rectangle.76</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow76-148" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape77-152" v:mID="77" v:groupContext="shape" transform="translate(232.75,-126.397)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow77-153" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape78-157" v:mID="78" v:groupContext="shape" transform="translate(280,-126.397)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow78-158" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape79-162" v:mID="79" v:groupContext="shape" transform="translate(313.75,-126.397)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow79-163" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape80-167" v:mID="80" v:groupContext="shape" transform="translate(361,-126.397)">
+ <title>Rectangle.80</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow80-168" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="385.58" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape81-173" v:mID="81" v:groupContext="shape" transform="translate(394.75,-126.397)">
+ <title>Rectangle.81</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow81-174" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="385.58" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape82-179" v:mID="82" v:groupContext="shape" transform="translate(196.75,-117.397)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow82-180" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="256.384" width="245.25" height="135.5" class="st15"/>
+ </g>
+ <rect x="0" y="256.384" width="245.25" height="135.5" class="st16"/>
+ </g>
+ <g id="shape83-184" v:mID="83" v:groupContext="shape" transform="translate(554.884,123.862) rotate(90)">
+ <title>Sheet.83</title>
+ <path d="M0 391.88 L99 391.88" class="st17"/>
+ </g>
+ <g id="shape84-187" v:mID="84" v:groupContext="shape" transform="translate(208,-248.397)">
+ <title>Sheet.84</title>
+ <desc>Load Balancing Flow Table</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="91.75" cy="386.259" width="183.5" height="11.25"/>
+ <rect x="0" y="380.634" width="183.5" height="11.25" class="st18"/>
+ <text x="26.14" y="389.86" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Load Balancing Flow Table</text> </g>
+ <g id="shape85-190" v:mID="85" v:groupContext="shape" transform="translate(190,-157.835)">
+ <title>Rectangle.85</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow85-191" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="363.759" width="261" height="28.125" class="st19"/>
+ </g>
+ <rect x="0" y="363.759" width="261" height="28.125" class="st20"/>
+ </g>
+ <g id="shape86-195" v:mID="86" v:groupContext="shape" transform="translate(163,-169.022)">
+ <title>Sheet.86</title>
+ <path d="M0 391.88 L18.76 391.88" class="st5"/>
+ </g>
+ <g id="shape87-200" v:mID="87" v:groupContext="shape" transform="translate(19,-198.107)">
+ <title>Sheet.87</title>
+ <desc>Hash value used to index Flow table</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="377.822" width="135" height="28.125"/>
+ <rect x="0" y="363.759" width="135" height="28.125" class="st7"/>
+ <text x="6.79" y="374.22" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Hash value used to index <tspan
+ x="42.16" dy="1.2em" class="st9">Flow table</tspan></text> </g>
+ <g id="shape88-204" v:mID="88" v:groupContext="shape" transform="translate(551.381,21.2928) rotate(87.9001)">
+ <title>Sheet.88</title>
+ <path d="M0 391.88 L20.86 391.88" class="st5"/>
+ </g>
+ <g id="shape89-209" v:mID="89" v:groupContext="shape" transform="translate(494.785,297.309) rotate(131.987)">
+ <title>Sheet.89</title>
+ <path d="M0 391.88 L30.84 391.88" class="st5"/>
+ </g>
+ <g id="shape90-214" v:mID="90" v:groupContext="shape" transform="translate(228.25,-92.5847)">
+ <title>Rectangle.90</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow90-215" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape91-220" v:mID="91" v:groupContext="shape" transform="translate(340.75,-92.5847)">
+ <title>Rectangle.91</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow91-221" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="group96-226" transform="translate(253,-51.4597)" v:mID="96" v:groupContext="group">
+ <title>Sheet.96</title>
+ <g id="shape97-227" v:mID="97" v:groupContext="shape" transform="translate(85.5,751.143) rotate(180)">
+ <title>Trapezoid</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow97-228" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,-0.345598,-1.97279)" class="st1">
+ <path d="M0 391.88 L85.5 391.88 L60.19 359.26 L25.31 359.26 L0 391.88 Z" class="st2"/>
+ </g>
+ <path d="M0 391.88 L85.5 391.88 L60.19 359.26 L25.31 359.26 L0 391.88 Z" class="st3"/>
+ </g>
+ <g id="shape98-232" v:mID="98" v:groupContext="shape" transform="translate(13.5,-6.525)">
+ <title>Sheet.98</title>
+ <desc>Match</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="381.689" width="54" height="20.3906"/>
+ <rect x="0" y="371.494" width="54" height="20.3906" class="st10"/>
+ <text x="10.98" y="385.29" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Match</text> </g>
+ </g>
+ <g id="shape99-235" v:mID="99" v:groupContext="shape" transform="translate(532.137,0.00916548) rotate(54.6508)">
+ <title>Sheet.99</title>
+ <path d="M0 391.88 L93.23 391.88" class="st5"/>
+ </g>
+ <g id="shape100-240" v:mID="100" v:groupContext="shape" transform="translate(683.134,224.487) rotate(90)">
+ <title>Sheet.100</title>
+ <path d="M0 391.88 L77.15 391.88" class="st5"/>
+ </g>
+ <g id="shape101-245" v:mID="101" v:groupContext="shape" transform="translate(692.213,476.024) rotate(129.078)">
+ <title>Sheet.101</title>
+ <path d="M0 391.88 L95.37 391.88" class="st5"/>
+ </g>
+ <g id="shape102-250" v:mID="102" v:groupContext="shape" transform="translate(293.5,-97.0847)">
+ <title>Rectangle.102</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow102-251" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape103-256" v:mID="103" v:groupContext="shape" transform="translate(169.75,-55.9597)">
+ <title>Rectangle.103</title>
+ <desc>Flow Key</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="382.884" width="54" height="18"/>
+ <g id="shadow103-257" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="54" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="54" height="18" class="st3"/>
+ <text x="8.87" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Flow Key</text> </g>
+ <g id="shape104-262" v:mID="104" v:groupContext="shape" transform="translate(226,-64.9597)">
+ <title>Sheet.104</title>
+ <path d="M0 391.88 L34.34 391.88" class="st5"/>
+ </g>
+ <g id="shape105-267" v:mID="105" v:groupContext="shape" transform="translate(54,-82.4597)">
+ <title>Sheet.105</title>
+ <desc>Retrieved keys are matched with input key</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="377.822" width="135" height="28.125"/>
+ <rect x="0" y="363.759" width="135" height="28.125" class="st7"/>
+ <text x="22.51" y="374.22" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Retrieved keys are <tspan
+ x="9.83" dy="1.2em" class="st9">matched with input key</tspan></text> </g>
+ <g id="shape106-271" v:mID="106" v:groupContext="shape" transform="translate(271,-23.9597)">
+ <title>Rectangle.106</title>
+ <desc>Action</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="382.884" width="54" height="18"/>
+ <g id="shadow106-272" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="54" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="54" height="18" class="st3"/>
+ <text x="8.67" y="387.08" class="st21" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action</text> </g>
+ <g id="shape111-277" v:mID="111" v:groupContext="shape" transform="translate(-94.8716,350.902) rotate(-90.0429)">
+ <title>Simple Arrow.111</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ArrowType" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <g id="shadow111-278" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,-1.97305,0.344122)" class="st1">
+ <path d="M0 391.88 L10.18 387.38 L10.18 389.63 L16.71 389.63 L16.71 391.88 L16.71 394.13 L10.18 394.13 L10.18 396.38
+ L0 391.88 Z" class="st12"/>
+ </g>
+ <path d="M0 391.88 L10.18 387.38 L10.18 389.63 L16.71 389.63 L16.71 391.88 L16.71 394.13 L10.18 394.13 L10.18 396.38
+ L0 391.88 Z" class="st13"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i4.svg b/doc/guides/prog_guide/img/efd_i4.svg
new file mode 100644
index 0000000..5be5ccd
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i4.svg
@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i4.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="2.78993in" height="1.78151in"
+ viewBox="0 0 200.875 128.269" xml:space="preserve" color-interpolation-filters="sRGB" class="st19">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:none;stroke:none;stroke-width:0.25}
+ .st2 {fill:#5b9bd5;font-family:Calibri;font-size:0.75em}
+ .st3 {font-size:1em}
+ .st4 {fill:#5b9bd5;font-family:Calibri;font-size:0.75em;font-weight:bold}
+ .st5 {fill:#deebf6;stroke:none;stroke-width:0.25}
+ .st6 {stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st7 {stroke:#5b9bd5;stroke-dasharray:0.75,1.5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st8 {fill:#ff0000;font-size:1em}
+ .st9 {baseline-shift:-28.8834%;font-size:0.577667em}
+ .st10 {fill:#ff0000;font-family:Calibri;font-size:0.75em}
+ .st11 {fill:#5b9bd5;font-size:1em}
+ .st12 {visibility:visible}
+ .st13 {fill:#5b9bd5;fill-opacity:0.25;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.25}
+ .st14 {fill:url(#grad0-73);stroke:#40709c;stroke-width:0.75}
+ .st15 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st16 {fill:#00fefe;font-size:1em}
+ .st17 {fill:#00b050}
+ .st18 {stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st19 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Patterns_And_Gradients">
+ <linearGradient id="grad0-73" x1="0" y1="0" x2="1" y2="0" gradientTransform="rotate(250 0.5 0.5)">
+ <stop offset="0" stop-color="#4f87bb" stop-opacity="1"/>
+ <stop offset="0.48" stop-color="#4f87bb" stop-opacity="1"/>
+ <stop offset="0.82" stop-color="#5b9bd5" stop-opacity="1"/>
+ </linearGradient>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(18.25,-59.3478)">
+ <title>Sheet.2</title>
+ <desc>Key 1 Key 2 ... Key 28</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="18" cy="121.519" width="36" height="13.5"/>
+ <rect x="0" y="114.769" width="36" height="13.5" class="st1"/>
+ <text x="8.09" y="108.02" class="st2" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1<v:newlineChar/><tspan
+ x="8.09" dy="1.2em" class="st3">Key </tspan>2<v:newlineChar/><tspan x="14.59" dy="1.2em" class="st3">...<v:newlineChar/></tspan><tspan
+ x="5.81" dy="1.2em" class="st3">Key </tspan>28</text> </g>
+ <g id="shape9-7" v:mID="9" v:groupContext="shape" transform="translate(52,-91.9728)">
+ <title>Sheet.9</title>
+ <desc>Target Value</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="17.4375" cy="122.644" width="34.88" height="11.25"/>
+ <rect x="0" y="117.019" width="34.875" height="11.25" class="st1"/>
+ <text x="5.43" y="119.94" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target <tspan x="6.77"
+ dy="1.2em" class="st3">Value</tspan></text> </g>
+ <g id="shape11-11" v:mID="11" v:groupContext="shape" transform="translate(52,-42.4728)">
+ <title>Sheet.11</title>
+ <desc>0 1 0</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="17.4375" cy="105.769" width="34.88" height="45"/>
+ <rect x="0" y="83.2689" width="34.875" height="45" class="st5"/>
+ <text x="15.16" y="92.27" class="st2" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0<v:newlineChar/><tspan
+ x="15.16" dy="1.2em" class="st3">1<v:newlineChar/><v:newlineChar/></tspan><tspan x="15.16" dy="2.4em"
+ class="st3">0</tspan></text> </g>
+ <g id="shape8-16" v:mID="8" v:groupContext="shape" transform="translate(180.269,21.6711) rotate(90)">
+ <title>Sheet.8</title>
+ <path d="M0 128.27 L69.75 128.27" class="st6"/>
+ </g>
+ <g id="shape10-19" v:mID="10" v:groupContext="shape" transform="translate(215.144,21.6711) rotate(90)">
+ <title>Sheet.10</title>
+ <path d="M0 128.27 L69.75 128.27" class="st6"/>
+ </g>
+ <g id="shape4-22" v:mID="4" v:groupContext="shape" transform="translate(22.75,-77.3478)">
+ <title>Sheet.4</title>
+ <path d="M0 128.27 L157.5 128.27" class="st7"/>
+ </g>
+ <g id="shape5-25" v:mID="5" v:groupContext="shape" transform="translate(23.875,-66.0978)">
+ <title>Sheet.5</title>
+ <path d="M0 128.27 L158.62 128.27" class="st7"/>
+ </g>
+ <g id="shape6-28" v:mID="6" v:groupContext="shape" transform="translate(22.75,-54.8478)">
+ <title>Sheet.6</title>
+ <path d="M0 128.27 L159.75 128.27" class="st7"/>
+ </g>
+ <g id="shape7-31" v:mID="7" v:groupContext="shape" transform="translate(22.75,-87.4728)">
+ <title>Sheet.7</title>
+ <path d="M0 128.27 L155.25 128.27" class="st6"/>
+ </g>
+ <g id="shape12-34" v:mID="12" v:groupContext="shape" transform="translate(91.9375,-42.4728)">
+ <title>Sheet.12</title>
+ <desc>0 0 0</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="8.4375" cy="105.769" width="16.88" height="45"/>
+ <rect x="0" y="83.2689" width="16.875" height="45" class="st1"/>
+ <text x="6.16" y="92.27" class="st2" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0<v:newlineChar/><tspan
+ x="6.16" dy="1.2em" class="st8">0<v:newlineChar/><v:newlineChar/></tspan><tspan x="6.16" dy="2.4em"
+ class="st3">0</tspan></text> </g>
+ <g id="shape26-39" v:mID="26" v:groupContext="shape" transform="translate(86.875,-88.5978)">
+ <title>Sheet.26</title>
+ <desc>H1(x)</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="14.0625" cy="122.644" width="28.13" height="11.25"/>
+ <rect x="0" y="117.019" width="28.125" height="11.25" class="st1"/>
+ <text x="5.03" y="125.34" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st9" v:baseFontSize="8">1</tspan><tspan dy="0.164em" class="st3">(</tspan>x)</text> </g>
+ <g id="shape27-44" v:mID="27" v:groupContext="shape" transform="translate(115,-42.4728)">
+ <title>Sheet.27</title>
+ <desc>1 1 0</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="8.4375" cy="105.769" width="16.88" height="45"/>
+ <rect x="0" y="83.2689" width="16.875" height="45" class="st1"/>
+ <text x="6.16" y="92.27" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1<v:newlineChar/><tspan
+ x="6.16" dy="1.2em" class="st11">1<v:newlineChar/><v:newlineChar/></tspan><tspan x="6.16" dy="2.4em"
+ class="st11">0</tspan></text> </g>
+ <g id="shape28-49" v:mID="28" v:groupContext="shape" transform="translate(109.938,-88.5978)">
+ <title>Sheet.28</title>
+ <desc>H2(x)</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="14.0625" cy="122.644" width="28.13" height="11.25"/>
+ <rect x="0" y="117.019" width="28.125" height="11.25" class="st1"/>
+ <text x="5.03" y="125.34" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st9" v:baseFontSize="8">2</tspan><tspan dy="0.164em" class="st3">(</tspan>x)</text> </g>
+ <g id="shape29-54" v:mID="29" v:groupContext="shape" transform="translate(155.5,-42.4728)">
+ <title>Sheet.29</title>
+ <desc>0 1 0</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="8.4375" cy="105.769" width="16.88" height="45"/>
+ <rect x="0" y="83.2689" width="16.875" height="45" class="st1"/>
+ <text x="6.16" y="92.27" class="st2" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0<v:newlineChar/><tspan
+ x="6.16" dy="1.2em" class="st3">1<v:newlineChar/><v:newlineChar/></tspan><tspan x="6.16" dy="2.4em"
+ class="st3">0</tspan></text> </g>
+ <g id="shape30-59" v:mID="30" v:groupContext="shape" transform="translate(150.438,-88.5978)">
+ <title>Sheet.30</title>
+ <desc>Hm(x)</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="14.0625" cy="122.644" width="28.13" height="11.25"/>
+ <rect x="0" y="117.019" width="28.125" height="11.25" class="st1"/>
+ <text x="4.24" y="125.34" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st9" v:baseFontSize="8">m</tspan><tspan dy="0.164em" class="st3">(</tspan>x)</text> </g>
+ <g id="shape31-64" v:mID="31" v:groupContext="shape" transform="translate(130.188,-89.7228)">
+ <title>Sheet.31</title>
+ <desc>…..</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="14.0625" cy="122.644" width="28.13" height="11.25"/>
+ <rect x="0" y="117.019" width="28.125" height="11.25" class="st1"/>
+ <text x="8.46" y="125.34" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>…..</text> </g>
+ <g id="shape32-67" v:mID="32" v:groupContext="shape" transform="translate(34,-23.3478)">
+ <title>Sheet.32</title>
+ <desc>Store m for this group of keys</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="66.375" cy="122.644" width="132.75" height="11.25"/>
+ <g id="shadow32-68" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st12">
+ <rect x="0" y="117.019" width="132.75" height="11.25" class="st13"/>
+ </g>
+ <rect x="0" y="117.019" width="132.75" height="11.25" class="st14"/>
+ <text x="6.32" y="125.64" class="st15" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Store <tspan
+ class="st16">m</tspan> for this group of keys</text> </g>
+ <g id="shape36-76" v:mID="36" v:groupContext="shape" transform="translate(159.381,-100.964)">
+ <title>Sheet.36</title>
+ <path d="M3.45 125.81 L6.87 119.34 L7.99 120.16 L3.87 128.27 L0 124.35 L0.86 123.13 L3.45 125.81 Z" class="st17"/>
+ </g>
+ <g id="group44-79" transform="translate(97.5625,-100.086)" v:mID="44" v:groupContext="group">
+ <title>Sheet.44</title>
+ <g id="shape42-80" v:mID="42" v:groupContext="shape" transform="translate(85.4972,28.6255) rotate(41.8011)">
+ <title>Sheet.42</title>
+ <path d="M0 128.27 L6.04 128.27" class="st18"/>
+ </g>
+ <g id="shape43-83" v:mID="43" v:groupContext="shape" transform="translate(-87.9035,34.8564) rotate(-43.2597)">
+ <title>Sheet.43</title>
+ <path d="M0 128.27 L5.87 128.27" class="st18"/>
+ </g>
+ </g>
+ <g id="group45-86" transform="translate(120.625,-100.086)" v:mID="45" v:groupContext="group">
+ <title>Sheet.45</title>
+ <g id="shape46-87" v:mID="46" v:groupContext="shape" transform="translate(85.4972,28.6255) rotate(41.8011)">
+ <title>Sheet.46</title>
+ <path d="M0 128.27 L6.04 128.27" class="st18"/>
+ </g>
+ <g id="shape47-90" v:mID="47" v:groupContext="shape" transform="translate(-87.9035,34.8564) rotate(-43.2597)">
+ <title>Sheet.47</title>
+ <path d="M0 128.27 L5.87 128.27" class="st18"/>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i5.svg b/doc/guides/prog_guide/img/efd_i5.svg
new file mode 100644
index 0000000..b6540ba
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i5.svg
@@ -0,0 +1,183 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i5.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="8.34375in" height="2.86443in"
+ viewBox="0 0 600.75 206.239" xml:space="preserve" color-interpolation-filters="sRGB" class="st14">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:1.5em}
+ .st5 {fill:#feffff;font-family:Calibri;font-size:1.16666em}
+ .st6 {marker-end:url(#mrkr5-36);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st7 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st8 {stroke:#5b9bd5;stroke-dasharray:1.5,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st9 {fill:none;stroke:none;stroke-width:0.25}
+ .st10 {fill:#5b9bd5;font-family:Calibri;font-size:1.5em;font-weight:bold}
+ .st11 {baseline-shift:-32.4951%;font-size:0.649902em}
+ .st12 {fill:#deebf6;stroke:#0070c0;stroke-width:1}
+ .st13 {fill:#5b9bd5;font-family:Calibri;font-size:1.5em}
+ .st14 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-36" class="st7" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(93.0294,-158.5)">
+ <title>Rectangle</title>
+ <desc>All Keys</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="216" cy="192.739" width="432" height="27"/>
+ <g id="shadow2-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="179.239" width="432" height="27" class="st2"/>
+ </g>
+ <rect x="0" y="179.239" width="432" height="27" class="st3"/>
+ <text x="187.88" y="198.14" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>All Keys</text> </g>
+ <g id="shape3-7" v:mID="3" v:groupContext="shape" transform="translate(21.0294,-77.5)">
+ <title>Rectangle.3</title>
+ <desc>Group 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="188.239" width="108" height="36"/>
+ <g id="shadow3-8" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="170.239" width="108" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="170.239" width="108" height="36" class="st3"/>
+ <text x="30.97" y="192.44" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group 1</text> </g>
+ <g id="shape4-13" v:mID="4" v:groupContext="shape" transform="translate(156.029,-77.5)">
+ <title>Rectangle.4</title>
+ <desc>Group 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="188.239" width="108" height="36"/>
+ <g id="shadow4-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="170.239" width="108" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="170.239" width="108" height="36" class="st3"/>
+ <text x="30.97" y="192.44" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group 2</text> </g>
+ <g id="shape5-19" v:mID="5" v:groupContext="shape" transform="translate(291.029,-77.5)">
+ <title>Rectangle.5</title>
+ <desc>Group 3</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="188.239" width="108" height="36"/>
+ <g id="shadow5-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="170.239" width="108" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="170.239" width="108" height="36" class="st3"/>
+ <text x="30.97" y="192.44" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group 3</text> </g>
+ <g id="shape6-25" v:mID="6" v:groupContext="shape" transform="translate(471.029,-77.5)">
+ <title>Rectangle.6</title>
+ <desc>Group X</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="188.239" width="108" height="36"/>
+ <g id="shadow6-26" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="170.239" width="108" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="170.239" width="108" height="36" class="st3"/>
+ <text x="30.88" y="192.44" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group X</text> </g>
+ <g id="shape7-31" v:mID="7" v:groupContext="shape" transform="translate(359.05,247.819) rotate(165.964)">
+ <title>Sheet.7</title>
+ <path d="M0 206.24 L178.5 206.24" class="st6"/>
+ </g>
+ <g id="shape8-37" v:mID="8" v:groupContext="shape" transform="translate(428.903,215.562) rotate(144.462)">
+ <title>Sheet.8</title>
+ <path d="M0 206.24 L70.39 206.24" class="st6"/>
+ </g>
+ <g id="shape9-42" v:mID="9" v:groupContext="shape" transform="translate(470.075,-81.0976) rotate(51.3402)">
+ <title>Sheet.9</title>
+ <path d="M0 206.24 L50.59 206.24" class="st6"/>
+ </g>
+ <g id="shape10-47" v:mID="10" v:groupContext="shape" transform="translate(364.228,-150.976) rotate(15.5241)">
+ <title>Sheet.10</title>
+ <path d="M0 206.24 L161.1 206.24" class="st6"/>
+ </g>
+ <g id="shape11-52" v:mID="11" v:groupContext="shape" transform="translate(408.029,-95.5)">
+ <title>Sheet.11</title>
+ <path d="M0 206.24 L45 206.24" class="st8"/>
+ </g>
+ <g id="shape12-55" v:mID="12" v:groupContext="shape" transform="translate(48.0294,-50.5)">
+ <title>Sheet.12</title>
+ <desc>H7</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="192.739" width="45" height="27"/>
+ <rect x="0" y="179.239" width="45" height="27" class="st9"/>
+ <text x="13.86" y="198.14" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan
+ dy="-0.284em" class="st11" v:baseFontSize="18">7</tspan></text> </g>
+ <g id="shape13-59" v:mID="13" v:groupContext="shape" transform="translate(192.029,-50.5)">
+ <title>Sheet.13</title>
+ <desc>H267</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="192.739" width="45" height="27"/>
+ <rect x="0" y="179.239" width="45" height="27" class="st9"/>
+ <text x="7.93" y="198.14" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st11" v:baseFontSize="18">267</tspan></text> </g>
+ <g id="shape14-63" v:mID="14" v:groupContext="shape" transform="translate(318.029,-50.5)">
+ <title>Sheet.14</title>
+ <desc>H46</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="192.739" width="45" height="27"/>
+ <rect x="0" y="179.239" width="45" height="27" class="st9"/>
+ <text x="10.89" y="198.14" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan
+ dy="-0.284em" class="st11" v:baseFontSize="18">46</tspan></text> </g>
+ <g id="shape15-67" v:mID="15" v:groupContext="shape" transform="translate(502.529,-50.5)">
+ <title>Sheet.15</title>
+ <desc>H132</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="192.739" width="45" height="27"/>
+ <rect x="0" y="179.239" width="45" height="27" class="st9"/>
+ <text x="7.93" y="198.14" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st11" v:baseFontSize="18">132</tspan></text> </g>
+ <g id="shape16-71" v:mID="16" v:groupContext="shape" transform="translate(111.029,-19)">
+ <title>Sheet.16</title>
+ <desc>Store hash function index for each group of keys</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="189" cy="192.739" width="378" height="27"/>
+ <rect x="0" y="179.239" width="378" height="27" class="st12"/>
+ <text x="12.27" y="198.14" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Store hash function index for each group of keys</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i6.svg b/doc/guides/prog_guide/img/efd_i6.svg
new file mode 100644
index 0000000..9aee30b
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i6.svg
@@ -0,0 +1,1254 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i6.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="8.2496in" height="5.89673in"
+ viewBox="0 0 593.971 424.565" xml:space="preserve" color-interpolation-filters="sRGB" class="st27">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st5 {fill:#feffff;font-family:Calibri;font-size:0.75em}
+ .st6 {fill:none;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st7 {fill:none;stroke:#2e75b5;stroke-width:2.25}
+ .st8 {fill:#305497;stroke:#2e75b5;stroke-width:1}
+ .st9 {fill:#feffff;font-family:Calibri;font-size:0.833336em;font-weight:bold}
+ .st10 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2)}
+ .st11 {fill:#5b9bd5}
+ .st12 {stroke:#c7c8c8;stroke-width:0.25}
+ .st13 {fill:#acccea;stroke:#c7c8c8;stroke-width:0.25}
+ .st14 {fill:#feffff;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st15 {fill:#ed7d31;stroke:#c7c8c8;stroke-width:0.25}
+ .st16 {fill:#deebf6;stroke:#c7c8c8;stroke-width:0.25}
+ .st17 {marker-end:url(#mrkr5-212);stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st18 {fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st19 {fill:none;stroke:#2e75b5;stroke-width:1}
+ .st20 {fill:#5b9bd5;font-family:Calibri;font-size:1.00001em}
+ .st21 {fill:none;stroke:none;stroke-width:0.25}
+ .st22 {font-size:1em}
+ .st23 {fill:#ffffff}
+ .st24 {stroke:#5b9bd5;stroke-dasharray:1.5,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st25 {marker-end:url(#mrkr5-444);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st26 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st27 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-212" class="st18" v:arrowType="5" v:arrowSize="2" v:setback="5.8" refX="-5.8" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ <marker id="mrkr5-444" class="st26" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(319.501,-335.688)">
+ <title>Rectangle.58</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow3-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape4-7" v:mID="4" v:groupContext="shape" transform="translate(353.251,-335.688)">
+ <title>Rectangle.59</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow4-8" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape5-13" v:mID="5" v:groupContext="shape" transform="translate(400.501,-335.688)">
+ <title>Rectangle.60</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow5-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape6-19" v:mID="6" v:groupContext="shape" transform="translate(434.251,-335.688)">
+ <title>Rectangle.61</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow6-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape7-25" v:mID="7" v:groupContext="shape" transform="translate(481.501,-335.688)">
+ <title>Rectangle.62</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow7-26" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape8-30" v:mID="8" v:groupContext="shape" transform="translate(515.251,-335.688)">
+ <title>Rectangle.63</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow8-31" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape9-35" v:mID="9" v:groupContext="shape" transform="translate(319.501,-313.188)">
+ <title>Rectangle.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow9-36" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape10-40" v:mID="10" v:groupContext="shape" transform="translate(353.251,-313.188)">
+ <title>Rectangle.65</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow10-41" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape11-45" v:mID="11" v:groupContext="shape" transform="translate(400.501,-313.188)">
+ <title>Rectangle.66</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow11-46" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape12-50" v:mID="12" v:groupContext="shape" transform="translate(434.251,-313.188)">
+ <title>Rectangle.67</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow12-51" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape13-55" v:mID="13" v:groupContext="shape" transform="translate(481.501,-313.188)">
+ <title>Rectangle.68</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow13-56" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape14-60" v:mID="14" v:groupContext="shape" transform="translate(515.251,-313.188)">
+ <title>Rectangle.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow14-61" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape15-65" v:mID="15" v:groupContext="shape" transform="translate(319.501,-277.188)">
+ <title>Rectangle.70</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow15-66" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape16-71" v:mID="16" v:groupContext="shape" transform="translate(353.251,-277.188)">
+ <title>Rectangle.71</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow16-72" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape17-77" v:mID="17" v:groupContext="shape" transform="translate(400.501,-277.188)">
+ <title>Rectangle.72</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow17-78" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape18-83" v:mID="18" v:groupContext="shape" transform="translate(434.251,-277.188)">
+ <title>Rectangle.73</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow18-84" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape19-89" v:mID="19" v:groupContext="shape" transform="translate(481.501,-277.188)">
+ <title>Rectangle.74</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow19-90" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape20-95" v:mID="20" v:groupContext="shape" transform="translate(515.251,-277.188)">
+ <title>Rectangle.75</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow20-96" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape21-101" v:mID="21" v:groupContext="shape" transform="translate(319.501,-240.687)">
+ <title>Rectangle.76</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow21-102" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape22-106" v:mID="22" v:groupContext="shape" transform="translate(353.251,-240.687)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow22-107" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape23-111" v:mID="23" v:groupContext="shape" transform="translate(400.501,-240.687)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow23-112" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape24-116" v:mID="24" v:groupContext="shape" transform="translate(434.251,-240.687)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow24-117" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape25-121" v:mID="25" v:groupContext="shape" transform="translate(481.501,-240.687)">
+ <title>Rectangle.80</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow25-122" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape26-127" v:mID="26" v:groupContext="shape" transform="translate(515.251,-240.687)">
+ <title>Rectangle.81</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow26-128" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape27-133" v:mID="27" v:groupContext="shape" transform="translate(317.251,-231.687)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow27-134" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st6"/>
+ </g>
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st7"/>
+ </g>
+ <g id="shape28-138" v:mID="28" v:groupContext="shape" transform="translate(328.501,-362.688)">
+ <title>Sheet.28</title>
+ <desc>Local Table for N Specific Flows Serviced at Node 1</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="110.423" cy="418.94" width="220.85" height="11.25"/>
+ <rect x="0" y="413.315" width="220.846" height="11.25" class="st8"/>
+ <text x="5.77" y="421.94" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Local Table for N Specific Flows Serviced at Node 1</text> </g>
+ <g id="group34-141" transform="translate(66.0294,-165.569)" v:mID="34" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Device)"/>
+ <v:cp v:nameU="SubShapeType" v:lbl="SubShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Load balancer)"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Load balancer</title>
+ <g id="shape35-142" v:mID="35" v:groupContext="shape" transform="translate(0,-7.33146)">
+ <title>Sheet.35</title>
+ <g id="shadow35-143" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23 Z" class="st10"/>
+ <path d="M0 377.86 L72 377.86" class="st6"/>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23" class="st6"/>
+ </g>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23 Z" class="st11"/>
+ <path d="M0 377.86 L72 377.86" class="st12"/>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23" class="st12"/>
+ </g>
+ <g id="shape36-152" v:mID="36" v:groupContext="shape" transform="translate(8.03054,-12.9324)">
+ <title>Sheet.36</title>
+ <g id="shadow36-153" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M45.34 421.81 L41.2 422.66 L44.12 424.56 L49.68 423.16 L48.75 417.51 L45.8 415.59 L46.69 419.68 L36.97
+ 413.34 L35.6 415.45 L45.34 421.81 ZM50.83 405.36 L39.2 405.36 L39.2 407.88 L50.8 407.88 L47.82 410.83
+ L51.34 410.83 L55.21 406.61 L51.32 402.39 L47.83 402.39 L50.83 405.36 ZM46.49 392.01 L36.75 398.37
+ L38.13 400.48 L47.84 394.14 L46.96 398.23 L49.91 396.31 L50.84 390.66 L45.28 389.26 L42.36 391.16
+ L46.49 392.01 ZM27.71 397.16 C22.66 397.16 18.58 401.25 18.58 406.29 C18.58 411.33 22.66 415.42
+ 27.71 415.42 C32.75 415.42 36.84 411.33 36.84 406.29 C36.84 401.25 32.75 397.16 27.71 397.16 ZM27.71
+ 400.04 C31.15 400.04 33.96 402.84 33.96 406.29 C33.96 409.74 31.15 412.54 27.71 412.54 C24.26 412.54
+ 21.46 409.74 21.46 406.29 C21.46 402.84 24.26 400.04 27.71 400.04 ZM11.64 405.04 L0 405.04 L0 407.56
+ L11.6 407.56 L8.62 410.51 L12.14 410.51 L16.01 406.29 L12.12 402.07 L8.64 402.07 L11.64 405.04 Z"
+ class="st2"/>
+ </g>
+ <path d="M45.34 421.81 L41.2 422.66 L44.12 424.56 L49.68 423.16 L48.75 417.51 L45.8 415.59 L46.69 419.68 L36.97 413.34
+ L35.6 415.45 L45.34 421.81 ZM50.83 405.36 L39.2 405.36 L39.2 407.88 L50.8 407.88 L47.82 410.83 L51.34
+ 410.83 L55.21 406.61 L51.32 402.39 L47.83 402.39 L50.83 405.36 ZM46.49 392.01 L36.75 398.37 L38.13 400.48
+ L47.84 394.14 L46.96 398.23 L49.91 396.31 L50.84 390.66 L45.28 389.26 L42.36 391.16 L46.49 392.01 ZM27.71
+ 397.16 C22.66 397.16 18.58 401.25 18.58 406.29 C18.58 411.33 22.66 415.42 27.71 415.42 C32.75 415.42
+ 36.84 411.33 36.84 406.29 C36.84 401.25 32.75 397.16 27.71 397.16 ZM27.71 400.04 C31.15 400.04 33.96
+ 402.84 33.96 406.29 C33.96 409.74 31.15 412.54 27.71 412.54 C24.26 412.54 21.46 409.74 21.46 406.29
+ C21.46 402.84 24.26 400.04 27.71 400.04 ZM11.64 405.04 L0 405.04 L0 407.56 L11.6 407.56 L8.62 410.51
+ L12.14 410.51 L16.01 406.29 L12.12 402.07 L8.64 402.07 L11.64 405.04 Z" class="st13"/>
+ </g>
+ </g>
+ <g id="shape37-157" v:mID="37" v:groupContext="shape" transform="translate(21.0294,-45.4375)">
+ <title>Rectangle</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow37-158" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="336.433" width="135" height="88.1315" class="st2"/>
+ </g>
+ <rect x="0" y="336.433" width="135" height="88.1315" class="st3"/>
+ </g>
+ <g id="shape38-162" v:mID="38" v:groupContext="shape" transform="translate(34.693,-126.438)">
+ <title>Sheet.38</title>
+ <desc>EFD Table</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="49.3364" cy="415.565" width="98.68" height="18"/>
+ <rect x="0" y="406.565" width="98.6728" height="18" class="st8"/>
+ <text x="24.87" y="419.17" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>EFD Table</text> </g>
+ <g id="shape39-165" v:mID="39" v:groupContext="shape" transform="translate(30.0294,-99.4375)">
+ <title>Rectangle.39</title>
+ <desc>Group_id</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="26.9182" cy="415.565" width="53.84" height="18"/>
+ <g id="shadow39-166" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st15"/>
+ <text x="7.87" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group_id</text> </g>
+ <g id="shape40-171" v:mID="40" v:groupContext="shape" transform="translate(93.193,-99.4375)">
+ <title>Rectangle.40</title>
+ <desc>Hash index</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="26.9182" cy="415.565" width="53.84" height="18"/>
+ <g id="shadow40-172" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st15"/>
+ <text x="4.64" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Hash index</text> </g>
+ <g id="shape41-177" v:mID="41" v:groupContext="shape" transform="translate(30.193,-82.4275)">
+ <title>Rectangle.41</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow41-178" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape42-182" v:mID="42" v:groupContext="shape" transform="translate(30.193,-66.8125)">
+ <title>Rectangle.42</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow42-183" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape43-187" v:mID="43" v:groupContext="shape" transform="translate(30.1112,-52.1875)">
+ <title>Rectangle.43</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow43-188" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape44-192" v:mID="44" v:groupContext="shape" transform="translate(93.0294,-81.4375)">
+ <title>Rectangle.44</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow44-193" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape45-197" v:mID="45" v:groupContext="shape" transform="translate(93.193,-66.8125)">
+ <title>Rectangle.45</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow45-198" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape46-202" v:mID="46" v:groupContext="shape" transform="translate(93.193,-52.1875)">
+ <title>Rectangle.46</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow46-203" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape47-207" v:mID="47" v:groupContext="shape" transform="translate(374.924,544.022) rotate(135)">
+ <title>Sheet.47</title>
+ <path d="M-0 417.75 A40.674 18.0151 -156.2 0 0 40.24 422.15 L40.49 421.89" class="st17"/>
+ </g>
+ <g id="shape48-213" v:mID="48" v:groupContext="shape" transform="translate(21.0294,-19)">
+ <title>Sheet.48</title>
+ <desc>Supports X*N Flows</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="415.565" width="135" height="18"/>
+ <rect x="0" y="406.565" width="135" height="18" class="st19"/>
+ <text x="19.05" y="419.17" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Supports X*N Flows</text> </g>
+ <g id="shape49-216" v:mID="49" v:groupContext="shape" transform="translate(48.0294,-229.938)">
+ <title>Sheet.49</title>
+ <desc>Frontend Server or Load Balancer</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="400.94" width="108" height="47.25"/>
+ <rect x="0" y="377.315" width="108" height="47.25" class="st21"/>
+ <text x="14.56" y="397.34" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Frontend Server<v:newlineChar/><tspan
+ x="13.16" dy="1.2em" class="st22">or Load Balancer </tspan> </text> </g>
+ <g id="group51-220" transform="translate(223.876,-310.938)" v:mID="51" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server</title>
+ <g id="shape52-221" v:mID="52" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.52</title>
+ <g id="shadow52-222" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape53-226" v:mID="53" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.53</title>
+ <g id="shadow53-227" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape54-231" v:mID="54" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.54</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow54-232" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape59-239" v:mID="59" v:groupContext="shape" transform="translate(277.876,-373.938)">
+ <title>Sheet.59</title>
+ <path d="M-0 424.56 A111.108 53.2538 42.31 0 1 93.83 421.21 L94.14 421.41" class="st17"/>
+ </g>
+ <g id="shape60-244" v:mID="60" v:groupContext="shape" transform="translate(205.876,-283.938)">
+ <title>Sheet.60</title>
+ <desc>Backend Server 1</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.93" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server 1</text> </g>
+ <g id="group61-247" transform="translate(223.876,-207.438)" v:mID="61" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server.61</title>
+ <g id="shape62-248" v:mID="62" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.62</title>
+ <g id="shadow62-249" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape63-253" v:mID="63" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.63</title>
+ <g id="shadow63-254" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape64-258" v:mID="64" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow64-259" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape65-266" v:mID="65" v:groupContext="shape" transform="translate(205.876,-180.437)">
+ <title>Sheet.65</title>
+ <desc>Backend Server 2</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.93" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server 2</text> </g>
+ <g id="group66-269" transform="translate(219.029,-58.9375)" v:mID="66" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server.66</title>
+ <g id="shape67-270" v:mID="67" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.67</title>
+ <g id="shadow67-271" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape68-275" v:mID="68" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.68</title>
+ <g id="shadow68-276" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape69-280" v:mID="69" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow69-281" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape70-288" v:mID="70" v:groupContext="shape" transform="translate(201.029,-26.056)">
+ <title>Sheet.70</title>
+ <desc>Backend Server X</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.86" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server X</text> </g>
+ <g id="shape71-291" v:mID="71" v:groupContext="shape" transform="translate(684.44,239.627) rotate(90)">
+ <title>Sheet.71</title>
+ <path d="M0 424.56 L45 424.56" class="st24"/>
+ </g>
+ <g id="shape72-294" v:mID="72" v:groupContext="shape" transform="translate(6.85967,-22.443) rotate(-38.1076)">
+ <title>Sheet.72</title>
+ <path d="M-0 424.56 A96.1331 44.4001 55.03 0 1 68.24 420.56 L68.51 420.79" class="st17"/>
+ </g>
+ <g id="shape73-299" v:mID="73" v:groupContext="shape" transform="translate(328.501,-135.937)">
+ <title>Rectangle.73</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow73-300" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape74-305" v:mID="74" v:groupContext="shape" transform="translate(362.251,-135.937)">
+ <title>Rectangle.74</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow74-306" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape75-311" v:mID="75" v:groupContext="shape" transform="translate(409.501,-135.937)">
+ <title>Rectangle.75</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow75-312" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape76-317" v:mID="76" v:groupContext="shape" transform="translate(443.251,-135.937)">
+ <title>Rectangle.76</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow76-318" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape77-323" v:mID="77" v:groupContext="shape" transform="translate(490.501,-135.937)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow77-324" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape78-328" v:mID="78" v:groupContext="shape" transform="translate(524.251,-135.937)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow78-329" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape79-333" v:mID="79" v:groupContext="shape" transform="translate(328.501,-113.437)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow79-334" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape80-338" v:mID="80" v:groupContext="shape" transform="translate(362.251,-113.437)">
+ <title>Rectangle.80</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow80-339" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape81-343" v:mID="81" v:groupContext="shape" transform="translate(409.501,-113.437)">
+ <title>Rectangle.81</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow81-344" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape82-348" v:mID="82" v:groupContext="shape" transform="translate(443.251,-113.437)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow82-349" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape83-353" v:mID="83" v:groupContext="shape" transform="translate(490.501,-113.437)">
+ <title>Rectangle.83</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow83-354" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape84-358" v:mID="84" v:groupContext="shape" transform="translate(524.251,-113.437)">
+ <title>Rectangle.84</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow84-359" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape85-363" v:mID="85" v:groupContext="shape" transform="translate(328.501,-77.4375)">
+ <title>Rectangle.85</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow85-364" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape86-369" v:mID="86" v:groupContext="shape" transform="translate(362.251,-77.4375)">
+ <title>Rectangle.86</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow86-370" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape87-375" v:mID="87" v:groupContext="shape" transform="translate(409.501,-77.4375)">
+ <title>Rectangle.87</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow87-376" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape88-381" v:mID="88" v:groupContext="shape" transform="translate(443.251,-77.4375)">
+ <title>Rectangle.88</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow88-382" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape89-387" v:mID="89" v:groupContext="shape" transform="translate(490.501,-77.4375)">
+ <title>Rectangle.89</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow89-388" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape90-393" v:mID="90" v:groupContext="shape" transform="translate(524.251,-77.4375)">
+ <title>Rectangle.90</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow90-394" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape91-399" v:mID="91" v:groupContext="shape" transform="translate(328.501,-40.9375)">
+ <title>Rectangle.91</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow91-400" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape92-404" v:mID="92" v:groupContext="shape" transform="translate(362.251,-40.9375)">
+ <title>Rectangle.92</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow92-405" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape93-409" v:mID="93" v:groupContext="shape" transform="translate(409.501,-40.9375)">
+ <title>Rectangle.93</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow93-410" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape94-414" v:mID="94" v:groupContext="shape" transform="translate(443.251,-40.9375)">
+ <title>Rectangle.94</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow94-415" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape95-419" v:mID="95" v:groupContext="shape" transform="translate(490.501,-40.9375)">
+ <title>Rectangle.95</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow95-420" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape96-425" v:mID="96" v:groupContext="shape" transform="translate(524.251,-40.9375)">
+ <title>Rectangle.96</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow96-426" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape97-431" v:mID="97" v:groupContext="shape" transform="translate(326.251,-31.9375)">
+ <title>Rectangle.97</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow97-432" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st6"/>
+ </g>
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st7"/>
+ </g>
+ <g id="shape98-436" v:mID="98" v:groupContext="shape" transform="translate(337.501,-162.938)">
+ <title>Sheet.98</title>
+ <desc>Local Table for N Specific Flows Serviced at Node X</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="110.423" cy="418.94" width="220.85" height="11.25"/>
+ <rect x="0" y="413.315" width="220.846" height="11.25" class="st8"/>
+ <text x="5.55" y="421.94" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Local Table for N Specific Flows Serviced at Node X</text> </g>
+ <g id="shape99-439" v:mID="99" v:groupContext="shape" transform="translate(-204.342,-29.4449) rotate(-53.7462)">
+ <title>Sheet.99</title>
+ <path d="M0 424.56 L160.37 424.56" class="st25"/>
+ </g>
+ <g id="shape100-445" v:mID="100" v:groupContext="shape" transform="translate(-37.6568,-164.882) rotate(-24.444)">
+ <title>Sheet.100</title>
+ <path d="M0 424.56 L101.71 424.56" class="st25"/>
+ </g>
+ <g id="shape101-450" v:mID="101" v:groupContext="shape" transform="translate(464.049,-50.8578) rotate(50.099)">
+ <title>Sheet.101</title>
+ <path d="M0 424.56 L139.8 424.56" class="st25"/>
+ </g>
+ <g id="shape102-455" v:mID="102" v:groupContext="shape" transform="translate(372.376,-207.438)">
+ <title>Sheet.102</title>
+ <desc>Supports N Flows</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="415.565" width="135" height="18"/>
+ <rect x="0" y="406.565" width="135" height="18" class="st19"/>
+ <text x="25.15" y="419.17" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Supports N Flows</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i7.svg b/doc/guides/prog_guide/img/efd_i7.svg
new file mode 100644
index 0000000..98f8000
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i7.svg
@@ -0,0 +1,790 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i8.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="10.6168in" height="4.81965in"
+ viewBox="0 0 764.409 347.015" xml:space="preserve" color-interpolation-filters="sRGB" class="st27">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st4 {fill:#000000;font-family:Intel Clear;font-size:0.998566em}
+ .st5 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st6 {stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st7 {stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st8 {stroke:#ca8f02;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st9 {stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st10 {fill:#c00000;font-family:Intel Clear;font-size:0.828804em;font-weight:bold}
+ .st11 {fill:#7f6d00;font-family:Intel Clear;font-size:0.828804em;font-weight:bold}
+ .st12 {fill:#00b050;stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st13 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st14 {fill:#00b050;stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st15 {fill:#ca8f02;stroke:#ca8f02;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st16 {fill:#004280;font-family:Intel Clear;font-size:0.828804em}
+ .st17 {fill:#ffffff;font-family:Intel Clear;font-size:0.998566em}
+ .st18 {fill:#ffffff;font-family:Intel Clear;font-size:1.49785em}
+ .st19 {visibility:visible}
+ .st20 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st21 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st22 {fill:#feffff;font-family:Symbol;font-size:1.16666em}
+ .st23 {font-size:1em}
+ .st24 {font-family:Calibri;font-size:1em}
+ .st25 {fill:none;stroke:none;stroke-width:0.25}
+ .st26 {fill:#ffffff;font-family:Calibri;font-size:1.00001em}
+ .st27 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(27.7836,-307.505)">
+ <title>Sheet.3</title>
+ <path d="M0 329.94 C-0 328.06 1.54 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.06 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.54 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape4-3" v:mID="4" v:groupContext="shape" transform="translate(27.7836,-307.505)">
+ <title>Sheet.4</title>
+ <path d="M0 329.94 C-0 328.06 1.54 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.06 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.54 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape5-5" v:mID="5" v:groupContext="shape" transform="translate(50.1544,-309.121)">
+ <title>Sheet.5</title>
+ <desc>Key1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1</text> </g>
+ <g id="shape6-9" v:mID="6" v:groupContext="shape" transform="translate(43.6909,-286.954)">
+ <title>Sheet.6</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape7-11" v:mID="7" v:groupContext="shape" transform="translate(27.7836,-266.044)">
+ <title>Sheet.7</title>
+ <path d="M0 330.04 C0 328.16 1.52 326.64 3.41 326.64 L68.51 326.64 C70.4 326.64 71.91 328.16 71.91 330.04 L71.91 343.62
+ C71.91 345.49 70.4 347.02 68.51 347.02 L3.41 347.02 C1.52 347.02 0 345.49 0 343.62 L0 330.04 Z"
+ class="st1"/>
+ </g>
+ <g id="shape8-13" v:mID="8" v:groupContext="shape" transform="translate(27.7836,-266.044)">
+ <title>Sheet.8</title>
+ <path d="M0 330.04 C0 328.16 1.52 326.64 3.41 326.64 L68.51 326.64 C70.4 326.64 71.91 328.16 71.91 330.04 L71.91 343.62
+ C71.91 345.49 70.4 347.02 68.51 347.02 L3.41 347.02 C1.52 347.02 0 345.49 0 343.62 L0 330.04 Z"
+ class="st2"/>
+ </g>
+ <g id="shape9-15" v:mID="9" v:groupContext="shape" transform="translate(50.7572,-267.602)">
+ <title>Sheet.9</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape10-19" v:mID="10" v:groupContext="shape" transform="translate(19.0195,-225.183)">
+ <title>Sheet.10</title>
+ <path d="M0 330.74 C0 328.94 1.46 327.48 3.26 327.48 L87.15 327.48 C88.95 327.48 90.4 328.94 90.4 330.74 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.26 347.02 C1.46 347.02 0 345.56 0 343.76 L0 330.74 Z"
+ class="st1"/>
+ </g>
+ <g id="shape11-21" v:mID="11" v:groupContext="shape" transform="translate(19.0195,-225.183)">
+ <title>Sheet.11</title>
+ <path d="M0 330.74 C0 328.94 1.46 327.48 3.26 327.48 L87.15 327.48 C88.95 327.48 90.4 328.94 90.4 330.74 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.26 347.02 C1.46 347.02 0 345.56 0 343.76 L0 330.74 Z"
+ class="st2"/>
+ </g>
+ <g id="shape12-23" v:mID="12" v:groupContext="shape" transform="translate(28.0373,-226.287)">
+ <title>Sheet.12</title>
+ <desc>0x0102ABCD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.3615" cy="339.824" width="86.73" height="14.3829"/>
+ <path d="M86.72 332.63 L0 332.63 L0 347.02 L86.72 347.02 L86.72 332.63" class="st3"/>
+ <text x="7.12" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102ABCD</text> </g>
+ <g id="shape13-27" v:mID="13" v:groupContext="shape" transform="translate(43.6909,-244.775)">
+ <title>Sheet.13</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape14-29" v:mID="14" v:groupContext="shape" transform="translate(40.7496,-210.444)">
+ <title>Sheet.14</title>
+ <path d="M26.29 334.91 C26.29 338.26 25.84 340.96 25.29 340.96 L14.16 340.96 C13.6 340.96 13.15 343.67 13.15 347.02 C13.15
+ 343.67 12.7 340.96 12.14 340.96 L1.01 340.96 C0.46 340.96 0 338.26 0 334.91" class="st6"/>
+ </g>
+ <g id="shape15-32" v:mID="15" v:groupContext="shape" transform="translate(125.629,-307.625)">
+ <title>Sheet.15</title>
+ <path d="M0 330.04 C0 328.16 1.52 326.64 3.41 326.64 L68.63 326.64 C70.51 326.64 72.03 328.16 72.03 330.04 L72.03 343.62
+ C72.03 345.49 70.51 347.02 68.63 347.02 L3.41 347.02 C1.52 347.02 0 345.49 0 343.62 L0 330.04 Z"
+ class="st1"/>
+ </g>
+ <g id="shape16-34" v:mID="16" v:groupContext="shape" transform="translate(125.629,-307.625)">
+ <title>Sheet.16</title>
+ <path d="M0 330.04 C0 328.16 1.52 326.64 3.41 326.64 L68.63 326.64 C70.51 326.64 72.03 328.16 72.03 330.04 L72.03 343.62
+ C72.03 345.49 70.51 347.02 68.63 347.02 L3.41 347.02 C1.52 347.02 0 345.49 0 343.62 L0 330.04 Z"
+ class="st2"/>
+ </g>
+ <g id="shape17-36" v:mID="17" v:groupContext="shape" transform="translate(148.034,-309.155)">
+ <title>Sheet.17</title>
+ <desc>Key2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key2</text> </g>
+ <g id="shape18-40" v:mID="18" v:groupContext="shape" transform="translate(141.536,-286.954)">
+ <title>Sheet.18</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape19-42" v:mID="19" v:groupContext="shape" transform="translate(125.629,-266.044)">
+ <title>Sheet.19</title>
+ <path d="M0 329.94 C0 328.06 1.54 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.06 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.54 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st1"/>
+ </g>
+ <g id="shape20-44" v:mID="20" v:groupContext="shape" transform="translate(125.629,-266.044)">
+ <title>Sheet.20</title>
+ <path d="M0 329.94 C0 328.06 1.54 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.06 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.54 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st2"/>
+ </g>
+ <g id="shape21-46" v:mID="21" v:groupContext="shape" transform="translate(148.636,-267.636)">
+ <title>Sheet.21</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape22-50" v:mID="22" v:groupContext="shape" transform="translate(116.865,-225.183)">
+ <title>Sheet.22</title>
+ <path d="M0 330.74 C0 328.94 1.46 327.48 3.26 327.48 L87.15 327.48 C88.95 327.48 90.4 328.94 90.4 330.74 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.26 347.02 C1.46 347.02 0 345.56 0 343.76 L0 330.74 Z"
+ class="st1"/>
+ </g>
+ <g id="shape23-52" v:mID="23" v:groupContext="shape" transform="translate(116.865,-225.183)">
+ <title>Sheet.23</title>
+ <path d="M0 330.74 C0 328.94 1.46 327.48 3.26 327.48 L87.15 327.48 C88.95 327.48 90.4 328.94 90.4 330.74 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.26 347.02 C1.46 347.02 0 345.56 0 343.76 L0 330.74 Z"
+ class="st2"/>
+ </g>
+ <g id="shape24-54" v:mID="24" v:groupContext="shape" transform="translate(125.917,-226.322)">
+ <title>Sheet.24</title>
+ <desc>0x0103CDAB</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.3615" cy="339.824" width="86.73" height="14.3829"/>
+ <path d="M86.72 332.63 L0 332.63 L0 347.02 L86.72 347.02 L86.72 332.63" class="st3"/>
+ <text x="7.12" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0103CDAB</text> </g>
+ <g id="shape25-58" v:mID="25" v:groupContext="shape" transform="translate(141.536,-244.775)">
+ <title>Sheet.25</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape26-60" v:mID="26" v:groupContext="shape" transform="translate(138.595,-210.444)">
+ <title>Sheet.26</title>
+ <path d="M26.29 334.91 C26.29 338.26 25.84 340.96 25.29 340.96 L14.16 340.96 C13.6 340.96 13.15 343.67 13.15 347.02 C13.15
+ 343.67 12.7 340.96 12.14 340.96 L1.01 340.96 C0.46 340.96 0 338.26 0 334.91" class="st7"/>
+ </g>
+ <g id="shape27-63" v:mID="27" v:groupContext="shape" transform="translate(221.793,-307.625)">
+ <title>Sheet.27</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.64 326.64 C70.52 326.64 72.03 328.17 72.03 330.04 L72.03 343.63
+ C72.03 345.5 70.52 347.02 68.64 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st1"/>
+ </g>
+ <g id="shape28-65" v:mID="28" v:groupContext="shape" transform="translate(221.793,-307.625)">
+ <title>Sheet.28</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.64 326.64 C70.52 326.64 72.03 328.17 72.03 330.04 L72.03 343.63
+ C72.03 345.5 70.52 347.02 68.64 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st2"/>
+ </g>
+ <g id="shape29-67" v:mID="29" v:groupContext="shape" transform="translate(244.237,-309.155)">
+ <title>Sheet.29</title>
+ <desc>Key3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3</text> </g>
+ <g id="shape30-71" v:mID="30" v:groupContext="shape" transform="translate(237.701,-286.954)">
+ <title>Sheet.30</title>
+ <path d="M0 336.65 L9.84 336.65 L9.84 326.28 L29.53 326.28 L29.53 336.65 L39.38 336.65 L19.69 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape31-73" v:mID="31" v:groupContext="shape" transform="translate(221.793,-266.044)">
+ <title>Sheet.31</title>
+ <path d="M0 329.94 C-0 328.07 1.55 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.07 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st1"/>
+ </g>
+ <g id="shape32-75" v:mID="32" v:groupContext="shape" transform="translate(221.793,-266.044)">
+ <title>Sheet.32</title>
+ <path d="M0 329.94 C-0 328.07 1.55 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.07 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st2"/>
+ </g>
+ <g id="shape33-77" v:mID="33" v:groupContext="shape" transform="translate(244.84,-267.636)">
+ <title>Sheet.33</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape34-81" v:mID="34" v:groupContext="shape" transform="translate(213.029,-225.183)">
+ <title>Sheet.34</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.27 327.48 C89.07 327.48 90.52 328.95 90.52 330.75 L90.52 343.76
+ C90.52 345.56 89.07 347.02 87.27 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape35-83" v:mID="35" v:groupContext="shape" transform="translate(213.029,-225.183)">
+ <title>Sheet.35</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.27 327.48 C89.07 327.48 90.52 328.95 90.52 330.75 L90.52 343.76
+ C90.52 345.56 89.07 347.02 87.27 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape36-85" v:mID="36" v:groupContext="shape" transform="translate(222.002,-226.322)">
+ <title>Sheet.36</title>
+ <desc>0x0102BAAD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.4787" cy="339.824" width="86.96" height="14.3829"/>
+ <path d="M86.96 332.63 L0 332.63 L0 347.02 L86.96 347.02 L86.96 332.63" class="st3"/>
+ <text x="7.13" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102BAAD</text> </g>
+ <g id="shape37-89" v:mID="37" v:groupContext="shape" transform="translate(237.701,-244.775)">
+ <title>Sheet.37</title>
+ <path d="M0 336.71 L9.84 336.71 L9.84 326.4 L29.53 326.4 L29.53 336.71 L39.38 336.71 L19.69 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape38-91" v:mID="38" v:groupContext="shape" transform="translate(234.759,-210.444)">
+ <title>Sheet.38</title>
+ <path d="M26.41 334.91 C26.41 338.26 25.96 340.96 25.41 340.96 L14.22 340.96 C13.66 340.96 13.21 343.67 13.21 347.02
+ C13.21 343.67 12.76 340.96 12.2 340.96 L1.01 340.96 C0.46 340.96 0 338.26 0 334.91" class="st6"/>
+ </g>
+ <g id="shape39-94" v:mID="39" v:groupContext="shape" transform="translate(319.759,-307.625)">
+ <title>Sheet.39</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.52 326.64 C70.4 326.64 71.91 328.17 71.91 330.04 L71.91 343.63
+ C71.91 345.5 70.4 347.02 68.52 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st1"/>
+ </g>
+ <g id="shape40-96" v:mID="40" v:groupContext="shape" transform="translate(319.759,-307.625)">
+ <title>Sheet.40</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.52 326.64 C70.4 326.64 71.91 328.17 71.91 330.04 L71.91 343.63
+ C71.91 345.5 70.4 347.02 68.52 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st2"/>
+ </g>
+ <g id="shape41-98" v:mID="41" v:groupContext="shape" transform="translate(342.125,-309.155)">
+ <title>Sheet.41</title>
+ <desc>Key4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4</text> </g>
+ <g id="shape42-102" v:mID="42" v:groupContext="shape" transform="translate(335.666,-286.954)">
+ <title>Sheet.42</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape43-104" v:mID="43" v:groupContext="shape" transform="translate(319.759,-266.044)">
+ <title>Sheet.43</title>
+ <path d="M0 329.94 C0 328.07 1.55 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.07 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape44-106" v:mID="44" v:groupContext="shape" transform="translate(319.759,-266.044)">
+ <title>Sheet.44</title>
+ <path d="M0 329.94 C0 328.07 1.55 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.07 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape45-108" v:mID="45" v:groupContext="shape" transform="translate(342.728,-267.636)">
+ <title>Sheet.45</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape46-112" v:mID="46" v:groupContext="shape" transform="translate(310.995,-225.183)">
+ <title>Sheet.46</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.15 327.48 C88.95 327.48 90.4 328.95 90.4 330.75 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape47-114" v:mID="47" v:groupContext="shape" transform="translate(310.995,-225.183)">
+ <title>Sheet.47</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.15 327.48 C88.95 327.48 90.4 328.95 90.4 330.75 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape48-116" v:mID="48" v:groupContext="shape" transform="translate(321.689,-226.322)">
+ <title>Sheet.48</title>
+ <desc>0x0104BEEF</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="41.4183" cy="339.824" width="82.84" height="14.3829"/>
+ <path d="M82.84 332.63 L0 332.63 L0 347.02 L82.84 347.02 L82.84 332.63" class="st3"/>
+ <text x="6.87" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0104BEEF</text> </g>
+ <g id="shape49-120" v:mID="49" v:groupContext="shape" transform="translate(335.666,-244.775)">
+ <title>Sheet.49</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape50-122" v:mID="50" v:groupContext="shape" transform="translate(332.725,-210.444)">
+ <title>Sheet.50</title>
+ <path d="M26.29 334.91 C26.29 338.27 25.84 340.96 25.29 340.96 L14.17 340.96 C13.61 340.96 13.15 343.67 13.15 347.02
+ C13.15 343.67 12.7 340.96 12.14 340.96 L1.02 340.96 C0.47 340.96 0 338.27 0 334.91" class="st6"/>
+ </g>
+ <g id="shape51-125" v:mID="51" v:groupContext="shape" transform="translate(416.884,-307.625)">
+ <title>Sheet.51</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.52 326.64 C70.4 326.64 71.91 328.17 71.91 330.04 L71.91 343.63
+ C71.91 345.5 70.4 347.02 68.52 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st1"/>
+ </g>
+ <g id="shape52-127" v:mID="52" v:groupContext="shape" transform="translate(416.884,-307.625)">
+ <title>Sheet.52</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.52 326.64 C70.4 326.64 71.91 328.17 71.91 330.04 L71.91 343.63
+ C71.91 345.5 70.4 347.02 68.52 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st2"/>
+ </g>
+ <g id="shape53-129" v:mID="53" v:groupContext="shape" transform="translate(439.255,-309.155)">
+ <title>Sheet.53</title>
+ <desc>Key5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key5</text> </g>
+ <g id="shape54-133" v:mID="54" v:groupContext="shape" transform="translate(432.791,-286.954)">
+ <title>Sheet.54</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape55-135" v:mID="55" v:groupContext="shape" transform="translate(416.884,-266.044)">
+ <title>Sheet.55</title>
+ <path d="M0 329.94 C0 328.07 1.55 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.07 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape56-137" v:mID="56" v:groupContext="shape" transform="translate(416.884,-266.044)">
+ <title>Sheet.56</title>
+ <path d="M0 329.94 C0 328.07 1.55 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.07 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape57-139" v:mID="57" v:groupContext="shape" transform="translate(439.858,-267.636)">
+ <title>Sheet.57</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape58-143" v:mID="58" v:groupContext="shape" transform="translate(408.12,-225.183)">
+ <title>Sheet.58</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.15 327.48 C88.95 327.48 90.4 328.95 90.4 330.75 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape59-145" v:mID="59" v:groupContext="shape" transform="translate(408.12,-225.183)">
+ <title>Sheet.59</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.15 327.48 C88.95 327.48 90.4 328.95 90.4 330.75 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape60-147" v:mID="60" v:groupContext="shape" transform="translate(416.778,-226.322)">
+ <title>Sheet.60</title>
+ <desc>0x0103DABD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.7817" cy="339.824" width="87.57" height="14.3829"/>
+ <path d="M87.56 332.63 L0 332.63 L0 347.02 L87.56 347.02 L87.56 332.63" class="st3"/>
+ <text x="7.17" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0103DABD</text> </g>
+ <g id="shape61-151" v:mID="61" v:groupContext="shape" transform="translate(432.791,-244.775)">
+ <title>Sheet.61</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape62-153" v:mID="62" v:groupContext="shape" transform="translate(429.85,-210.444)">
+ <title>Sheet.62</title>
+ <path d="M26.29 334.91 C26.29 338.27 25.84 340.96 25.29 340.96 L14.17 340.96 C13.61 340.96 13.15 343.67 13.15 347.02
+ C13.15 343.67 12.7 340.96 12.14 340.96 L1.02 340.96 C0.47 340.96 0 338.27 0 334.91" class="st7"/>
+ </g>
+ <g id="shape63-156" v:mID="63" v:groupContext="shape" transform="translate(514.489,-307.625)">
+ <title>Sheet.63</title>
+ <path d="M0 330.06 C-0 328.17 1.53 326.64 3.42 326.64 L68.64 326.64 C70.53 326.64 72.03 328.17 72.03 330.06 L72.03 343.63
+ C72.03 345.52 70.53 347.02 68.64 347.02 L3.42 347.02 C1.53 347.02 0 345.52 0 343.63 L0 330.06 Z"
+ class="st1"/>
+ </g>
+ <g id="shape64-158" v:mID="64" v:groupContext="shape" transform="translate(514.489,-307.625)">
+ <title>Sheet.64</title>
+ <path d="M0 330.06 C-0 328.17 1.53 326.64 3.42 326.64 L68.64 326.64 C70.53 326.64 72.03 328.17 72.03 330.06 L72.03 343.63
+ C72.03 345.52 70.53 347.02 68.64 347.02 L3.42 347.02 C1.53 347.02 0 345.52 0 343.63 L0 330.06 Z"
+ class="st2"/>
+ </g>
+ <g id="shape65-160" v:mID="65" v:groupContext="shape" transform="translate(536.883,-309.19)">
+ <title>Sheet.65</title>
+ <desc>Key6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key6</text> </g>
+ <g id="shape66-164" v:mID="66" v:groupContext="shape" transform="translate(530.396,-287.074)">
+ <title>Sheet.66</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape67-166" v:mID="67" v:groupContext="shape" transform="translate(514.489,-266.044)">
+ <title>Sheet.67</title>
+ <path d="M0 329.94 C0 328.08 1.56 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.08 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.56 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st1"/>
+ </g>
+ <g id="shape68-168" v:mID="68" v:groupContext="shape" transform="translate(514.489,-266.044)">
+ <title>Sheet.68</title>
+ <path d="M0 329.94 C0 328.08 1.56 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.08 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.56 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st2"/>
+ </g>
+ <g id="shape69-170" v:mID="69" v:groupContext="shape" transform="translate(537.486,-267.671)">
+ <title>Sheet.69</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape70-174" v:mID="70" v:groupContext="shape" transform="translate(505.725,-225.183)">
+ <title>Sheet.70</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.16 327.48 C88.96 327.48 90.4 328.95 90.4 330.75 L90.4 343.78
+ C90.4 345.58 88.96 347.02 87.16 347.02 L3.27 347.02 C1.47 347.02 0 345.58 0 343.78 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape71-176" v:mID="71" v:groupContext="shape" transform="translate(505.725,-225.183)">
+ <title>Sheet.71</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.16 327.48 C88.96 327.48 90.4 328.95 90.4 330.75 L90.4 343.78
+ C90.4 345.58 88.96 347.02 87.16 347.02 L3.27 347.02 C1.47 347.02 0 345.58 0 343.78 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape72-178" v:mID="72" v:groupContext="shape" transform="translate(514.766,-226.356)">
+ <title>Sheet.72</title>
+ <desc>0x0102ADCB</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.3615" cy="339.824" width="86.73" height="14.3829"/>
+ <path d="M86.72 332.63 L0 332.63 L0 347.02 L86.72 347.02 L86.72 332.63" class="st3"/>
+ <text x="7.12" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102ADCB</text> </g>
+ <g id="shape73-182" v:mID="73" v:groupContext="shape" transform="translate(530.396,-244.775)">
+ <title>Sheet.73</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape74-184" v:mID="74" v:groupContext="shape" transform="translate(527.455,-210.564)">
+ <title>Sheet.74</title>
+ <path d="M26.29 335.03 C26.29 338.36 25.87 341.02 25.3 341.02 L14.17 341.02 C13.6 341.02 13.15 343.72 13.15 347.02 C13.15
+ 343.72 12.73 341.02 12.16 341.02 L1.02 341.02 C0.45 341.02 0 338.36 0 335.03" class="st6"/>
+ </g>
+ <g id="shape75-187" v:mID="75" v:groupContext="shape" transform="translate(610.653,-307.505)">
+ <title>Sheet.75</title>
+ <path d="M0 329.94 C0 328.08 1.56 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.08 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.56 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st1"/>
+ </g>
+ <g id="shape76-189" v:mID="76" v:groupContext="shape" transform="translate(610.653,-307.505)">
+ <title>Sheet.76</title>
+ <path d="M0 329.94 C0 328.08 1.56 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.08 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.56 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st2"/>
+ </g>
+ <g id="shape77-191" v:mID="77" v:groupContext="shape" transform="translate(633.086,-309.121)">
+ <title>Sheet.77</title>
+ <desc>Key7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7</text> </g>
+ <g id="shape78-195" v:mID="78" v:groupContext="shape" transform="translate(626.561,-286.954)">
+ <title>Sheet.78</title>
+ <path d="M0 336.65 L9.84 336.65 L9.84 326.28 L29.53 326.28 L29.53 336.65 L39.38 336.65 L19.69 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape79-197" v:mID="79" v:groupContext="shape" transform="translate(610.653,-266.044)">
+ <title>Sheet.79</title>
+ <path d="M0 330.06 C-0 328.17 1.53 326.64 3.42 326.64 L68.64 326.64 C70.53 326.64 72.03 328.17 72.03 330.06 L72.03 343.63
+ C72.03 345.52 70.53 347.02 68.64 347.02 L3.42 347.02 C1.53 347.02 0 345.52 0 343.63 L0 330.06 Z"
+ class="st1"/>
+ </g>
+ <g id="shape80-199" v:mID="80" v:groupContext="shape" transform="translate(610.653,-266.044)">
+ <title>Sheet.80</title>
+ <path d="M0 330.06 C-0 328.17 1.53 326.64 3.42 326.64 L68.64 326.64 C70.53 326.64 72.03 328.17 72.03 330.06 L72.03 343.63
+ C72.03 345.52 70.53 347.02 68.64 347.02 L3.42 347.02 C1.53 347.02 0 345.52 0 343.63 L0 330.06 Z"
+ class="st2"/>
+ </g>
+ <g id="shape81-201" v:mID="81" v:groupContext="shape" transform="translate(633.689,-267.602)">
+ <title>Sheet.81</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape82-205" v:mID="82" v:groupContext="shape" transform="translate(601.889,-225.183)">
+ <title>Sheet.82</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.28 327.48 C89.08 327.48 90.52 328.95 90.52 330.75 L90.52 343.78
+ C90.52 345.58 89.08 347.02 87.28 347.02 L3.27 347.02 C1.47 347.02 0 345.58 0 343.78 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape83-207" v:mID="83" v:groupContext="shape" transform="translate(601.889,-225.183)">
+ <title>Sheet.83</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.28 327.48 C89.08 327.48 90.52 328.95 90.52 330.75 L90.52 343.78
+ C90.52 345.58 89.08 347.02 87.28 347.02 L3.27 347.02 C1.47 347.02 0 345.58 0 343.78 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape84-209" v:mID="84" v:groupContext="shape" transform="translate(610.969,-226.287)">
+ <title>Sheet.84</title>
+ <desc>0x0104DBCA</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.3615" cy="339.824" width="86.73" height="14.3829"/>
+ <path d="M86.72 332.63 L0 332.63 L0 347.02 L86.72 347.02 L86.72 332.63" class="st3"/>
+ <text x="7.12" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0104DBCA</text> </g>
+ <g id="shape85-213" v:mID="85" v:groupContext="shape" transform="translate(626.561,-244.775)">
+ <title>Sheet.85</title>
+ <path d="M0 336.71 L9.84 336.71 L9.84 326.4 L29.53 326.4 L29.53 336.71 L39.38 336.71 L19.69 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape86-215" v:mID="86" v:groupContext="shape" transform="translate(623.619,-210.444)">
+ <title>Sheet.86</title>
+ <path d="M26.41 334.91 C26.41 338.27 25.96 340.96 25.42 340.96 L14.23 340.96 C13.69 340.96 13.21 343.69 13.21 347.02
+ C13.21 343.69 12.76 340.96 12.22 340.96 L1.02 340.96 C0.48 340.96 0 338.27 0 334.91" class="st8"/>
+ </g>
+ <g id="shape87-218" v:mID="87" v:groupContext="shape" transform="translate(242.323,-81.6288)">
+ <title>Sheet.87</title>
+ <path d="M0 281.23 L0 347.02 L41.18 347.02 L41.18 281.23 L0 281.23 L0 281.23 Z" class="st1"/>
+ </g>
+ <g id="shape88-220" v:mID="88" v:groupContext="shape" transform="translate(247.009,-81.6288)">
+ <title>Sheet.88</title>
+ <path d="M0 281.23 L41.18 281.23 L41.18 347.02 L0 347.02 L0 281.23" class="st9"/>
+ </g>
+ <g id="shape89-223" v:mID="89" v:groupContext="shape" transform="translate(245.254,-132.398)">
+ <title>Sheet.89</title>
+ <desc>0x0102</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.3211" cy="341.046" width="42.65" height="11.9384"/>
+ <path d="M42.64 335.08 L0 335.08 L0 347.02 L42.64 347.02 L42.64 335.08" class="st3"/>
+ <text x="4" y="344.03" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102</text> </g>
+ <g id="shape90-227" v:mID="90" v:groupContext="shape" transform="translate(245.015,-82.7016)">
+ <title>Sheet.90</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.79425" cy="341.046" width="9.59" height="11.9384"/>
+ <path d="M9.59 335.08 L0 335.08 L0 347.02 L9.59 347.02 L9.59 335.08" class="st3"/>
+ <text x="1.84" y="344.03" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape91-231" v:mID="91" v:groupContext="shape" transform="translate(336.326,-81.6288)">
+ <title>Sheet.91</title>
+ <path d="M0 281.23 L0 347.02 L41.18 347.02 L41.18 281.23 L0 281.23 L0 281.23 Z" class="st1"/>
+ </g>
+ <g id="shape92-233" v:mID="92" v:groupContext="shape" transform="translate(339.598,-81.6288)">
+ <title>Sheet.92</title>
+ <path d="M0 281.23 L41.18 281.23 L41.18 347.02 L0 347.02 L0 281.23" class="st9"/>
+ </g>
+ <g id="shape93-236" v:mID="93" v:groupContext="shape" transform="translate(339.264,-132.398)">
+ <title>Sheet.93</title>
+ <desc>0x0103</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.3211" cy="341.046" width="42.65" height="11.9384"/>
+ <path d="M42.64 335.08 L0 335.08 L0 347.02 L42.64 347.02 L42.64 335.08" class="st3"/>
+ <text x="4" y="344.03" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0103</text> </g>
+ <g id="shape94-240" v:mID="94" v:groupContext="shape" transform="translate(339.024,-82.7016)">
+ <title>Sheet.94</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.79425" cy="341.046" width="9.59" height="11.9384"/>
+ <path d="M9.59 335.08 L0 335.08 L0 347.02 L9.59 347.02 L9.59 335.08" class="st3"/>
+ <text x="1.84" y="344.03" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape95-244" v:mID="95" v:groupContext="shape" transform="translate(438.598,-81.5089)">
+ <title>Sheet.95</title>
+ <path d="M0 281.23 L0 347.02 L41.18 347.02 L41.18 281.23 L0 281.23 L0 281.23 Z" class="st1"/>
+ </g>
+ <g id="shape96-246" v:mID="96" v:groupContext="shape" transform="translate(438.598,-81.5089)">
+ <title>Sheet.96</title>
+ <path d="M0 281.23 L41.18 281.23 L41.18 347.02 L0 347.02 L0 281.23" class="st9"/>
+ </g>
+ <g id="shape97-249" v:mID="97" v:groupContext="shape" transform="translate(437.81,-132.27)">
+ <title>Sheet.97</title>
+ <desc>0x0104</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.3211" cy="341.046" width="42.65" height="11.9384"/>
+ <path d="M42.64 335.08 L0 335.08 L0 347.02 L42.64 347.02 L42.64 335.08" class="st3"/>
+ <text x="4" y="344.03" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0104</text> </g>
+ <g id="shape98-253" v:mID="98" v:groupContext="shape" transform="translate(437.57,-82.5735)">
+ <title>Sheet.98</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.79425" cy="341.046" width="9.59" height="11.9384"/>
+ <path d="M9.59 335.08 L0 335.08 L0 347.02 L9.59 347.02 L9.59 335.08" class="st3"/>
+ <text x="1.84" y="344.03" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape99-257" v:mID="99" v:groupContext="shape" transform="translate(53.5505,-147.924)">
+ <title>Sheet.99</title>
+ <path d="M0.59 283.52 L206.27 343.39 L205.7 345.34 L0 285.48 L0.59 283.52 L0.59 283.52 ZM205.85 341.14 L210.88 345.79
+ L204.14 347.02 L205.85 341.14 L205.85 341.14 Z" class="st12"/>
+ </g>
+ <g id="shape100-259" v:mID="100" v:groupContext="shape" transform="translate(151.516,-147.924)">
+ <title>Sheet.100</title>
+ <path d="M0.59 283.52 L202.41 343.41 L201.83 345.35 L0 285.48 L0.59 283.52 L0.59 283.52 ZM202.01 341.16 L207.01 345.83
+ L200.27 347.02 L202.01 341.16 L202.01 341.16 Z" class="st13"/>
+ </g>
+ <g id="shape101-261" v:mID="101" v:groupContext="shape" transform="translate(246.975,-147.37)">
+ <title>Sheet.101</title>
+ <path d="M2 283.72 L15.77 341.83 L13.79 342.3 L0 284.18 L2 283.72 L2 283.72 ZM17.53 340.36 L15.97 347.02 L11.57 341.77
+ L17.53 340.36 L17.53 340.36 Z" class="st12"/>
+ </g>
+ <g id="shape102-263" v:mID="102" v:groupContext="shape" transform="translate(262.972,-147.37)">
+ <title>Sheet.102</title>
+ <path d="M82.31 283.13 L3.45 343.12 L4.68 344.74 L83.54 284.76 L82.31 283.13 L82.31 283.13 ZM3.02 340.89 L0 347.02 L6.74
+ 345.74 L3.02 340.89 L3.02 340.89 Z" class="st12"/>
+ </g>
+ <g id="shape103-265" v:mID="103" v:groupContext="shape" transform="translate(358.537,-149.107)">
+ <title>Sheet.103</title>
+ <path d="M83.92 284.85 L3.53 343.2 L4.73 344.84 L85.12 286.5 L83.92 284.85 L83.92 284.85 ZM3.15 340.95 L0 347.02 L6.75
+ 345.89 L3.15 340.95 L3.15 340.95 Z" class="st13"/>
+ </g>
+ <g id="shape104-267" v:mID="104" v:groupContext="shape" transform="translate(264.413,-147.534)">
+ <title>Sheet.104</title>
+ <path d="M275.95 283 L4.77 343.27 L5.22 345.25 L276.37 285 L275.95 283 L275.95 283 ZM5.31 341.05 L0 345.37 L6.66 347.02
+ L5.31 341.05 L5.31 341.05 Z" class="st14"/>
+ </g>
+ <g id="shape105-269" v:mID="105" v:groupContext="shape" transform="translate(456.982,-148.103)">
+ <title>Sheet.105</title>
+ <path d="M179.48 283.72 L4.5 343.48 L5.16 345.43 L180.14 285.66 L179.48 283.72 L179.48 283.72 ZM4.8 341.23 L0 346.12
+ L6.81 347.02 L4.8 341.23 L4.8 341.23 Z" class="st15"/>
+ </g>
+ <g id="shape106-271" v:mID="106" v:groupContext="shape" transform="translate(335.628,-18)">
+ <title>Sheet.106</title>
+ <path d="M0 309.64 C0 305.52 2.99 302.16 6.65 302.16 L14.2 302.16 L8.01 284.85 L35.48 302.16 L78.47 302.16 C82.15 302.16
+ 85.12 305.52 85.12 309.64 L85.12 309.64 L85.12 320.85 L85.12 339.54 C85.12 343.68 82.15 347.02 78.47 347.02
+ L35.48 347.02 L14.2 347.02 L14.2 347.02 L6.65 347.02 C2.99 347.02 0 343.68 0 339.54 L0 320.85 L0 309.64
+ L0 309.64 Z" class="st5"/>
+ </g>
+ <g id="shape109-273" v:mID="109" v:groupContext="shape" transform="translate(157.564,-62.4234)">
+ <title>Sheet.109</title>
+ <path d="M16.21 347.02 C11.74 347.02 8.1 346.42 8.1 345.67 L8.1 303.49 C8.1 302.75 4.49 302.14 0 302.14 C4.49 302.14
+ 8.1 301.54 8.1 300.79 L8.1 258.61 C8.1 257.88 11.74 257.26 16.21 257.26" class="st7"/>
+ </g>
+ <g id="shape110-276" v:mID="110" v:groupContext="shape" transform="translate(113.844,-100.157)">
+ <title>Sheet.110</title>
+ <desc>Groups</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="20.2175" cy="341.046" width="40.44" height="11.9384"/>
+ <path d="M40.44 335.08 L0 335.08 L0 347.02 L40.44 347.02 L40.44 335.08" class="st3"/>
+ <text x="3.85" y="344.03" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Groups</text> </g>
+ <g id="shape111-280" v:mID="111" v:groupContext="shape" transform="translate(196.718,-76.2186)">
+ <title>Sheet.111</title>
+ <path d="M0 331.97 C0 330.32 2.27 328.96 5.04 328.96 L37.61 328.96 L60.43 284.85 L53.72 328.96 L59.43 328.96 C62.22 328.96
+ 64.47 330.32 64.47 331.97 L64.47 331.97 L64.47 336.48 L64.47 344.01 C64.47 345.67 62.22 347.02 59.43 347.02
+ L53.72 347.02 L37.61 347.02 L37.61 347.02 L5.04 347.02 C2.27 347.02 0 345.67 0 344.01 L0 336.48 L0 331.97
+ L0 331.97 Z" class="st5"/>
+ </g>
+ <g id="shape112-282" v:mID="112" v:groupContext="shape" transform="translate(196.65,-80.2991)">
+ <title>Sheet.112</title>
+ <desc>group id</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="27.7691" cy="339.824" width="55.54" height="14.3829"/>
+ <path d="M55.54 332.63 L0 332.63 L0 347.02 L55.54 347.02 L55.54 332.63" class="st3"/>
+ <text x="5.09" y="343.42" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>group id</text> </g>
+ <g id="shape114-286" v:mID="114" v:groupContext="shape" transform="translate(506.433,-128.007)">
+ <title>Sheet.114</title>
+ <desc>-</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="6.63728" cy="336.229" width="13.28" height="21.5726"/>
+ <path d="M13.27 325.44 L0 325.44 L0 347.02 L13.27 347.02 L13.27 325.44" class="st3"/>
+ <text x="3.06" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>-</text> </g>
+ <g id="shape115-290" v:mID="115" v:groupContext="shape" transform="translate(529.004,-128.007)">
+ <title>Sheet.115</title>
+ <desc>Keys separated into</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="97.1729" cy="336.229" width="194.35" height="21.5726"/>
+ <path d="M194.35 325.44 L0 325.44 L0 347.02 L194.35 347.02 L194.35 325.44" class="st3"/>
+ <text x="17.06" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Keys separated into </text> </g>
+ <g id="shape116-294" v:mID="116" v:groupContext="shape" transform="translate(529.004,-106.438)">
+ <title>Sheet.116</title>
+ <desc>groups based on</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="83.1587" cy="336.229" width="166.32" height="21.5726"/>
+ <path d="M166.32 325.44 L0 325.44 L0 347.02 L166.32 347.02 L166.32 325.44" class="st3"/>
+ <text x="15.23" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>groups based on </text> </g>
+ <g id="shape117-298" v:mID="117" v:groupContext="shape" transform="translate(529.004,-84.869)">
+ <title>Sheet.117</title>
+ <desc>some bits from hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="97.731" cy="336.229" width="195.47" height="21.5726"/>
+ <path d="M195.46 325.44 L0 325.44 L0 347.02 L195.46 347.02 L195.46 325.44" class="st3"/>
+ <text x="14.94" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>some bits from hash</text> </g>
+ <g id="shape118-302" v:mID="118" v:groupContext="shape" transform="translate(506.433,-63.2999)">
+ <title>Sheet.118</title>
+ <desc>-</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="6.63728" cy="336.229" width="13.28" height="21.5726"/>
+ <path d="M13.27 325.44 L0 325.44 L0 347.02 L13.27 347.02 L13.27 325.44" class="st3"/>
+ <text x="3.06" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>-</text> </g>
+ <g id="shape119-306" v:mID="119" v:groupContext="shape" transform="translate(529.004,-63.2999)">
+ <title>Sheet.119</title>
+ <desc>Groups contain a</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="84.2539" cy="336.229" width="168.51" height="21.5726"/>
+ <path d="M168.51 325.44 L0 325.44 L0 347.02 L168.51 347.02 L168.51 325.44" class="st3"/>
+ <text x="15.38" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Groups contain a </text> </g>
+ <g id="shape120-310" v:mID="120" v:groupContext="shape" transform="translate(529.004,-41.7308)">
+ <title>Sheet.120</title>
+ <desc>small number of</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="81.4635" cy="336.229" width="162.93" height="21.5726"/>
+ <path d="M162.93 325.44 L0 325.44 L0 347.02 L162.93 347.02 L162.93 325.44" class="st3"/>
+ <text x="15.01" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>small number of </text> </g>
+ <g id="shape121-314" v:mID="121" v:groupContext="shape" transform="translate(529.004,-20.1617)">
+ <title>Sheet.121</title>
+ <desc>keys (<28)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.4481" cy="336.229" width="100.9" height="21.5726"/>
+ <path d="M100.9 325.44 L0 325.44 L0 347.02 L100.9 347.02 L100.9 325.44" class="st3"/>
+ <text x="8.77" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>keys (<28)</text> </g>
+ <g id="shape122-318" v:mID="122" v:groupContext="shape" transform="translate(19.1996,-146.276)">
+ <title>Sheet.122</title>
+ <path d="M0 310.17 C-0 306.1 3.62 302.8 8.07 302.8 L14.46 302.8 L29.68 282.28 L36.14 302.8 L78.65 302.8 C83.11 302.8
+ 86.72 306.1 86.72 310.17 L86.72 310.17 L86.72 321.22 L86.72 339.65 C86.72 343.72 83.11 347.02 78.65 347.02
+ L36.14 347.02 L14.46 347.02 L14.46 347.02 L8.07 347.02 C3.62 347.02 0 343.72 0 339.65 L0 321.22 L0 310.17
+ L0 310.17 Z" class="st5"/>
+ </g>
+ <g id="shape123-320" v:mID="123" v:groupContext="shape" transform="translate(41.9777,-174.053)">
+ <title>Sheet.123</title>
+ <desc>Group</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="22.8289" cy="339.824" width="45.66" height="14.3829"/>
+ <path d="M45.66 332.63 L0 332.63 L0 347.02 L45.66 347.02 L45.66 332.63" class="st3"/>
+ <text x="5.9" y="343.42" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group </text> </g>
+ <g id="shape124-324" v:mID="124" v:groupContext="shape" transform="translate(34.4142,-159.674)">
+ <title>Sheet.124</title>
+ <desc>Identifier</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="31.5173" cy="339.824" width="63.04" height="14.3829"/>
+ <path d="M63.03 332.63 L0 332.63 L0 347.02 L63.03 347.02 L63.03 332.63" class="st3"/>
+ <text x="7.04" y="343.42" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Identifier </text> </g>
+ <g id="shape125-328" v:mID="125" v:groupContext="shape" transform="translate(28.7716,-145.295)">
+ <title>Sheet.125</title>
+ <desc>(simplified)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="36.2165" cy="339.824" width="72.44" height="14.3829"/>
+ <path d="M72.43 332.63 L0 332.63 L0 347.02 L72.43 347.02 L72.43 332.63" class="st3"/>
+ <text x="6.19" y="343.42" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(simplified)</text> </g>
+ <g id="shape127-332" v:mID="127" v:groupContext="shape" transform="translate(517.688,-71.2991)">
+ <title>Sheet.127</title>
+ <desc>Keys separated into groups based on some bits from hash. Grou...</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="112.5" cy="302.139" width="225" height="89.7513"/>
+ <g id="shadow127-333" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st19">
+ <rect x="0" y="257.264" width="225" height="89.7513" class="st20"/>
+ </g>
+ <rect x="0" y="257.264" width="225" height="89.7513" class="st21"/>
+ <text x="4" y="281.09" class="st22" v:langID="1033"><v:paragraph v:indentFirst="-18" v:indentLeft="18" v:bullet="1"/><v:tabList/><tspan
+ class="st23" v:isBullet="true">·</tspan> <tspan class="st24">Keys separated into groups based </tspan><tspan
+ x="22" dy="1.204em" class="st24">on some bits from hash</tspan><tspan class="st24">.<v:newlineChar/></tspan><tspan
+ x="4" dy="1.211em" class="st23" v:isBullet="true">·</tspan> <tspan class="st24">Groups contain a small number of </tspan><tspan
+ x="22" dy="1.204em" class="st24">keys </tspan><tspan class="st24">(</tspan><tspan class="st24"><</tspan><tspan
+ class="st24">28</tspan><tspan class="st24">)</tspan></text> </g>
+ <g id="shape129-349" v:mID="129" v:groupContext="shape" transform="translate(336.326,-26.2991)">
+ <title>Sheet.129</title>
+ <desc>Total # of keys in group so far</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="39.6784" cy="333.515" width="79.36" height="27"/>
+ <rect x="0" y="320.015" width="79.3567" height="27" class="st25"/>
+ <text x="4.5" y="329.92" class="st26" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Total # of keys <tspan
+ x="4.39" dy="1.2em" class="st23">in group so far</tspan></text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i8.svg b/doc/guides/prog_guide/img/efd_i8.svg
new file mode 100644
index 0000000..d0fd463
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i8.svg
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i9.svg Page-2 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="4.98372in" height="2.08442in"
+ viewBox="0 0 358.828 150.078" xml:space="preserve" color-interpolation-filters="sRGB" class="st8">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st4 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st5 {fill:#000000;font-family:Intel Clear;font-size:0.998566em}
+ .st6 {fill:#c00000;font-family:Intel Clear;font-size:0.828804em;font-weight:bold}
+ .st7 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st8 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="4" v:index="2" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-2</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape4-1" v:mID="4" v:groupContext="shape" transform="translate(206.306,-19.0195)">
+ <title>Sheet.4</title>
+ <path d="M0 38.04 L0 150.08 L133.5 150.08 L133.5 38.04 L0 38.04 L0 38.04 Z" class="st1"/>
+ </g>
+ <g id="shape5-3" v:mID="5" v:groupContext="shape" transform="translate(206.306,-19.0195)">
+ <title>Sheet.5</title>
+ <path d="M0 38.04 L133.5 38.04 L133.5 150.08 L0 150.08 L0 38.04" class="st2"/>
+ </g>
+ <g id="shape6-6" v:mID="6" v:groupContext="shape" transform="translate(215.55,-70.7853)">
+ <title>Sheet.6</title>
+ <path d="M0 121.92 C0 118.82 2.54 116.29 5.64 116.29 L110.69 116.29 C113.81 116.29 116.33 118.82 116.33 121.92 L116.33
+ 144.45 C116.33 147.56 113.81 150.08 110.69 150.08 L5.64 150.08 C2.54 150.08 0 147.56 0 144.45 L0 121.92
+ Z" class="st1"/>
+ </g>
+ <g id="shape7-8" v:mID="7" v:groupContext="shape" transform="translate(215.55,-70.7853)">
+ <title>Sheet.7</title>
+ <path d="M0 121.92 C0 118.82 2.54 116.29 5.64 116.29 L110.69 116.29 C113.81 116.29 116.33 118.82 116.33 121.92 L116.33
+ 144.45 C116.33 147.56 113.81 150.08 110.69 150.08 L5.64 150.08 C2.54 150.08 0 147.56 0 144.45 L0 121.92
+ Z" class="st3"/>
+ </g>
+ <g id="shape8-10" v:mID="8" v:groupContext="shape" transform="translate(242.756,-86.1914)">
+ <title>Sheet.8</title>
+ <desc>hash_index</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="36.9951" cy="142.887" width="74" height="14.3829"/>
+ <path d="M73.99 135.7 L0 135.7 L0 150.08 L73.99 150.08 L73.99 135.7" class="st4"/>
+ <text x="6.29" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash_index</text> </g>
+ <g id="shape9-14" v:mID="9" v:groupContext="shape" transform="translate(229.67,-71.812)">
+ <title>Sheet.9</title>
+ <desc>(integer, 16 bits)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="52.0635" cy="142.887" width="104.13" height="14.3829"/>
+ <path d="M104.13 135.7 L0 135.7 L0 150.08 L104.13 150.08 L104.13 135.7" class="st4"/>
+ <text x="8.25" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(integer, 16 bits)</text> </g>
+ <g id="shape10-18" v:mID="10" v:groupContext="shape" transform="translate(215.55,-27.1678)">
+ <title>Sheet.10</title>
+ <path d="M0 121.92 C0 118.82 2.54 116.29 5.64 116.29 L110.69 116.29 C113.81 116.29 116.33 118.82 116.33 121.92 L116.33
+ 144.45 C116.33 147.56 113.81 150.08 110.69 150.08 L5.64 150.08 C2.54 150.08 0 147.56 0 144.45 L0 121.92
+ Z" class="st1"/>
+ </g>
+ <g id="shape11-20" v:mID="11" v:groupContext="shape" transform="translate(215.55,-27.1678)">
+ <title>Sheet.11</title>
+ <path d="M0 121.92 C0 118.82 2.54 116.29 5.64 116.29 L110.69 116.29 C113.81 116.29 116.33 118.82 116.33 121.92 L116.33
+ 144.45 C116.33 147.56 113.81 150.08 110.69 150.08 L5.64 150.08 C2.54 150.08 0 147.56 0 144.45 L0 121.92
+ Z" class="st3"/>
+ </g>
+ <g id="shape12-22" v:mID="12" v:groupContext="shape" transform="translate(237.836,-42.6033)">
+ <title>Sheet.12</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="42.693" cy="142.887" width="85.39" height="14.3829"/>
+ <path d="M85.39 135.7 L0 135.7 L0 150.08 L85.39 150.08 L85.39 135.7" class="st4"/>
+ <text x="7.03" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape13-26" v:mID="13" v:groupContext="shape" transform="translate(251.643,-28.2239)">
+ <title>Sheet.13</title>
+ <desc>(16 bits)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="26.9562" cy="142.887" width="53.92" height="14.3829"/>
+ <path d="M53.91 135.7 L0 135.7 L0 150.08 L53.91 150.08 L53.91 135.7" class="st4"/>
+ <text x="4.98" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(16 bits)</text> </g>
+ <g id="shape14-30" v:mID="14" v:groupContext="shape" transform="translate(213.473,-114.303)">
+ <title>Sheet.14</title>
+ <desc>Group ID: 0x0102</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="47.976" cy="144.109" width="95.96" height="11.9384"/>
+ <path d="M95.95 138.14 L0 138.14 L0 150.08 L95.95 150.08 L95.95 138.14" class="st4"/>
+ <text x="7.47" y="147.09" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group ID: 0x0102</text> </g>
+ <g id="shape15-34" v:mID="15" v:groupContext="shape" transform="translate(19.0195,-99.4242)">
+ <title>Sheet.15</title>
+ <path d="M0 129.31 C0 127.02 1.87 125.15 4.16 125.15 L109.18 125.15 C111.47 125.15 113.33 127.02 113.33 129.31 L113.33
+ 145.93 C113.33 148.22 111.47 150.08 109.18 150.08 L4.16 150.08 C1.87 150.08 0 148.22 0 145.93 L0 129.31
+ Z" class="st1"/>
+ </g>
+ <g id="shape16-36" v:mID="16" v:groupContext="shape" transform="translate(19.0195,-99.4242)">
+ <title>Sheet.16</title>
+ <path d="M0 129.31 C0 127.02 1.87 125.15 4.16 125.15 L109.18 125.15 C111.47 125.15 113.33 127.02 113.33 129.31 L113.33
+ 145.93 C113.33 148.22 111.47 150.08 109.18 150.08 L4.16 150.08 C1.87 150.08 0 148.22 0 145.93 L0 129.31
+ Z" class="st3"/>
+ </g>
+ <g id="shape17-38" v:mID="17" v:groupContext="shape" transform="translate(33.9485,-103.285)">
+ <title>Sheet.17</title>
+ <desc>Key1: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="49.6862" cy="142.887" width="99.38" height="14.3829"/>
+ <path d="M99.37 135.7 L0 135.7 L0 150.08 L99.37 150.08 L99.37 135.7" class="st4"/>
+ <text x="7.94" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1: Value = 0</text> </g>
+ <g id="shape18-42" v:mID="18" v:groupContext="shape" transform="translate(19.0195,-74.6198)">
+ <title>Sheet.18</title>
+ <path d="M0 129.31 C0 127.02 1.87 125.15 4.16 125.15 L109.18 125.15 C111.47 125.15 113.33 127.02 113.33 129.31 L113.33
+ 145.93 C113.33 148.22 111.47 150.08 109.18 150.08 L4.16 150.08 C1.87 150.08 0 148.22 0 145.93 L0 129.31
+ Z" class="st1"/>
+ </g>
+ <g id="shape19-44" v:mID="19" v:groupContext="shape" transform="translate(19.0195,-74.6198)">
+ <title>Sheet.19</title>
+ <path d="M0 129.31 C0 127.02 1.87 125.15 4.16 125.15 L109.18 125.15 C111.47 125.15 113.33 127.02 113.33 129.31 L113.33
+ 145.93 C113.33 148.22 111.47 150.08 109.18 150.08 L4.16 150.08 C1.87 150.08 0 148.22 0 145.93 L0 129.31
+ Z" class="st3"/>
+ </g>
+ <g id="shape20-46" v:mID="20" v:groupContext="shape" transform="translate(33.9485,-78.4626)">
+ <title>Sheet.20</title>
+ <desc>Key3: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="49.6862" cy="142.887" width="99.38" height="14.3829"/>
+ <path d="M99.37 135.7 L0 135.7 L0 150.08 L99.37 150.08 L99.37 135.7" class="st4"/>
+ <text x="7.94" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3: Value = 1</text> </g>
+ <g id="shape21-50" v:mID="21" v:groupContext="shape" transform="translate(19.0195,-49.5757)">
+ <title>Sheet.21</title>
+ <path d="M0 129.21 C0 126.91 1.88 125.03 4.19 125.03 L109.15 125.03 C111.46 125.03 113.33 126.91 113.33 129.21 L113.33
+ 145.91 C113.33 148.21 111.46 150.08 109.15 150.08 L4.19 150.08 C1.88 150.08 0 148.21 0 145.91 L0 129.21
+ Z" class="st1"/>
+ </g>
+ <g id="shape22-52" v:mID="22" v:groupContext="shape" transform="translate(19.0195,-49.5757)">
+ <title>Sheet.22</title>
+ <path d="M0 129.21 C0 126.91 1.88 125.03 4.19 125.03 L109.15 125.03 C111.46 125.03 113.33 126.91 113.33 129.21 L113.33
+ 145.91 C113.33 148.21 111.46 150.08 109.15 150.08 L4.19 150.08 C1.88 150.08 0 148.21 0 145.91 L0 129.21
+ Z" class="st3"/>
+ </g>
+ <g id="shape23-54" v:mID="23" v:groupContext="shape" transform="translate(33.9485,-53.4903)">
+ <title>Sheet.23</title>
+ <desc>Key4: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="49.6862" cy="142.887" width="99.38" height="14.3829"/>
+ <path d="M99.37 135.7 L0 135.7 L0 150.08 L99.37 150.08 L99.37 135.7" class="st4"/>
+ <text x="7.94" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4: Value = 0</text> </g>
+ <g id="shape24-58" v:mID="24" v:groupContext="shape" transform="translate(19.0195,-25.0109)">
+ <title>Sheet.24</title>
+ <path d="M0 129.21 C0 126.91 1.88 125.03 4.19 125.03 L109.15 125.03 C111.46 125.03 113.33 126.91 113.33 129.21 L113.33
+ 145.91 C113.33 148.21 111.46 150.08 109.15 150.08 L4.19 150.08 C1.88 150.08 0 148.21 0 145.91 L0 129.21
+ Z" class="st1"/>
+ </g>
+ <g id="shape25-60" v:mID="25" v:groupContext="shape" transform="translate(19.0195,-25.0109)">
+ <title>Sheet.25</title>
+ <path d="M0 129.21 C0 126.91 1.88 125.03 4.19 125.03 L109.15 125.03 C111.46 125.03 113.33 126.91 113.33 129.21 L113.33
+ 145.91 C113.33 148.21 111.46 150.08 109.15 150.08 L4.19 150.08 C1.88 150.08 0 148.21 0 145.91 L0 129.21
+ Z" class="st3"/>
+ </g>
+ <g id="shape26-62" v:mID="26" v:groupContext="shape" transform="translate(33.9485,-28.927)">
+ <title>Sheet.26</title>
+ <desc>Key7: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="49.6862" cy="142.887" width="99.38" height="14.3829"/>
+ <path d="M99.37 135.7 L0 135.7 L0 150.08 L99.37 150.08 L99.37 135.7" class="st4"/>
+ <text x="7.94" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7: Value = 1</text> </g>
+ <g id="shape27-66" v:mID="27" v:groupContext="shape" transform="translate(141.536,-51.5529)">
+ <title>Sheet.27</title>
+ <path d="M0 115.39 L22.75 115.39 L22.75 103.82 L45.5 126.95 L22.75 150.08 L22.75 138.51 L0 138.51 L0 115.39 Z"
+ class="st7"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i9.svg b/doc/guides/prog_guide/img/efd_i9.svg
new file mode 100644
index 0000000..b2e385d
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i9.svg
@@ -0,0 +1,390 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i10.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="9.8125in" height="3.76365in"
+ viewBox="0 0 706.5 270.983" xml:space="preserve" color-interpolation-filters="sRGB" class="st9">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st4 {fill:#000000;font-family:Arial;font-size:0.998566em}
+ .st5 {fill:#000000;font-family:Arial;font-size:0.918686em}
+ .st6 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st7 {fill:#ffffff;font-family:Arial;font-size:0.998566em}
+ .st8 {fill:#ffffff;font-family:Arial;font-size:1.49785em}
+ .st9 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape68-1" v:mID="68" v:groupContext="shape" transform="translate(196.523,-158.978)">
+ <title>Sheet.68</title>
+ <path d="M0 250.22 C0 247.95 1.89 246.06 4.17 246.06 L317.25 246.06 C319.53 246.06 321.39 247.95 321.39 250.22 L321.39
+ 266.85 C321.39 269.13 319.53 270.98 317.25 270.98 L4.17 270.98 C1.89 270.98 0 269.13 0 266.85 L0 250.22
+ Z" class="st1"/>
+ </g>
+ <g id="shape69-3" v:mID="69" v:groupContext="shape" transform="translate(196.523,-158.978)">
+ <title>Sheet.69</title>
+ <path d="M0 250.22 C0 247.95 1.89 246.06 4.17 246.06 L317.25 246.06 C319.53 246.06 321.39 247.95 321.39 250.22 L321.39
+ 266.85 C321.39 269.13 319.53 270.98 317.25 270.98 L4.17 270.98 C1.89 270.98 0 269.13 0 266.85 L0 250.22
+ Z" class="st2"/>
+ </g>
+ <g id="shape70-5" v:mID="70" v:groupContext="shape" transform="translate(186.139,-162.437)">
+ <title>Sheet.70</title>
+ <desc>(hash(key, seed1) + hash_index *</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="106.671" cy="263.792" width="213.35" height="14.3829"/>
+ <path d="M213.34 256.6 L0 256.6 L0 270.98 L213.34 270.98 L213.34 256.6" class="st3"/>
+ <text x="17.24" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(hash(key, seed1) + hash_index * </text> </g>
+ <g id="shape71-9" v:mID="71" v:groupContext="shape" transform="translate(381.48,-162.845)">
+ <title>Sheet.71</title>
+ <desc>hash(key</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="27.4843" cy="264.367" width="54.97" height="13.2327"/>
+ <path d="M54.97 257.75 L0 257.75 L0 270.98 L54.97 270.98 L54.97 257.75" class="st3"/>
+ <text x="5.12" y="267.67" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash(key</text> </g>
+ <g id="shape72-13" v:mID="72" v:groupContext="shape" transform="translate(424.755,-162.437)">
+ <title>Sheet.72</title>
+ <desc>, seed2)) % 16</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="46.7254" cy="263.792" width="93.46" height="14.3829"/>
+ <path d="M93.45 256.6 L0 256.6 L0 270.98 L93.45 270.98 L93.45 256.6" class="st3"/>
+ <text x="7.76" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>, seed2)) % 16</text> </g>
+ <g id="shape73-17" v:mID="73" v:groupContext="shape" transform="translate(524.094,-148.373)">
+ <title>Sheet.73</title>
+ <path d="M0 236.29 L22.75 236.29 L22.75 224.73 L45.5 247.86 L22.75 270.98 L22.75 259.42 L0 259.42 L0 236.29 Z"
+ class="st6"/>
+ </g>
+ <g id="shape74-19" v:mID="74" v:groupContext="shape" transform="translate(574.148,-217.574)">
+ <title>Sheet.74</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st1"/>
+ </g>
+ <g id="shape75-21" v:mID="75" v:groupContext="shape" transform="translate(574.148,-217.574)">
+ <title>Sheet.75</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st2"/>
+ </g>
+ <g id="shape76-23" v:mID="76" v:groupContext="shape" transform="translate(584.296,-231.499)">
+ <title>Sheet.76</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="40.993" cy="263.792" width="81.99" height="14.3829"/>
+ <path d="M81.99 256.6 L0 256.6 L0 270.98 L81.99 270.98 L81.99 256.6" class="st3"/>
+ <text x="7.01" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape77-27" v:mID="77" v:groupContext="shape" transform="translate(655.369,-231.499)">
+ <title>Sheet.77</title>
+ <desc>bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1076" cy="263.792" width="22.22" height="14.3829"/>
+ <path d="M22.22 256.6 L0 256.6 L0 270.98 L22.22 270.98 L22.22 256.6" class="st3"/>
+ <text x="4.78" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit </text> </g>
+ <g id="shape78-31" v:mID="78" v:groupContext="shape" transform="translate(588.858,-217.12)">
+ <title>Sheet.78</title>
+ <desc>index for key1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="44.8113" cy="263.792" width="89.63" height="14.3829"/>
+ <path d="M89.62 256.6 L0 256.6 L0 270.98 L89.62 270.98 L89.62 256.6" class="st3"/>
+ <text x="7.51" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>index for key1</text> </g>
+ <g id="shape79-35" v:mID="79" v:groupContext="shape" transform="translate(573.548,-178.869)">
+ <title>Sheet.79</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st1"/>
+ </g>
+ <g id="shape80-37" v:mID="80" v:groupContext="shape" transform="translate(573.548,-178.869)">
+ <title>Sheet.80</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st2"/>
+ </g>
+ <g id="shape81-39" v:mID="81" v:groupContext="shape" transform="translate(584.296,-192.768)">
+ <title>Sheet.81</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="40.993" cy="263.792" width="81.99" height="14.3829"/>
+ <path d="M81.99 256.6 L0 256.6 L0 270.98 L81.99 270.98 L81.99 256.6" class="st3"/>
+ <text x="7.01" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape82-43" v:mID="82" v:groupContext="shape" transform="translate(655.369,-192.768)">
+ <title>Sheet.82</title>
+ <desc>bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1076" cy="263.792" width="22.22" height="14.3829"/>
+ <path d="M22.22 256.6 L0 256.6 L0 270.98 L22.22 270.98 L22.22 256.6" class="st3"/>
+ <text x="4.78" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit </text> </g>
+ <g id="shape83-47" v:mID="83" v:groupContext="shape" transform="translate(588.858,-178.388)">
+ <title>Sheet.83</title>
+ <desc>index for key3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="44.8113" cy="263.792" width="89.63" height="14.3829"/>
+ <path d="M89.62 256.6 L0 256.6 L0 270.98 L89.62 270.98 L89.62 256.6" class="st3"/>
+ <text x="7.51" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>index for key3</text> </g>
+ <g id="shape84-51" v:mID="84" v:groupContext="shape" transform="translate(574.148,-139.326)">
+ <title>Sheet.84</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st1"/>
+ </g>
+ <g id="shape85-53" v:mID="85" v:groupContext="shape" transform="translate(574.148,-139.326)">
+ <title>Sheet.85</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st2"/>
+ </g>
+ <g id="shape86-55" v:mID="86" v:groupContext="shape" transform="translate(584.296,-153.227)">
+ <title>Sheet.86</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="40.993" cy="263.792" width="81.99" height="14.3829"/>
+ <path d="M81.99 256.6 L0 256.6 L0 270.98 L81.99 270.98 L81.99 256.6" class="st3"/>
+ <text x="7.01" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape87-59" v:mID="87" v:groupContext="shape" transform="translate(655.369,-153.227)">
+ <title>Sheet.87</title>
+ <desc>bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1076" cy="263.792" width="22.22" height="14.3829"/>
+ <path d="M22.22 256.6 L0 256.6 L0 270.98 L22.22 270.98 L22.22 256.6" class="st3"/>
+ <text x="4.78" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit </text> </g>
+ <g id="shape88-63" v:mID="88" v:groupContext="shape" transform="translate(588.858,-138.848)">
+ <title>Sheet.88</title>
+ <desc>index for key4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="44.8113" cy="263.792" width="89.63" height="14.3829"/>
+ <path d="M89.62 256.6 L0 256.6 L0 270.98 L89.62 270.98 L89.62 256.6" class="st3"/>
+ <text x="7.51" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>index for key4</text> </g>
+ <g id="shape89-67" v:mID="89" v:groupContext="shape" transform="translate(574.148,-100.622)">
+ <title>Sheet.89</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st1"/>
+ </g>
+ <g id="shape90-69" v:mID="90" v:groupContext="shape" transform="translate(574.148,-100.622)">
+ <title>Sheet.90</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st2"/>
+ </g>
+ <g id="shape91-71" v:mID="91" v:groupContext="shape" transform="translate(584.296,-114.496)">
+ <title>Sheet.91</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="40.993" cy="263.792" width="81.99" height="14.3829"/>
+ <path d="M81.99 256.6 L0 256.6 L0 270.98 L81.99 270.98 L81.99 256.6" class="st3"/>
+ <text x="7.01" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape92-75" v:mID="92" v:groupContext="shape" transform="translate(655.369,-114.496)">
+ <title>Sheet.92</title>
+ <desc>bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1076" cy="263.792" width="22.22" height="14.3829"/>
+ <path d="M22.22 256.6 L0 256.6 L0 270.98 L22.22 270.98 L22.22 256.6" class="st3"/>
+ <text x="4.78" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit </text> </g>
+ <g id="shape93-79" v:mID="93" v:groupContext="shape" transform="translate(588.858,-100.117)">
+ <title>Sheet.93</title>
+ <desc>index for key7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="44.8113" cy="263.792" width="89.63" height="14.3829"/>
+ <path d="M89.62 256.6 L0 256.6 L0 270.98 L89.62 270.98 L89.62 256.6" class="st3"/>
+ <text x="7.51" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>index for key7</text> </g>
+ <g id="shape94-83" v:mID="94" v:groupContext="shape" transform="translate(205.227,-191.137)">
+ <title>Sheet.94</title>
+ <path d="M0 217.76 C0 213 3.87 209.14 8.64 209.14 L14.53 209.14 L14.53 209.14 L36.32 209.14 L78.52 209.14 C83.3 209.14
+ 87.16 213 87.16 217.76 L87.16 239.33 L87.16 239.33 L87.16 252.27 L87.16 252.27 C87.16 257.05 83.3 260.9
+ 78.52 260.9 L36.32 260.9 L18.46 270.98 L14.53 260.9 L8.64 260.9 C3.87 260.9 0 257.05 0 252.27 L0 239.33
+ L0 239.33 L0 217.76 Z" class="st6"/>
+ </g>
+ <g id="shape95-85" v:mID="95" v:groupContext="shape" transform="translate(214.98,-225.215)">
+ <title>Sheet.95</title>
+ <desc>CRC32 (32</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="38.2947" cy="263.792" width="76.59" height="14.3829"/>
+ <path d="M76.59 256.6 L0 256.6 L0 270.98 L76.59 270.98 L76.59 256.6" class="st3"/>
+ <text x="8.33" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>CRC32 (32 </text> </g>
+ <g id="shape96-89" v:mID="96" v:groupContext="shape" transform="translate(222.123,-210.835)">
+ <title>Sheet.96</title>
+ <desc>bit output)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="32.5584" cy="263.792" width="65.12" height="14.3829"/>
+ <path d="M65.12 256.6 L0 256.6 L0 270.98 L65.12 270.98 L65.12 256.6" class="st3"/>
+ <text x="5.91" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit output)</text> </g>
+ <g id="shape97-93" v:mID="97" v:groupContext="shape" transform="translate(305.473,-188.366)">
+ <title>Sheet.97</title>
+ <path d="M0 226.84 C0 223.28 2.9 220.39 6.47 220.39 L21.37 220.39 L21.37 220.39 L53.42 220.39 L121.77 220.39 C125.34
+ 220.39 128.22 223.28 128.22 226.84 L128.22 242.97 L128.22 242.97 L128.22 252.65 L128.22 252.65 C128.22 256.21
+ 125.34 259.09 121.77 259.09 L53.42 259.09 L38.73 270.98 L21.37 259.09 L6.47 259.09 C2.9 259.09 0 256.21
+ 0 252.65 L0 242.97 L0 242.97 L0 226.84 Z" class="st6"/>
+ </g>
+ <g id="shape98-95" v:mID="98" v:groupContext="shape" transform="translate(318.48,-217.733)">
+ <title>Sheet.98</title>
+ <desc>Goal: Find a valid</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="57.4478" cy="263.792" width="114.9" height="14.3829"/>
+ <path d="M114.9 256.6 L0 256.6 L0 270.98 L114.9 270.98 L114.9 256.6" class="st3"/>
+ <text x="10.82" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Goal: Find a valid </text> </g>
+ <g id="shape99-99" v:mID="99" v:groupContext="shape" transform="translate(339.077,-203.354)">
+ <title>Sheet.99</title>
+ <desc>hash_index</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="37.1611" cy="263.792" width="74.33" height="14.3829"/>
+ <path d="M74.32 256.6 L0 256.6 L0 270.98 L74.32 270.98 L74.32 256.6" class="st3"/>
+ <text x="6.51" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash_index</text> </g>
+ <g id="shape100-103" v:mID="100" v:groupContext="shape" transform="translate(438.135,-185.939)">
+ <title>Sheet.100</title>
+ <path d="M0 217.36 C0 213.8 2.91 210.89 6.48 210.89 L21.37 210.89 L21.37 210.89 L53.42 210.89 L121.77 210.89 C125.34
+ 210.89 128.22 213.8 128.22 217.36 L128.22 233.48 L128.22 233.48 L128.22 243.15 L128.22 243.15 C128.22 246.72
+ 125.34 249.59 121.77 249.59 L53.42 249.59 L54.75 270.98 L21.37 249.59 L6.48 249.59 C2.91 249.59 0 246.72
+ 0 243.15 L0 233.48 L0 233.48 L0 217.36 Z" class="st6"/>
+ </g>
+ <g id="shape101-105" v:mID="101" v:groupContext="shape" transform="translate(448.763,-224.802)">
+ <title>Sheet.101</title>
+ <desc>Lookup Table has</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.6085" cy="263.792" width="117.22" height="14.3829"/>
+ <path d="M117.22 256.6 L0 256.6 L0 270.98 L117.22 270.98 L117.22 256.6" class="st3"/>
+ <text x="10.98" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Lookup Table has </text> </g>
+ <g id="shape102-109" v:mID="102" v:groupContext="shape" transform="translate(484.549,-210.423)">
+ <title>Sheet.102</title>
+ <desc>16 bits</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="22.2166" cy="263.792" width="44.44" height="14.3829"/>
+ <path d="M44.43 256.6 L0 256.6 L0 270.98 L44.43 270.98 L44.43 256.6" class="st3"/>
+ <text x="4.56" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>16 bits</text> </g>
+ <g id="shape103-113" v:mID="103" v:groupContext="shape" transform="translate(369.583,-90.8555)">
+ <title>Sheet.103</title>
+ <path d="M0 227.76 C0 222.98 3.89 219.1 8.67 219.1 L14.53 219.1 L34.47 205.09 L36.32 219.1 L78.5 219.1 C83.29 219.1 87.16
+ 222.98 87.16 227.76 L87.16 227.76 L87.16 240.73 L87.16 262.34 C87.16 267.12 83.29 270.98 78.5 270.98 L36.32
+ 270.98 L14.53 270.98 L14.53 270.98 L8.67 270.98 C3.89 270.98 0 267.12 0 262.34 L0 240.73 L0 227.76 L0 227.76
+ Z" class="st6"/>
+ </g>
+ <g id="shape104-115" v:mID="104" v:groupContext="shape" transform="translate(383.264,-114.932)">
+ <title>Sheet.104</title>
+ <desc>CRC32 (32</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="38.2947" cy="263.792" width="76.59" height="14.3829"/>
+ <path d="M76.59 256.6 L0 256.6 L0 270.98 L76.59 270.98 L76.59 256.6" class="st3"/>
+ <text x="8.33" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>CRC32 (32 </text> </g>
+ <g id="shape105-119" v:mID="105" v:groupContext="shape" transform="translate(386.505,-100.553)">
+ <title>Sheet.105</title>
+ <desc>bit output)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="32.5584" cy="263.792" width="65.12" height="14.3829"/>
+ <path d="M65.12 256.6 L0 256.6 L0 270.98 L65.12 270.98 L65.12 256.6" class="st3"/>
+ <text x="5.91" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit output)</text> </g>
+ <g id="shape106-123" v:mID="106" v:groupContext="shape" transform="translate(313.397,-18)">
+ <title>Sheet.106</title>
+ <path d="M0 226.35 C0 221.43 4.02 217.42 8.94 217.42 L347.02 217.42 C351.97 217.42 355.96 221.43 355.96 226.35 L355.96
+ 262.06 C355.96 267 351.97 270.98 347.02 270.98 L8.94 270.98 C4.02 270.98 0 267 0 262.06 L0 226.35 Z"
+ class="st6"/>
+ </g>
+ <g id="shape107-125" v:mID="107" v:groupContext="shape" transform="translate(313.98,-41.963)">
+ <title>Sheet.107</title>
+ <desc>Goal is to find a hash_index that produces</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="177.75" cy="260.197" width="355.5" height="21.5726"/>
+ <path d="M355.5 249.41 L0 249.41 L0 270.98 L355.5 270.98 L355.5 249.41" class="st3"/>
+ <text x="9.88" y="265.59" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Goal is to find a hash_index that produces </text> </g>
+ <g id="shape108-129" v:mID="108" v:groupContext="shape" transform="translate(318.48,-20.3939)">
+ <title>Sheet.108</title>
+ <desc>a lookup_table with no contradictions</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="175.5" cy="260.197" width="351" height="21.5726"/>
+ <path d="M351 249.41 L0 249.41 L0 270.98 L351 270.98 L351 249.41" class="st3"/>
+ <text x="28.12" y="265.59" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>a lookup_table with no contradictions</text> </g>
+ <g id="shape109-133" v:mID="109" v:groupContext="shape" transform="translate(18,-196.244)">
+ <title>Sheet.109</title>
+ <path d="M0 250.22 C0 247.92 1.87 246.06 4.16 246.06 L109.18 246.06 C111.47 246.06 113.33 247.92 113.33 250.22 L113.33
+ 266.83 C113.33 269.13 111.47 270.98 109.18 270.98 L4.16 270.98 C1.87 270.98 0 269.13 0 266.83 L0 250.22
+ Z" class="st1"/>
+ </g>
+ <g id="shape110-135" v:mID="110" v:groupContext="shape" transform="translate(29.8201,-196.244)">
+ <title>Sheet.110</title>
+ <path d="M0 250.22 C-0 247.92 1.67 246.06 3.73 246.06 L97.79 246.06 C99.85 246.06 101.51 247.92 101.51 250.22 L101.51
+ 266.83 C101.51 269.13 99.85 270.98 97.79 270.98 L3.73 270.98 C1.67 270.98 0 269.13 0 266.83 L0 250.22 Z"
+ class="st2"/>
+ </g>
+ <g id="shape111-137" v:mID="111" v:groupContext="shape" transform="translate(32.5663,-199.746)">
+ <title>Sheet.111</title>
+ <desc>Key1: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.7562" cy="263.792" width="101.52" height="14.3829"/>
+ <path d="M101.51 256.6 L0 256.6 L0 270.98 L101.51 270.98 L101.51 256.6" class="st3"/>
+ <text x="8.29" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1: Value = 0</text> </g>
+ <g id="shape112-141" v:mID="112" v:groupContext="shape" transform="translate(18,-171.44)">
+ <title>Sheet.112</title>
+ <path d="M0 250.22 C0 247.92 1.87 246.06 4.16 246.06 L109.18 246.06 C111.47 246.06 113.33 247.92 113.33 250.22 L113.33
+ 266.83 C113.33 269.13 111.47 270.98 109.18 270.98 L4.16 270.98 C1.87 270.98 0 269.13 0 266.83 L0 250.22
+ Z" class="st1"/>
+ </g>
+ <g id="shape113-143" v:mID="113" v:groupContext="shape" transform="translate(29.8201,-171.44)">
+ <title>Sheet.113</title>
+ <path d="M0 250.22 C0 247.92 1.67 246.06 3.73 246.06 L97.79 246.06 C99.85 246.06 101.51 247.92 101.51 250.22 L101.51
+ 266.83 C101.51 269.13 99.85 270.98 97.79 270.98 L3.73 270.98 C1.67 270.98 0 269.13 0 266.83 L0 250.22 Z"
+ class="st2"/>
+ </g>
+ <g id="shape114-145" v:mID="114" v:groupContext="shape" transform="translate(32.5663,-174.923)">
+ <title>Sheet.114</title>
+ <desc>Key3: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.7562" cy="263.792" width="101.52" height="14.3829"/>
+ <path d="M101.51 256.6 L0 256.6 L0 270.98 L101.51 270.98 L101.51 256.6" class="st3"/>
+ <text x="8.29" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3: Value = 1</text> </g>
+ <g id="shape115-149" v:mID="115" v:groupContext="shape" transform="translate(18,-146.396)">
+ <title>Sheet.115</title>
+ <path d="M0 250.12 C0 247.81 1.88 245.94 4.19 245.94 L109.15 245.94 C111.46 245.94 113.33 247.81 113.33 250.12 L113.33
+ 266.81 C113.33 269.12 111.46 270.98 109.15 270.98 L4.19 270.98 C1.88 270.98 0 269.12 0 266.81 L0 250.12
+ Z" class="st1"/>
+ </g>
+ <g id="shape116-151" v:mID="116" v:groupContext="shape" transform="translate(29.8201,-146.396)">
+ <title>Sheet.116</title>
+ <path d="M0 250.12 C0 247.81 1.68 245.94 3.75 245.94 L97.77 245.94 C99.84 245.94 101.51 247.81 101.51 250.12 L101.51
+ 266.81 C101.51 269.12 99.84 270.98 97.77 270.98 L3.75 270.98 C1.68 270.98 0 269.12 0 266.81 L0 250.12 Z"
+ class="st2"/>
+ </g>
+ <g id="shape117-153" v:mID="117" v:groupContext="shape" transform="translate(32.5663,-149.951)">
+ <title>Sheet.117</title>
+ <desc>Key4: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.7562" cy="263.792" width="101.52" height="14.3829"/>
+ <path d="M101.51 256.6 L0 256.6 L0 270.98 L101.51 270.98 L101.51 256.6" class="st3"/>
+ <text x="8.29" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4: Value = 0</text> </g>
+ <g id="shape118-157" v:mID="118" v:groupContext="shape" transform="translate(18,-121.831)">
+ <title>Sheet.118</title>
+ <path d="M0 250.12 C0 247.81 1.88 245.94 4.19 245.94 L109.15 245.94 C111.46 245.94 113.33 247.81 113.33 250.12 L113.33
+ 266.81 C113.33 269.12 111.46 270.98 109.15 270.98 L4.19 270.98 C1.88 270.98 0 269.12 0 266.81 L0 250.12
+ Z" class="st1"/>
+ </g>
+ <g id="shape119-159" v:mID="119" v:groupContext="shape" transform="translate(29.8201,-121.831)">
+ <title>Sheet.119</title>
+ <path d="M0 250.12 C0 247.81 1.68 245.94 3.75 245.94 L97.77 245.94 C99.84 245.94 101.51 247.81 101.51 250.12 L101.51
+ 266.81 C101.51 269.12 99.84 270.98 97.77 270.98 L3.75 270.98 C1.68 270.98 0 269.12 0 266.81 L0 250.12 Z"
+ class="st2"/>
+ </g>
+ <g id="shape120-161" v:mID="120" v:groupContext="shape" transform="translate(32.5663,-125.388)">
+ <title>Sheet.120</title>
+ <desc>Key7: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.7562" cy="263.792" width="101.52" height="14.3829"/>
+ <path d="M101.51 256.6 L0 256.6 L0 270.98 L101.51 270.98 L101.51 256.6" class="st3"/>
+ <text x="8.29" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7: Value = 1</text> </g>
+ <g id="shape121-165" v:mID="121" v:groupContext="shape" transform="translate(140.517,-148.373)">
+ <title>Sheet.121</title>
+ <path d="M0 236.29 L22.75 236.29 L22.75 224.73 L45.5 247.86 L22.75 270.98 L22.75 259.42 L0 259.42 L0 236.29 Z"
+ class="st6"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/index.rst b/doc/guides/prog_guide/index.rst
index ed7f770..7f825cb 100644
--- a/doc/guides/prog_guide/index.rst
+++ b/doc/guides/prog_guide/index.rst
@@ -47,6 +47,7 @@ Programmer's Guide
link_bonding_poll_mode_drv_lib
timer_lib
hash_lib
+ efd_lib
lpm_lib
lpm6_lib
packet_distrib_lib
@@ -167,6 +168,28 @@ Programmer's Guide
:numref:`figure_figure39` :ref:`figure_figure39`
+:numref:`figure_efd1` :ref:`figure_efd1`
+
+:numref:`figure_efd2` :ref:`figure_efd2`
+
+:numref:`figure_efd3` :ref:`figure_efd3`
+
+:numref:`figure_efd4` :ref:`figure_efd4`
+
+:numref:`figure_efd5` :ref:`figure_efd5`
+
+:numref:`figure_efd6` :ref:`figure_efd6`
+
+:numref:`figure_efd7` :ref:`figure_efd7`
+
+:numref:`figure_efd8` :ref:`figure_efd8`
+
+:numref:`figure_efd9` :ref:`figure_efd9`
+
+:numref:`figure_efd10` :ref:`figure_efd10`
+
+:numref:`figure_efd11` :ref:`figure_efd11`
+
**Tables**
diff --git a/doc/guides/rel_notes/release_17_02.rst b/doc/guides/rel_notes/release_17_02.rst
index 8673d22..7dff3a2 100644
--- a/doc/guides/rel_notes/release_17_02.rst
+++ b/doc/guides/rel_notes/release_17_02.rst
@@ -68,6 +68,9 @@ New Features
is much smaller than a hash-based flow table and therefore, it can better fit for
CPU cache, being able to scale to millions of flow keys.
+ See the :ref:`Elastic Flow Distributor Library <Efd_Library>` documentation in
+ the Programmers Guide document, for more information.
+
Resolved Issues
---------------
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v6 5/5] doc: add flow distributor guide
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 " Pablo de Lara
` (3 preceding siblings ...)
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 4/5] doc: add EFD library section in Programmers guide Pablo de Lara
@ 2017-01-16 19:21 ` Pablo de Lara
2017-01-17 20:35 ` Thomas Monjalon
2017-01-17 20:29 ` [dpdk-dev] [PATCH v6 0/5] Elastic Flow Distributor Thomas Monjalon
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 0/6] " Pablo de Lara
6 siblings, 1 reply; 63+ messages in thread
From: Pablo de Lara @ 2017-01-16 19:21 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Sameh Gobriel
Signed-off-by: Sameh Gobriel <sameh.gobriel@intel.com>
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 1 +
doc/guides/sample_app_ug/flow_distributor.rst | 494 ++++++++
doc/guides/sample_app_ug/img/flow_distributor.svg | 1254 +++++++++++++++++++++
doc/guides/sample_app_ug/index.rst | 1 +
4 files changed, 1750 insertions(+)
create mode 100644 doc/guides/sample_app_ug/flow_distributor.rst
create mode 100644 doc/guides/sample_app_ug/img/flow_distributor.svg
diff --git a/MAINTAINERS b/MAINTAINERS
index 66e9466..0d3b247 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -535,6 +535,7 @@ F: lib/librte_efd/
F: doc/guides/prog_guide/efd_lib.rst
F: app/test/test_efd*
F: examples/flow_distributor/
+F: doc/guides/sample_app_ug/flow_distributor.rst
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
diff --git a/doc/guides/sample_app_ug/flow_distributor.rst b/doc/guides/sample_app_ug/flow_distributor.rst
new file mode 100644
index 0000000..a8cd5f4
--- /dev/null
+++ b/doc/guides/sample_app_ug/flow_distributor.rst
@@ -0,0 +1,494 @@
+.. BSD LICENSE
+ Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of Intel Corporation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Flow Distributor Sample Application
+===================================
+
+This sample application demonstrates the use of EFD library as a flow-level
+load balancer, for more information about the EFD Library please refer to the
+DPDK programmer's guide.
+
+This sample application is a variant of the
+:ref:`client-server sample application <multi_process_app>`
+where a specific target node is specified for every and each flow
+(not in a round-robin fashion as the original load balancing sample application).
+
+Overview
+--------
+
+The architecture of the EFD flow-based load balancer sample application is
+presented in the following figure.
+
+.. _figure_efd_sample_app_overview:
+
+.. figure:: img/flow_distributor.*
+
+ Using EFD as a Flow-Level Load Balancer
+
+As shown in :numref:`_figure_efd_sample_app_overview`,
+the sample application consists of a front-end node (distributor)
+using the EFD library to create a load-balancing table for flows,
+for each flow a target backend worker node is specified. The EFD table does not
+store the flow key (unlike a regular hash table), and hence, it can
+individually load-balance millions of flows (number of targets * maximum number
+of flows fit in a flow table per target) while still fitting in CPU cache.
+
+It should be noted that although they are referred to as nodes, the frontend
+distributor and worker nodes are processes running on the same platform.
+
+Front-end Distributor
+~~~~~~~~~~~~~~~~~~~~~
+
+Upon initializing, the frontend distributor node (process) creates a flow
+distributor table (based on the EFD library) which is populated with flow
+information and its intended target node.
+
+The sample application assigns a specific target node_id (process) for each of
+the IP destination addresses as follows:
+
+.. code-block:: c
+
+ node_id = i % num_nodes; /* Target node id is generated */
+ ip_dst = rte_cpu_to_be_32(i); /* Specific ip destination address is
+ assigned to this target node */
+
+then the pair of <key,target> is inserted into the flow distribution table.
+
+The main loop of the the distributor node receives a burst of packets, then for
+each packet, a flow key (IP destination address) is extracted. The flow
+distributor table is looked up and the target node id is returned. Packets are
+then enqueued to the specified target node id.
+
+It should be noted that flow distributor table is not a membership test table.
+I.e. if the key has already been inserted the target node id will be correct,
+but for new keys the flow distributor table will return a value (which can be
+valid).
+
+Backend Worker Nodes
+~~~~~~~~~~~~~~~~~~~~
+
+Upon initializing, the worker node (process) creates a flow table (a regular
+hash table that stores the key default size 1M flows) which is populated with
+only the flow information that is serviced at this node. This flow key is
+essential to point out new keys that have not been inserted before.
+
+The worker node's main loop is simply receiving packets then doing a hash table
+lookup. If a match occurs then statistics are updated for flows serviced by
+this node. If no match is found in the local hash table then this indicates
+that this is a new flow, which is dropped.
+
+
+Compiling the Application
+-------------------------
+
+The sequence of steps used to build the application is:
+
+#. Export the required environment variables:
+
+ .. code-block:: console
+
+ export RTE_SDK=/path/to/rte_sdk
+ export RTE_TARGET=x86_64-native-linuxapp-gcc
+
+#. Build the application executable file:
+
+ .. code-block:: console
+
+ cd ${RTE_SDK}/examples/flow_distributor/
+ make
+
+ For more details on how to build the DPDK libraries and sample
+ applications,
+ please refer to the *DPDK Getting Started Guide.*
+
+
+Running the Application
+-----------------------
+
+The application has two binaries to be run: the front-end distributor
+and the back-end node.
+
+The frontend distributor (distributor) has the following command line options::
+
+ ./distributor [EAL options] -- -p PORTMASK -n NUM_NODES -f NUM_FLOWS
+
+Where,
+
+* ``-p PORTMASK:`` Hexadecimal bitmask of ports to configure
+* ``-n NUM_NODES:`` Number of back-end nodes that will be used
+* ``-f NUM_FLOWS:`` Number of flows to be added in the EFD table (1 million, by default)
+
+The back-end node (node) has the following command line options::
+
+ ./node [EAL options] -- -n NODE_ID
+
+Where,
+
+* ``-n NODE_ID:`` Node ID, which cannot be equal or higher than NUM_MODES
+
+
+First, the distributor app must be launched, with the number of nodes that will be run.
+Once it has been started, the node instances can be run, with different NODE_ID.
+These instances have to be run as secondary processes, with ``--proc-type=secondary``
+in the EAL options, which will attach to the primary process memory, and therefore,
+they can access the queues created by the primary process to distribute packets.
+
+To successfully run the application, the command line used to start the
+application has to be in sync with the traffic flows configured on the traffic
+generator side.
+
+For examples of application command lines and traffic generator flows, please
+refer to the DPDK Test Report. For more details on how to set up and run the
+sample applications provided with DPDK package, please refer to the
+:ref:`DPDK Getting Started Guide for Linux <linux_gsg>` and
+:ref:`DPDK Getting Started Guide for FreeBSD <freebsd_gsg>`.
+
+
+Explanation
+-----------
+
+As described in previous sections, there are two processes in this example.
+
+The first process, the front-end distributor, creates and populates the EFD table,
+which is used to distribute packets to nodes, which the number of flows
+specified in the command line (1 million, by default).
+
+
+.. code-block:: c
+
+ static void
+ create_flow_distributor_table(void)
+ {
+ uint8_t socket_id = rte_socket_id();
+
+ /* create table */
+ efd_table = rte_efd_create("flow table", num_flows * 2, sizeof(uint32_t),
+ 1 << socket_id, socket_id);
+
+ if (efd_table == NULL)
+ rte_exit(EXIT_FAILURE, "Problem creating the flow table\n");
+ }
+
+ static void
+ populate_flow_distributor_table(void)
+ {
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint8_t socket_id = rte_socket_id();
+ uint64_t node_id;
+
+ /* Add flows in table */
+ for (i = 0; i < num_flows; i++) {
+ node_id = i % num_nodes;
+
+ ip_dst = rte_cpu_to_be_32(i);
+ ret = rte_efd_update(efd_table, socket_id,
+ (void *)&ip_dst, (efd_value_t)node_id);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u in "
+ "flow distributor table\n", i);
+ }
+
+ printf("EFD table: Adding 0x%x keys\n", num_flows);
+ }
+
+After initialization, packets are received from the enabled ports, and the IPv4
+address from the packets is used as a key to look up in the EFD table,
+which tells the node where the packet has to be distributed.
+
+.. code-block:: c
+
+ static void
+ process_packets(uint32_t port_num __rte_unused, struct rte_mbuf *pkts[],
+ uint16_t rx_count, unsigned int socket_id)
+ {
+ uint16_t i;
+ uint8_t node;
+ efd_value_t data[EFD_BURST_MAX];
+ const void *key_ptrs[EFD_BURST_MAX];
+
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[EFD_BURST_MAX];
+
+ for (i = 0; i < rx_count; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(pkts[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = (void *)&ipv4_dst_ip[i];
+ }
+
+ rte_efd_lookup_bulk(efd_table, socket_id, rx_count,
+ (const void **) key_ptrs, data);
+ for (i = 0; i < rx_count; i++) {
+ node = (uint8_t) ((uintptr_t)data[i]);
+
+ if (node >= num_nodes) {
+ /*
+ * Node is out of range, which means that
+ * flow has not been inserted
+ */
+ flow_dist_stats.drop++;
+ rte_pktmbuf_free(pkts[i]);
+ } else {
+ flow_dist_stats.distributed++;
+ enqueue_rx_packet(node, pkts[i]);
+ }
+ }
+
+ for (i = 0; i < num_nodes; i++)
+ flush_rx_queue(i);
+ }
+
+The burst of packets received is enqueued in temporary buffers (per node),
+and enqueued in the shared ring between the distributor and the node.
+After this, a new burst of packets is received and this process is
+repeated infinitely.
+
+.. code-block:: c
+
+ static void
+ flush_rx_queue(uint16_t node)
+ {
+ uint16_t j;
+ struct node *cl;
+
+ if (cl_rx_buf[node].count == 0)
+ return;
+
+ cl = &nodes[node];
+ if (rte_ring_enqueue_bulk(cl->rx_q, (void **)cl_rx_buf[node].buffer,
+ cl_rx_buf[node].count) != 0){
+ for (j = 0; j < cl_rx_buf[node].count; j++)
+ rte_pktmbuf_free(cl_rx_buf[node].buffer[j]);
+ cl->stats.rx_drop += cl_rx_buf[node].count;
+ } else
+ cl->stats.rx += cl_rx_buf[node].count;
+
+ cl_rx_buf[node].count = 0;
+ }
+
+The second process, the back-end node, receives the packets from the shared
+ring with the distributor and send them out, if they belong to the node.
+
+At initialization, it attaches to the distributor process memory, to have
+access to the shared ring, parameters and statistics.
+
+.. code-block:: c
+
+ rx_ring = rte_ring_lookup(get_rx_queue_name(node_id));
+ if (rx_ring == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get RX ring - "
+ "is distributor process running?\n");
+
+ mp = rte_mempool_lookup(PKTMBUF_POOL_NAME);
+ if (mp == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get mempool for mbufs\n");
+
+ mz = rte_memzone_lookup(MZ_SHARED_INFO);
+ if (mz == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get port info structure\n");
+ info = mz->addr;
+ tx_stats = &(info->tx_stats[node_id]);
+ filter_stats = &(info->filter_stats[node_id]);
+
+Then, the hash table that contains the flows that will be handled
+by the node is created and populated.
+
+.. code-block:: c
+
+ static struct rte_hash *
+ create_hash_table(const struct shared_info *info)
+ {
+ uint32_t num_flows_node = info->num_flows / info->num_nodes;
+ char name[RTE_HASH_NAMESIZE];
+ struct rte_hash *h;
+
+ /* create table */
+ struct rte_hash_parameters hash_params = {
+ .entries = num_flows_node * 2, /* table load = 50% */
+ .key_len = sizeof(uint32_t), /* Store IPv4 dest IP address */
+ .socket_id = rte_socket_id(),
+ .hash_func_init_val = 0,
+ };
+
+ snprintf(name, sizeof(name), "hash_table_%d", node_id);
+ hash_params.name = name;
+ h = rte_hash_create(&hash_params);
+
+ if (h == NULL)
+ rte_exit(EXIT_FAILURE,
+ "Problem creating the hash table for node %d\n",
+ node_id);
+ return h;
+ }
+
+ static void
+ populate_hash_table(const struct rte_hash *h, const struct shared_info *info)
+ {
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint32_t num_flows_node = 0;
+ uint64_t target_node;
+
+ /* Add flows in table */
+ for (i = 0; i < info->num_flows; i++) {
+ target_node = i % info->num_nodes;
+ if (target_node != node_id)
+ continue;
+
+ ip_dst = rte_cpu_to_be_32(i);
+
+ ret = rte_hash_add_key(h, (void *) &ip_dst);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u "
+ "in hash table\n", i);
+ else
+ num_flows_node++;
+
+ }
+
+ printf("Hash table: Adding 0x%x keys\n", num_flows_node);
+ }
+
+After initialization, packets are dequeued from the shared ring
+(from the distributor) and, like in the distributor process,
+the IPv4 address from the packets is used as a key to look up in the hash table.
+If there is a hit, packet is stored in a buffer, to be eventually transmitted
+in one of the enabled ports. If key is not there, packet is dropped, since the
+flow is not handled by the node.
+
+.. code-block:: c
+
+ static inline void
+ handle_packets(struct rte_hash *h, struct rte_mbuf **bufs, uint16_t num_packets)
+ {
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[PKT_READ_SIZE];
+ const void *key_ptrs[PKT_READ_SIZE];
+ unsigned int i;
+ int32_t positions[PKT_READ_SIZE] = {0};
+
+ for (i = 0; i < num_packets; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(bufs[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = &ipv4_dst_ip[i];
+ }
+ /* Check if packets belongs to any flows handled by this node */
+ rte_hash_lookup_bulk(h, key_ptrs, num_packets, positions);
+
+ for (i = 0; i < num_packets; i++) {
+ if (likely(positions[i] >= 0)) {
+ filter_stats->passed++;
+ transmit_packet(bufs[i]);
+ } else {
+ filter_stats->drop++;
+ /* Drop packet, as flow is not handled by this node */
+ rte_pktmbuf_free(bufs[i]);
+ }
+ }
+ }
+
+Finally, note that both processes updates statistics, such as transmitted, received
+and dropped packets, which are shown and refreshed by the distributor app.
+
+.. code-block:: c
+
+ static void
+ do_stats_display(void)
+ {
+ unsigned int i, j;
+ const char clr[] = {27, '[', '2', 'J', '\0'};
+ const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
+ uint64_t port_tx[RTE_MAX_ETHPORTS], port_tx_drop[RTE_MAX_ETHPORTS];
+ uint64_t node_tx[MAX_NODES], node_tx_drop[MAX_NODES];
+
+ /* to get TX stats, we need to do some summing calculations */
+ memset(port_tx, 0, sizeof(port_tx));
+ memset(port_tx_drop, 0, sizeof(port_tx_drop));
+ memset(node_tx, 0, sizeof(node_tx));
+ memset(node_tx_drop, 0, sizeof(node_tx_drop));
+
+ for (i = 0; i < num_nodes; i++) {
+ const struct tx_stats *tx = &info->tx_stats[i];
+
+ for (j = 0; j < info->num_ports; j++) {
+ const uint64_t tx_val = tx->tx[info->id[j]];
+ const uint64_t drop_val = tx->tx_drop[info->id[j]];
+
+ port_tx[j] += tx_val;
+ port_tx_drop[j] += drop_val;
+ node_tx[i] += tx_val;
+ node_tx_drop[i] += drop_val;
+ }
+ }
+
+ /* Clear screen and move to top left */
+ printf("%s%s", clr, topLeft);
+
+ printf("PORTS\n");
+ printf("-----\n");
+ for (i = 0; i < info->num_ports; i++)
+ printf("Port %u: '%s'\t", (unsigned int)info->id[i],
+ get_printable_mac_addr(info->id[i]));
+ printf("\n\n");
+ for (i = 0; i < info->num_ports; i++) {
+ printf("Port %u - rx: %9"PRIu64"\t"
+ "tx: %9"PRIu64"\n",
+ (unsigned int)info->id[i], info->rx_stats.rx[i],
+ port_tx[i]);
+ }
+
+ printf("\nFLOW DISTRIBUTOR\n");
+ printf("-----\n");
+ printf("distributed: %9"PRIu64", drop: %9"PRIu64"\n",
+ flow_dist_stats.distributed, flow_dist_stats.drop);
+
+ printf("\nNODES\n");
+ printf("-------\n");
+ for (i = 0; i < num_nodes; i++) {
+ const unsigned long long rx = nodes[i].stats.rx;
+ const unsigned long long rx_drop = nodes[i].stats.rx_drop;
+ const struct filter_stats *filter = &info->filter_stats[i];
+
+ printf("Node %2u - rx: %9llu, rx_drop: %9llu\n"
+ " tx: %9"PRIu64", tx_drop: %9"PRIu64"\n"
+ " filter_passed: %9"PRIu64", "
+ "filter_drop: %9"PRIu64"\n",
+ i, rx, rx_drop, node_tx[i], node_tx_drop[i],
+ filter->passed, filter->drop);
+ }
+
+ printf("\n");
+ }
diff --git a/doc/guides/sample_app_ug/img/flow_distributor.svg b/doc/guides/sample_app_ug/img/flow_distributor.svg
new file mode 100644
index 0000000..9aee30b
--- /dev/null
+++ b/doc/guides/sample_app_ug/img/flow_distributor.svg
@@ -0,0 +1,1254 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i6.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="8.2496in" height="5.89673in"
+ viewBox="0 0 593.971 424.565" xml:space="preserve" color-interpolation-filters="sRGB" class="st27">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st5 {fill:#feffff;font-family:Calibri;font-size:0.75em}
+ .st6 {fill:none;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st7 {fill:none;stroke:#2e75b5;stroke-width:2.25}
+ .st8 {fill:#305497;stroke:#2e75b5;stroke-width:1}
+ .st9 {fill:#feffff;font-family:Calibri;font-size:0.833336em;font-weight:bold}
+ .st10 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2)}
+ .st11 {fill:#5b9bd5}
+ .st12 {stroke:#c7c8c8;stroke-width:0.25}
+ .st13 {fill:#acccea;stroke:#c7c8c8;stroke-width:0.25}
+ .st14 {fill:#feffff;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st15 {fill:#ed7d31;stroke:#c7c8c8;stroke-width:0.25}
+ .st16 {fill:#deebf6;stroke:#c7c8c8;stroke-width:0.25}
+ .st17 {marker-end:url(#mrkr5-212);stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st18 {fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st19 {fill:none;stroke:#2e75b5;stroke-width:1}
+ .st20 {fill:#5b9bd5;font-family:Calibri;font-size:1.00001em}
+ .st21 {fill:none;stroke:none;stroke-width:0.25}
+ .st22 {font-size:1em}
+ .st23 {fill:#ffffff}
+ .st24 {stroke:#5b9bd5;stroke-dasharray:1.5,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st25 {marker-end:url(#mrkr5-444);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st26 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st27 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-212" class="st18" v:arrowType="5" v:arrowSize="2" v:setback="5.8" refX="-5.8" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ <marker id="mrkr5-444" class="st26" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(319.501,-335.688)">
+ <title>Rectangle.58</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow3-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape4-7" v:mID="4" v:groupContext="shape" transform="translate(353.251,-335.688)">
+ <title>Rectangle.59</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow4-8" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape5-13" v:mID="5" v:groupContext="shape" transform="translate(400.501,-335.688)">
+ <title>Rectangle.60</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow5-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape6-19" v:mID="6" v:groupContext="shape" transform="translate(434.251,-335.688)">
+ <title>Rectangle.61</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow6-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape7-25" v:mID="7" v:groupContext="shape" transform="translate(481.501,-335.688)">
+ <title>Rectangle.62</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow7-26" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape8-30" v:mID="8" v:groupContext="shape" transform="translate(515.251,-335.688)">
+ <title>Rectangle.63</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow8-31" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape9-35" v:mID="9" v:groupContext="shape" transform="translate(319.501,-313.188)">
+ <title>Rectangle.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow9-36" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape10-40" v:mID="10" v:groupContext="shape" transform="translate(353.251,-313.188)">
+ <title>Rectangle.65</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow10-41" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape11-45" v:mID="11" v:groupContext="shape" transform="translate(400.501,-313.188)">
+ <title>Rectangle.66</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow11-46" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape12-50" v:mID="12" v:groupContext="shape" transform="translate(434.251,-313.188)">
+ <title>Rectangle.67</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow12-51" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape13-55" v:mID="13" v:groupContext="shape" transform="translate(481.501,-313.188)">
+ <title>Rectangle.68</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow13-56" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape14-60" v:mID="14" v:groupContext="shape" transform="translate(515.251,-313.188)">
+ <title>Rectangle.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow14-61" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape15-65" v:mID="15" v:groupContext="shape" transform="translate(319.501,-277.188)">
+ <title>Rectangle.70</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow15-66" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape16-71" v:mID="16" v:groupContext="shape" transform="translate(353.251,-277.188)">
+ <title>Rectangle.71</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow16-72" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape17-77" v:mID="17" v:groupContext="shape" transform="translate(400.501,-277.188)">
+ <title>Rectangle.72</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow17-78" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape18-83" v:mID="18" v:groupContext="shape" transform="translate(434.251,-277.188)">
+ <title>Rectangle.73</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow18-84" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape19-89" v:mID="19" v:groupContext="shape" transform="translate(481.501,-277.188)">
+ <title>Rectangle.74</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow19-90" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape20-95" v:mID="20" v:groupContext="shape" transform="translate(515.251,-277.188)">
+ <title>Rectangle.75</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow20-96" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape21-101" v:mID="21" v:groupContext="shape" transform="translate(319.501,-240.687)">
+ <title>Rectangle.76</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow21-102" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape22-106" v:mID="22" v:groupContext="shape" transform="translate(353.251,-240.687)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow22-107" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape23-111" v:mID="23" v:groupContext="shape" transform="translate(400.501,-240.687)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow23-112" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape24-116" v:mID="24" v:groupContext="shape" transform="translate(434.251,-240.687)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow24-117" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape25-121" v:mID="25" v:groupContext="shape" transform="translate(481.501,-240.687)">
+ <title>Rectangle.80</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow25-122" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape26-127" v:mID="26" v:groupContext="shape" transform="translate(515.251,-240.687)">
+ <title>Rectangle.81</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow26-128" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape27-133" v:mID="27" v:groupContext="shape" transform="translate(317.251,-231.687)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow27-134" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st6"/>
+ </g>
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st7"/>
+ </g>
+ <g id="shape28-138" v:mID="28" v:groupContext="shape" transform="translate(328.501,-362.688)">
+ <title>Sheet.28</title>
+ <desc>Local Table for N Specific Flows Serviced at Node 1</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="110.423" cy="418.94" width="220.85" height="11.25"/>
+ <rect x="0" y="413.315" width="220.846" height="11.25" class="st8"/>
+ <text x="5.77" y="421.94" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Local Table for N Specific Flows Serviced at Node 1</text> </g>
+ <g id="group34-141" transform="translate(66.0294,-165.569)" v:mID="34" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Device)"/>
+ <v:cp v:nameU="SubShapeType" v:lbl="SubShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Load balancer)"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Load balancer</title>
+ <g id="shape35-142" v:mID="35" v:groupContext="shape" transform="translate(0,-7.33146)">
+ <title>Sheet.35</title>
+ <g id="shadow35-143" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23 Z" class="st10"/>
+ <path d="M0 377.86 L72 377.86" class="st6"/>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23" class="st6"/>
+ </g>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23 Z" class="st11"/>
+ <path d="M0 377.86 L72 377.86" class="st12"/>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23" class="st12"/>
+ </g>
+ <g id="shape36-152" v:mID="36" v:groupContext="shape" transform="translate(8.03054,-12.9324)">
+ <title>Sheet.36</title>
+ <g id="shadow36-153" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M45.34 421.81 L41.2 422.66 L44.12 424.56 L49.68 423.16 L48.75 417.51 L45.8 415.59 L46.69 419.68 L36.97
+ 413.34 L35.6 415.45 L45.34 421.81 ZM50.83 405.36 L39.2 405.36 L39.2 407.88 L50.8 407.88 L47.82 410.83
+ L51.34 410.83 L55.21 406.61 L51.32 402.39 L47.83 402.39 L50.83 405.36 ZM46.49 392.01 L36.75 398.37
+ L38.13 400.48 L47.84 394.14 L46.96 398.23 L49.91 396.31 L50.84 390.66 L45.28 389.26 L42.36 391.16
+ L46.49 392.01 ZM27.71 397.16 C22.66 397.16 18.58 401.25 18.58 406.29 C18.58 411.33 22.66 415.42
+ 27.71 415.42 C32.75 415.42 36.84 411.33 36.84 406.29 C36.84 401.25 32.75 397.16 27.71 397.16 ZM27.71
+ 400.04 C31.15 400.04 33.96 402.84 33.96 406.29 C33.96 409.74 31.15 412.54 27.71 412.54 C24.26 412.54
+ 21.46 409.74 21.46 406.29 C21.46 402.84 24.26 400.04 27.71 400.04 ZM11.64 405.04 L0 405.04 L0 407.56
+ L11.6 407.56 L8.62 410.51 L12.14 410.51 L16.01 406.29 L12.12 402.07 L8.64 402.07 L11.64 405.04 Z"
+ class="st2"/>
+ </g>
+ <path d="M45.34 421.81 L41.2 422.66 L44.12 424.56 L49.68 423.16 L48.75 417.51 L45.8 415.59 L46.69 419.68 L36.97 413.34
+ L35.6 415.45 L45.34 421.81 ZM50.83 405.36 L39.2 405.36 L39.2 407.88 L50.8 407.88 L47.82 410.83 L51.34
+ 410.83 L55.21 406.61 L51.32 402.39 L47.83 402.39 L50.83 405.36 ZM46.49 392.01 L36.75 398.37 L38.13 400.48
+ L47.84 394.14 L46.96 398.23 L49.91 396.31 L50.84 390.66 L45.28 389.26 L42.36 391.16 L46.49 392.01 ZM27.71
+ 397.16 C22.66 397.16 18.58 401.25 18.58 406.29 C18.58 411.33 22.66 415.42 27.71 415.42 C32.75 415.42
+ 36.84 411.33 36.84 406.29 C36.84 401.25 32.75 397.16 27.71 397.16 ZM27.71 400.04 C31.15 400.04 33.96
+ 402.84 33.96 406.29 C33.96 409.74 31.15 412.54 27.71 412.54 C24.26 412.54 21.46 409.74 21.46 406.29
+ C21.46 402.84 24.26 400.04 27.71 400.04 ZM11.64 405.04 L0 405.04 L0 407.56 L11.6 407.56 L8.62 410.51
+ L12.14 410.51 L16.01 406.29 L12.12 402.07 L8.64 402.07 L11.64 405.04 Z" class="st13"/>
+ </g>
+ </g>
+ <g id="shape37-157" v:mID="37" v:groupContext="shape" transform="translate(21.0294,-45.4375)">
+ <title>Rectangle</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow37-158" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="336.433" width="135" height="88.1315" class="st2"/>
+ </g>
+ <rect x="0" y="336.433" width="135" height="88.1315" class="st3"/>
+ </g>
+ <g id="shape38-162" v:mID="38" v:groupContext="shape" transform="translate(34.693,-126.438)">
+ <title>Sheet.38</title>
+ <desc>EFD Table</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="49.3364" cy="415.565" width="98.68" height="18"/>
+ <rect x="0" y="406.565" width="98.6728" height="18" class="st8"/>
+ <text x="24.87" y="419.17" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>EFD Table</text> </g>
+ <g id="shape39-165" v:mID="39" v:groupContext="shape" transform="translate(30.0294,-99.4375)">
+ <title>Rectangle.39</title>
+ <desc>Group_id</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="26.9182" cy="415.565" width="53.84" height="18"/>
+ <g id="shadow39-166" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st15"/>
+ <text x="7.87" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group_id</text> </g>
+ <g id="shape40-171" v:mID="40" v:groupContext="shape" transform="translate(93.193,-99.4375)">
+ <title>Rectangle.40</title>
+ <desc>Hash index</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="26.9182" cy="415.565" width="53.84" height="18"/>
+ <g id="shadow40-172" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st15"/>
+ <text x="4.64" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Hash index</text> </g>
+ <g id="shape41-177" v:mID="41" v:groupContext="shape" transform="translate(30.193,-82.4275)">
+ <title>Rectangle.41</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow41-178" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape42-182" v:mID="42" v:groupContext="shape" transform="translate(30.193,-66.8125)">
+ <title>Rectangle.42</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow42-183" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape43-187" v:mID="43" v:groupContext="shape" transform="translate(30.1112,-52.1875)">
+ <title>Rectangle.43</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow43-188" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape44-192" v:mID="44" v:groupContext="shape" transform="translate(93.0294,-81.4375)">
+ <title>Rectangle.44</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow44-193" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape45-197" v:mID="45" v:groupContext="shape" transform="translate(93.193,-66.8125)">
+ <title>Rectangle.45</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow45-198" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape46-202" v:mID="46" v:groupContext="shape" transform="translate(93.193,-52.1875)">
+ <title>Rectangle.46</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow46-203" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape47-207" v:mID="47" v:groupContext="shape" transform="translate(374.924,544.022) rotate(135)">
+ <title>Sheet.47</title>
+ <path d="M-0 417.75 A40.674 18.0151 -156.2 0 0 40.24 422.15 L40.49 421.89" class="st17"/>
+ </g>
+ <g id="shape48-213" v:mID="48" v:groupContext="shape" transform="translate(21.0294,-19)">
+ <title>Sheet.48</title>
+ <desc>Supports X*N Flows</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="415.565" width="135" height="18"/>
+ <rect x="0" y="406.565" width="135" height="18" class="st19"/>
+ <text x="19.05" y="419.17" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Supports X*N Flows</text> </g>
+ <g id="shape49-216" v:mID="49" v:groupContext="shape" transform="translate(48.0294,-229.938)">
+ <title>Sheet.49</title>
+ <desc>Frontend Server or Load Balancer</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="400.94" width="108" height="47.25"/>
+ <rect x="0" y="377.315" width="108" height="47.25" class="st21"/>
+ <text x="14.56" y="397.34" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Frontend Server<v:newlineChar/><tspan
+ x="13.16" dy="1.2em" class="st22">or Load Balancer </tspan> </text> </g>
+ <g id="group51-220" transform="translate(223.876,-310.938)" v:mID="51" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server</title>
+ <g id="shape52-221" v:mID="52" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.52</title>
+ <g id="shadow52-222" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape53-226" v:mID="53" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.53</title>
+ <g id="shadow53-227" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape54-231" v:mID="54" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.54</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow54-232" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape59-239" v:mID="59" v:groupContext="shape" transform="translate(277.876,-373.938)">
+ <title>Sheet.59</title>
+ <path d="M-0 424.56 A111.108 53.2538 42.31 0 1 93.83 421.21 L94.14 421.41" class="st17"/>
+ </g>
+ <g id="shape60-244" v:mID="60" v:groupContext="shape" transform="translate(205.876,-283.938)">
+ <title>Sheet.60</title>
+ <desc>Backend Server 1</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.93" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server 1</text> </g>
+ <g id="group61-247" transform="translate(223.876,-207.438)" v:mID="61" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server.61</title>
+ <g id="shape62-248" v:mID="62" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.62</title>
+ <g id="shadow62-249" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape63-253" v:mID="63" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.63</title>
+ <g id="shadow63-254" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape64-258" v:mID="64" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow64-259" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape65-266" v:mID="65" v:groupContext="shape" transform="translate(205.876,-180.437)">
+ <title>Sheet.65</title>
+ <desc>Backend Server 2</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.93" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server 2</text> </g>
+ <g id="group66-269" transform="translate(219.029,-58.9375)" v:mID="66" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server.66</title>
+ <g id="shape67-270" v:mID="67" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.67</title>
+ <g id="shadow67-271" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape68-275" v:mID="68" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.68</title>
+ <g id="shadow68-276" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape69-280" v:mID="69" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow69-281" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape70-288" v:mID="70" v:groupContext="shape" transform="translate(201.029,-26.056)">
+ <title>Sheet.70</title>
+ <desc>Backend Server X</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.86" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server X</text> </g>
+ <g id="shape71-291" v:mID="71" v:groupContext="shape" transform="translate(684.44,239.627) rotate(90)">
+ <title>Sheet.71</title>
+ <path d="M0 424.56 L45 424.56" class="st24"/>
+ </g>
+ <g id="shape72-294" v:mID="72" v:groupContext="shape" transform="translate(6.85967,-22.443) rotate(-38.1076)">
+ <title>Sheet.72</title>
+ <path d="M-0 424.56 A96.1331 44.4001 55.03 0 1 68.24 420.56 L68.51 420.79" class="st17"/>
+ </g>
+ <g id="shape73-299" v:mID="73" v:groupContext="shape" transform="translate(328.501,-135.937)">
+ <title>Rectangle.73</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow73-300" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape74-305" v:mID="74" v:groupContext="shape" transform="translate(362.251,-135.937)">
+ <title>Rectangle.74</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow74-306" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape75-311" v:mID="75" v:groupContext="shape" transform="translate(409.501,-135.937)">
+ <title>Rectangle.75</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow75-312" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape76-317" v:mID="76" v:groupContext="shape" transform="translate(443.251,-135.937)">
+ <title>Rectangle.76</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow76-318" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape77-323" v:mID="77" v:groupContext="shape" transform="translate(490.501,-135.937)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow77-324" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape78-328" v:mID="78" v:groupContext="shape" transform="translate(524.251,-135.937)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow78-329" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape79-333" v:mID="79" v:groupContext="shape" transform="translate(328.501,-113.437)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow79-334" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape80-338" v:mID="80" v:groupContext="shape" transform="translate(362.251,-113.437)">
+ <title>Rectangle.80</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow80-339" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape81-343" v:mID="81" v:groupContext="shape" transform="translate(409.501,-113.437)">
+ <title>Rectangle.81</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow81-344" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape82-348" v:mID="82" v:groupContext="shape" transform="translate(443.251,-113.437)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow82-349" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape83-353" v:mID="83" v:groupContext="shape" transform="translate(490.501,-113.437)">
+ <title>Rectangle.83</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow83-354" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape84-358" v:mID="84" v:groupContext="shape" transform="translate(524.251,-113.437)">
+ <title>Rectangle.84</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow84-359" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape85-363" v:mID="85" v:groupContext="shape" transform="translate(328.501,-77.4375)">
+ <title>Rectangle.85</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow85-364" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape86-369" v:mID="86" v:groupContext="shape" transform="translate(362.251,-77.4375)">
+ <title>Rectangle.86</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow86-370" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape87-375" v:mID="87" v:groupContext="shape" transform="translate(409.501,-77.4375)">
+ <title>Rectangle.87</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow87-376" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape88-381" v:mID="88" v:groupContext="shape" transform="translate(443.251,-77.4375)">
+ <title>Rectangle.88</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow88-382" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape89-387" v:mID="89" v:groupContext="shape" transform="translate(490.501,-77.4375)">
+ <title>Rectangle.89</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow89-388" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape90-393" v:mID="90" v:groupContext="shape" transform="translate(524.251,-77.4375)">
+ <title>Rectangle.90</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow90-394" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape91-399" v:mID="91" v:groupContext="shape" transform="translate(328.501,-40.9375)">
+ <title>Rectangle.91</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow91-400" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape92-404" v:mID="92" v:groupContext="shape" transform="translate(362.251,-40.9375)">
+ <title>Rectangle.92</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow92-405" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape93-409" v:mID="93" v:groupContext="shape" transform="translate(409.501,-40.9375)">
+ <title>Rectangle.93</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow93-410" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape94-414" v:mID="94" v:groupContext="shape" transform="translate(443.251,-40.9375)">
+ <title>Rectangle.94</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow94-415" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape95-419" v:mID="95" v:groupContext="shape" transform="translate(490.501,-40.9375)">
+ <title>Rectangle.95</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow95-420" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape96-425" v:mID="96" v:groupContext="shape" transform="translate(524.251,-40.9375)">
+ <title>Rectangle.96</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow96-426" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape97-431" v:mID="97" v:groupContext="shape" transform="translate(326.251,-31.9375)">
+ <title>Rectangle.97</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow97-432" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st6"/>
+ </g>
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st7"/>
+ </g>
+ <g id="shape98-436" v:mID="98" v:groupContext="shape" transform="translate(337.501,-162.938)">
+ <title>Sheet.98</title>
+ <desc>Local Table for N Specific Flows Serviced at Node X</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="110.423" cy="418.94" width="220.85" height="11.25"/>
+ <rect x="0" y="413.315" width="220.846" height="11.25" class="st8"/>
+ <text x="5.55" y="421.94" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Local Table for N Specific Flows Serviced at Node X</text> </g>
+ <g id="shape99-439" v:mID="99" v:groupContext="shape" transform="translate(-204.342,-29.4449) rotate(-53.7462)">
+ <title>Sheet.99</title>
+ <path d="M0 424.56 L160.37 424.56" class="st25"/>
+ </g>
+ <g id="shape100-445" v:mID="100" v:groupContext="shape" transform="translate(-37.6568,-164.882) rotate(-24.444)">
+ <title>Sheet.100</title>
+ <path d="M0 424.56 L101.71 424.56" class="st25"/>
+ </g>
+ <g id="shape101-450" v:mID="101" v:groupContext="shape" transform="translate(464.049,-50.8578) rotate(50.099)">
+ <title>Sheet.101</title>
+ <path d="M0 424.56 L139.8 424.56" class="st25"/>
+ </g>
+ <g id="shape102-455" v:mID="102" v:groupContext="shape" transform="translate(372.376,-207.438)">
+ <title>Sheet.102</title>
+ <desc>Supports N Flows</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="415.565" width="135" height="18"/>
+ <rect x="0" y="406.565" width="135" height="18" class="st19"/>
+ <text x="25.15" y="419.17" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Supports N Flows</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/sample_app_ug/index.rst b/doc/guides/sample_app_ug/index.rst
index 775e2f7..260f6a5 100644
--- a/doc/guides/sample_app_ug/index.rst
+++ b/doc/guides/sample_app_ug/index.rst
@@ -57,6 +57,7 @@ Sample Applications User Guides
l3_forward_virtual
link_status_intr
load_balancer
+ flow_distributor
multi_process
qos_metering
qos_scheduler
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [dpdk-dev] [PATCH v5 0/5] Elastic Flow Distributor
2017-01-16 15:08 ` [dpdk-dev] [PATCH v5 0/5] Elastic Flow Distributor Thomas Monjalon
@ 2017-01-17 8:34 ` De Lara Guarch, Pablo
0 siblings, 0 replies; 63+ messages in thread
From: De Lara Guarch, Pablo @ 2017-01-17 8:34 UTC (permalink / raw)
To: Thomas Monjalon; +Cc: dev
Hi Thomas,
> -----Original Message-----
> From: Thomas Monjalon [mailto:thomas.monjalon@6wind.com]
> Sent: Monday, January 16, 2017 3:08 PM
> To: De Lara Guarch, Pablo
> Cc: dev@dpdk.org
> Subject: Re: [dpdk-dev] [PATCH v5 0/5] Elastic Flow Distributor
>
> Unfortunately, it does not build:
>
> rte_efd.o: In function `rte_efd_create':
> lib/librte_efd/rte_efd.c:(.text+0xed): undefined reference to `log2'
> lib/librte_efd/rte_efd.c:(.text+0x51b): undefined reference to
> `rte_ring_create'
> rte_efd.o: In function `rte_efd_free':
> lib/librte_efd/rte_efd.c:(.text+0x739): undefined reference to
> `rte_ring_free'
>
> Configuration tested: x86_64-native-linuxapp-clang+shared+next+debug
I fixed that problem in v6 :)
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [dpdk-dev] [PATCH v6 0/5] Elastic Flow Distributor
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 " Pablo de Lara
` (4 preceding siblings ...)
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 5/5] doc: add flow distributor guide Pablo de Lara
@ 2017-01-17 20:29 ` Thomas Monjalon
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 0/6] " Pablo de Lara
6 siblings, 0 replies; 63+ messages in thread
From: Thomas Monjalon @ 2017-01-17 20:29 UTC (permalink / raw)
To: Pablo de Lara; +Cc: dev
2017-01-16 19:21, Pablo de Lara:
> EFD is a distributor library that uses perfect hashing to determine a
> target/value for a given incoming flow key. It has the following advantages:
> first, because it uses perfect hashing it does not store the key itself and
> hence lookup performance is not dependent on the key size. Second, the
> target/value can be any arbitrary value hence the system designer and/or
> operator can better optimize service rates and inter-cluster network traffic
> locating. Third, since the storage requirement is much smaller than a
> hash-based flow table (i.e. better fit for CPU cache), EFD can scale to millions
> of flow keys. Finally, with current optimized library implementation performance
> is fully scalable with number of CPU cores.
>
> The basic idea of EFD is when a given key is to be inserted, a family of hash
> functions is searched until the correct hash function that maps the input key to
> the correct value is found. However, rather than explicitly storing all keys and
> their associated values, EFD stores only indices of hash functions that map keys
> to values, and thereby consumes much less space than conventional flow-based
> tables. The lookup operation is very simple, similar to computational-based
> scheme, given an input key the lookup operation is reduced to hashing that key
> with the correct hash function.
>
> Intuitively, finding a hash function that maps each of a large number (millions)
> of input keys to the correct output value is effectively impossible, as a result
> EFD, breaks the problem into smaller pieces (divide and conquer). EFD divides
> the entire input key set into many small groups. Each group consists of
> approximately 20-28 keys (a configurable parameter for the library), then, for
> each small group, a brute force search to find a hash function that produces the
> correct outputs for each key in the group.
> It should be mentioned that since in the online lookup table for EFD doesn’t
> store the key itself, the size of the EFD table is independent of the key size
> and hence EFD lookup performance which is almost constant irrespective of the
> length of the key which is a highly desirable feature especially for longer
> keys.
>
> Library code is included in the patch, plus an sample application that shows
> how the library can be used.
>
> RFC for this library was already sent:
> http://dpdk.org/ml/archives/dev/2016-October/049238.html
>
> Changes in v6:
>
> - Separated x86 SIMD code in different file
It would have been nice to make a separate patch for x86.
> - Fixed shared library compilation
> - Fixed figure reference in documentation
> - Added missing parameter documentation
Unfortunately, there is another compilation error on ARM:
lib/librte_efd/rte_efd.c:39:23: fatal error:
immintrin.h: No such file or directory
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [dpdk-dev] [PATCH v6 1/5] efd: new Elastic Flow Distributor library
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 1/5] efd: new Elastic Flow Distributor library Pablo de Lara
@ 2017-01-17 20:32 ` Thomas Monjalon
2017-01-17 21:11 ` Thomas Monjalon
1 sibling, 0 replies; 63+ messages in thread
From: Thomas Monjalon @ 2017-01-17 20:32 UTC (permalink / raw)
To: Pablo de Lara; +Cc: dev, Byron Marohn, Saikrishna Edupuganti
2017-01-16 19:21, Pablo de Lara:
> +#if defined(RTE_ARCH_X86)
> + if (RTE_EFD_VALUE_NUM_BITS > 3 && rte_cpu_get_flag_enabled(RTE_CPUFLAG_AVX2))
magic number spotted!
A comment would be probably helpful in this case.
> + table->lookup_fn = EFD_LOOKUP_AVX2;
> + else
> +#endif
> + table->lookup_fn = EFD_LOOKUP_SCALAR;
>
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [dpdk-dev] [PATCH v6 5/5] doc: add flow distributor guide
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 5/5] doc: add flow distributor guide Pablo de Lara
@ 2017-01-17 20:35 ` Thomas Monjalon
0 siblings, 0 replies; 63+ messages in thread
From: Thomas Monjalon @ 2017-01-17 20:35 UTC (permalink / raw)
To: Pablo de Lara; +Cc: dev, Sameh Gobriel
2017-01-16 19:21, Pablo de Lara:
> Signed-off-by: Sameh Gobriel <sameh.gobriel@intel.com>
> Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
> Acked-by: Christian Maciocco <christian.maciocco@intel.com>
> ---
> MAINTAINERS | 1 +
> doc/guides/sample_app_ug/flow_distributor.rst | 494 ++++++++
> doc/guides/sample_app_ug/img/flow_distributor.svg | 1254 +++++++++++++++++++++
> doc/guides/sample_app_ug/index.rst | 1 +
A reference to fix:
doc/guides/sample_app_ug/flow_distributor.rst:55: WARNING:
undefined label: _figure_efd_sample_app_overview
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [dpdk-dev] [PATCH v6 1/5] efd: new Elastic Flow Distributor library
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 1/5] efd: new Elastic Flow Distributor library Pablo de Lara
2017-01-17 20:32 ` Thomas Monjalon
@ 2017-01-17 21:11 ` Thomas Monjalon
1 sibling, 0 replies; 63+ messages in thread
From: Thomas Monjalon @ 2017-01-17 21:11 UTC (permalink / raw)
To: Pablo de Lara; +Cc: dev, Byron Marohn, Saikrishna Edupuganti
2017-01-16 19:21, Pablo de Lara:
> +# this lib depends upon:
> +DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_mbuf
> +DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_mempool
> +DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_eal
> +DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_ether
> +DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_ring
copy/paste spotted
I think only EAL and ring are needed.
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v7 0/6] Elastic Flow Distributor
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 " Pablo de Lara
` (5 preceding siblings ...)
2017-01-17 20:29 ` [dpdk-dev] [PATCH v6 0/5] Elastic Flow Distributor Thomas Monjalon
@ 2017-01-17 22:10 ` Pablo de Lara
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 1/6] efd: new Elastic Flow Distributor library Pablo de Lara
` (7 more replies)
6 siblings, 8 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-17 22:10 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara
EFD is a distributor library that uses perfect hashing to determine a
target/value for a given incoming flow key. It has the following advantages:
first, because it uses perfect hashing it does not store the key itself and
hence lookup performance is not dependent on the key size. Second, the
target/value can be any arbitrary value hence the system designer and/or
operator can better optimize service rates and inter-cluster network traffic
locating. Third, since the storage requirement is much smaller than a
hash-based flow table (i.e. better fit for CPU cache), EFD can scale to millions
of flow keys. Finally, with current optimized library implementation performance
is fully scalable with number of CPU cores.
The basic idea of EFD is when a given key is to be inserted, a family of hash
functions is searched until the correct hash function that maps the input key to
the correct value is found. However, rather than explicitly storing all keys and
their associated values, EFD stores only indices of hash functions that map keys
to values, and thereby consumes much less space than conventional flow-based
tables. The lookup operation is very simple, similar to computational-based
scheme, given an input key the lookup operation is reduced to hashing that key
with the correct hash function.
Intuitively, finding a hash function that maps each of a large number (millions)
of input keys to the correct output value is effectively impossible, as a result
EFD, breaks the problem into smaller pieces (divide and conquer). EFD divides
the entire input key set into many small groups. Each group consists of
approximately 20-28 keys (a configurable parameter for the library), then, for
each small group, a brute force search to find a hash function that produces the
correct outputs for each key in the group.
It should be mentioned that since in the online lookup table for EFD doesn’t
store the key itself, the size of the EFD table is independent of the key size
and hence EFD lookup performance which is almost constant irrespective of the
length of the key which is a highly desirable feature especially for longer
keys.
Library code is included in the patch, plus an sample application that shows
how the library can be used.
RFC for this library was already sent:
http://dpdk.org/ml/archives/dev/2016-October/049238.html
Changes in v7:
- Fixed figure reference in documentation
- Removed unnecessary library dependencies
- Separated x86 SIMD code in different patch
- Added extra comments
- Fixed ARM compilation issue
Changes in v6:
- Separated x86 SIMD code in different file
- Fixed shared library compilation
- Fixed figure reference in documentation
- Added missing parameter documentation
Changes in v5:
- Removed trailing whitespace in doc
Changes in v4:
- Added References section
Changes in v3:
- Fixed SVG files
- Fixed copyright dates
- Reformatted parts of documentation
Changes in v2:
- Added documentation for library and sample app
- Fixed checkpatch errors/warnings
- Added functional and performance tests
- Made key size variable at runtime
- Made code multi-architecture compatible at runtime
Pablo de Lara (6):
efd: new Elastic Flow Distributor library
efd: add AVX2 vect lookup function
app/test: add EFD functional and perf tests
examples/flow_distributor: sample app to demonstrate EFD usage
doc: add EFD library section in Programmers guide
doc: add flow distributor guide
MAINTAINERS | 9 +
app/test/Makefile | 5 +-
app/test/test_efd.c | 494 ++++++++
app/test/test_efd_perf.c | 407 +++++++
config/common_base | 5 +
doc/api/doxy-api-index.md | 3 +-
doc/api/doxy-api.conf | 1 +
doc/api/examples.dox | 4 +
doc/guides/prog_guide/efd_lib.rst | 440 +++++++
doc/guides/prog_guide/img/efd_i1.svg | 130 ++
doc/guides/prog_guide/img/efd_i10.svg | 384 ++++++
doc/guides/prog_guide/img/efd_i11.svg | 319 +++++
doc/guides/prog_guide/img/efd_i12.svg | 1008 ++++++++++++++++
doc/guides/prog_guide/img/efd_i2.svg | 280 +++++
doc/guides/prog_guide/img/efd_i3.svg | 634 ++++++++++
doc/guides/prog_guide/img/efd_i4.svg | 203 ++++
doc/guides/prog_guide/img/efd_i5.svg | 183 +++
doc/guides/prog_guide/img/efd_i6.svg | 1254 +++++++++++++++++++
doc/guides/prog_guide/img/efd_i7.svg | 790 ++++++++++++
doc/guides/prog_guide/img/efd_i8.svg | 182 +++
doc/guides/prog_guide/img/efd_i9.svg | 390 ++++++
doc/guides/prog_guide/index.rst | 23 +
doc/guides/rel_notes/release_17_02.rst | 14 +
doc/guides/rel_notes/release_17_02.rst.rej | 19 +
doc/guides/sample_app_ug/flow_distributor.rst | 494 ++++++++
doc/guides/sample_app_ug/img/flow_distributor.svg | 1254 +++++++++++++++++++
doc/guides/sample_app_ug/index.rst | 1 +
examples/Makefile | 1 +
examples/flow_distributor/Makefile | 44 +
examples/flow_distributor/distributor/Makefile | 57 +
examples/flow_distributor/distributor/args.c | 200 +++
examples/flow_distributor/distributor/args.h | 39 +
examples/flow_distributor/distributor/init.c | 371 ++++++
examples/flow_distributor/distributor/init.h | 76 ++
examples/flow_distributor/distributor/main.c | 362 ++++++
examples/flow_distributor/node/Makefile | 48 +
examples/flow_distributor/node/node.c | 417 +++++++
examples/flow_distributor/shared/common.h | 99 ++
lib/Makefile | 3 +-
lib/librte_eal/common/include/rte_log.h | 3 +-
lib/librte_efd/Makefile | 56 +
lib/librte_efd/rte_efd.c | 1344 +++++++++++++++++++++
lib/librte_efd/rte_efd.h | 300 +++++
lib/librte_efd/rte_efd_version.map | 13 +
lib/librte_efd/rte_efd_x86.h | 86 ++
mk/rte.app.mk | 3 +-
46 files changed, 12447 insertions(+), 5 deletions(-)
create mode 100644 app/test/test_efd.c
create mode 100644 app/test/test_efd_perf.c
create mode 100644 doc/guides/prog_guide/efd_lib.rst
create mode 100644 doc/guides/prog_guide/img/efd_i1.svg
create mode 100644 doc/guides/prog_guide/img/efd_i10.svg
create mode 100644 doc/guides/prog_guide/img/efd_i11.svg
create mode 100644 doc/guides/prog_guide/img/efd_i12.svg
create mode 100644 doc/guides/prog_guide/img/efd_i2.svg
create mode 100644 doc/guides/prog_guide/img/efd_i3.svg
create mode 100644 doc/guides/prog_guide/img/efd_i4.svg
create mode 100644 doc/guides/prog_guide/img/efd_i5.svg
create mode 100644 doc/guides/prog_guide/img/efd_i6.svg
create mode 100644 doc/guides/prog_guide/img/efd_i7.svg
create mode 100644 doc/guides/prog_guide/img/efd_i8.svg
create mode 100644 doc/guides/prog_guide/img/efd_i9.svg
create mode 100644 doc/guides/rel_notes/release_17_02.rst.rej
create mode 100644 doc/guides/sample_app_ug/flow_distributor.rst
create mode 100644 doc/guides/sample_app_ug/img/flow_distributor.svg
create mode 100644 examples/flow_distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/args.c
create mode 100644 examples/flow_distributor/distributor/args.h
create mode 100644 examples/flow_distributor/distributor/init.c
create mode 100644 examples/flow_distributor/distributor/init.h
create mode 100644 examples/flow_distributor/distributor/main.c
create mode 100644 examples/flow_distributor/node/Makefile
create mode 100644 examples/flow_distributor/node/node.c
create mode 100644 examples/flow_distributor/shared/common.h
create mode 100644 lib/librte_efd/Makefile
create mode 100644 lib/librte_efd/rte_efd.c
create mode 100644 lib/librte_efd/rte_efd.h
create mode 100644 lib/librte_efd/rte_efd_version.map
create mode 100644 lib/librte_efd/rte_efd_x86.h
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v7 1/6] efd: new Elastic Flow Distributor library
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 0/6] " Pablo de Lara
@ 2017-01-17 22:10 ` Pablo de Lara
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 2/6] efd: add AVX2 vect lookup function Pablo de Lara
` (6 subsequent siblings)
7 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-17 22:10 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Byron Marohn, Saikrishna Edupuganti
Elastic Flow Distributor (EFD) is a distributor library that uses
perfect hashing to determine a target/value for a given incoming flow key.
It has the following advantages:
- First, because it uses perfect hashing, it does not store
the key itself and hence lookup performance is not dependent
on the key size.
- Second, the target/value can be any arbitrary value hence
the system designer and/or operator can better optimize service rates
and inter-cluster network traffic locating.
- Third, since the storage requirement is much smaller than a hash-based
flow table (i.e. better fit for CPU cache), EFD can scale to
millions of flow keys.
Finally, with current optimized library implementation performance
is fully scalable with number of CPU cores.
Signed-off-by: Byron Marohn <byron.marohn@intel.com>
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 5 +
config/common_base | 5 +
doc/api/doxy-api-index.md | 3 +-
doc/api/doxy-api.conf | 1 +
doc/guides/rel_notes/release_17_02.rst | 11 +
doc/guides/rel_notes/release_17_02.rst.rej | 19 +
lib/Makefile | 3 +-
lib/librte_eal/common/include/rte_log.h | 3 +-
lib/librte_efd/Makefile | 56 ++
lib/librte_efd/rte_efd.c | 1324 ++++++++++++++++++++++++++++
lib/librte_efd/rte_efd.h | 300 +++++++
lib/librte_efd/rte_efd_version.map | 13 +
mk/rte.app.mk | 3 +-
13 files changed, 1742 insertions(+), 4 deletions(-)
create mode 100644 doc/guides/rel_notes/release_17_02.rst.rej
create mode 100644 lib/librte_efd/Makefile
create mode 100644 lib/librte_efd/rte_efd.c
create mode 100644 lib/librte_efd/rte_efd.h
create mode 100644 lib/librte_efd/rte_efd_version.map
diff --git a/MAINTAINERS b/MAINTAINERS
index 9645c9b..9c60d67 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -528,6 +528,11 @@ F: app/test/test_acl.*
F: examples/l3fwd-acl/
F: doc/guides/sample_app_ug/l3_forward_access_ctrl.rst
+EFD
+M: Byron Marohn <byron.marohn@intel.com>
+M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
+F: lib/librte_efd/
+
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
M: Pablo de Lara <pablo.de.lara.guarch@intel.com>
diff --git a/config/common_base b/config/common_base
index 8e9dcfa..869d8fb 100644
--- a/config/common_base
+++ b/config/common_base
@@ -467,6 +467,11 @@ CONFIG_RTE_LIBRTE_HASH=y
CONFIG_RTE_LIBRTE_HASH_DEBUG=n
#
+# Compile librte_efd
+#
+CONFIG_RTE_LIBRTE_EFD=y
+
+#
# Compile librte_jobstats
#
CONFIG_RTE_LIBRTE_JOBSTATS=y
diff --git a/doc/api/doxy-api-index.md b/doc/api/doxy-api-index.md
index 72d59b2..0d34e2f 100644
--- a/doc/api/doxy-api-index.md
+++ b/doc/api/doxy-api-index.md
@@ -90,7 +90,8 @@ There are many libraries, so their headers may be grouped by topics:
[frag/reass] (@ref rte_ip_frag.h),
[LPM IPv4 route] (@ref rte_lpm.h),
[LPM IPv6 route] (@ref rte_lpm6.h),
- [ACL] (@ref rte_acl.h)
+ [ACL] (@ref rte_acl.h),
+ [EFD] (@ref rte_efd.h)
- **QoS**:
[metering] (@ref rte_meter.h),
diff --git a/doc/api/doxy-api.conf b/doc/api/doxy-api.conf
index b340fcf..6892315 100644
--- a/doc/api/doxy-api.conf
+++ b/doc/api/doxy-api.conf
@@ -40,6 +40,7 @@ INPUT = doc/api/doxy-api-index.md \
lib/librte_compat \
lib/librte_cryptodev \
lib/librte_distributor \
+ lib/librte_efd \
lib/librte_ether \
lib/librte_hash \
lib/librte_ip_frag \
diff --git a/doc/guides/rel_notes/release_17_02.rst b/doc/guides/rel_notes/release_17_02.rst
index 4a3b947..32ea8d9 100644
--- a/doc/guides/rel_notes/release_17_02.rst
+++ b/doc/guides/rel_notes/release_17_02.rst
@@ -96,6 +96,17 @@ New Features
See the :ref:`Virtio Interrupt Mode <virtio_interrupt_mode>` documentation
for more information.
+* **Added Elastic Flow Distributor library (rte_efd).**
+
+ This new library uses perfect hashing to determine a target/value for a
+ given incoming flow key.
+
+ It does not store the key itself for lookup operations, and therefore,
+ lookup performance is not dependent on the key size. Also, the target/value
+ can be any arbitrary value (8 bits by default). Finally, the storage requirement
+ is much smaller than a hash-based flow table and therefore, it can better fit for
+ CPU cache, being able to scale to millions of flow keys.
+
Resolved Issues
---------------
diff --git a/doc/guides/rel_notes/release_17_02.rst.rej b/doc/guides/rel_notes/release_17_02.rst.rej
new file mode 100644
index 0000000..9a6260a
--- /dev/null
+++ b/doc/guides/rel_notes/release_17_02.rst.rej
@@ -0,0 +1,19 @@
+diff a/doc/guides/rel_notes/release_17_02.rst b/doc/guides/rel_notes/release_17_02.rst (rejected hunks)
+@@ -57,6 +57,17 @@ New Features
+ Six new APIs have been added to the ixgbe PMD for MACsec offload support.
+ The declarations for the APIs can be found in ``rte_pmd_ixgbe.h``.
+
++* **Added Elastic Flow Distributor library (rte_efd).**
++
++ This new library uses perfect hashing to determine a target/value for a
++ given incoming flow key.
++
++ It does not store the key itself for lookup operations, and therefore,
++ lookup performance is not dependent on the key size. Also, the target/value
++ can be any arbitrary value (8 bits by default). Finally, the storage requirement
++ is much smaller than a hash-based flow table and therefore, it can better fit for
++ CPU cache, being able to scale to millions of flow keys.
++
+
+ Resolved Issues
+ ---------------
diff --git a/lib/Makefile b/lib/Makefile
index 990f23a..4178325 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -1,6 +1,6 @@
# BSD LICENSE
#
-# Copyright(c) 2010-2015 Intel Corporation. All rights reserved.
+# Copyright(c) 2010-2017 Intel Corporation. All rights reserved.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -43,6 +43,7 @@ DIRS-$(CONFIG_RTE_LIBRTE_ETHER) += librte_ether
DIRS-$(CONFIG_RTE_LIBRTE_CRYPTODEV) += librte_cryptodev
DIRS-$(CONFIG_RTE_LIBRTE_VHOST) += librte_vhost
DIRS-$(CONFIG_RTE_LIBRTE_HASH) += librte_hash
+DIRS-$(CONFIG_RTE_LIBRTE_EFD) += librte_efd
DIRS-$(CONFIG_RTE_LIBRTE_LPM) += librte_lpm
DIRS-$(CONFIG_RTE_LIBRTE_ACL) += librte_acl
DIRS-$(CONFIG_RTE_LIBRTE_NET) += librte_net
diff --git a/lib/librte_eal/common/include/rte_log.h b/lib/librte_eal/common/include/rte_log.h
index 671e274..954b96c 100644
--- a/lib/librte_eal/common/include/rte_log.h
+++ b/lib/librte_eal/common/include/rte_log.h
@@ -1,7 +1,7 @@
/*-
* BSD LICENSE
*
- * Copyright(c) 2010-2014 Intel Corporation. All rights reserved.
+ * Copyright(c) 2010-2017 Intel Corporation. All rights reserved.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -79,6 +79,7 @@ extern struct rte_logs rte_logs;
#define RTE_LOGTYPE_PIPELINE 0x00008000 /**< Log related to pipeline. */
#define RTE_LOGTYPE_MBUF 0x00010000 /**< Log related to mbuf. */
#define RTE_LOGTYPE_CRYPTODEV 0x00020000 /**< Log related to cryptodev. */
+#define RTE_LOGTYPE_EFD 0x00040000 /**< Log related to EFD. */
/* these log types can be used in an application */
#define RTE_LOGTYPE_USER1 0x01000000 /**< User-defined log type 1. */
diff --git a/lib/librte_efd/Makefile b/lib/librte_efd/Makefile
new file mode 100644
index 0000000..d8e1f94
--- /dev/null
+++ b/lib/librte_efd/Makefile
@@ -0,0 +1,56 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+# library name
+LIB = librte_efd.a
+
+LDLIBS += -lm
+
+CFLAGS += -O3
+CFLAGS += $(WERROR_FLAGS) -I$(SRCDIR)
+
+EXPORT_MAP := rte_efd_version.map
+
+LIBABIVER := 1
+
+# all source are stored in SRCS-y
+SRCS-$(CONFIG_RTE_LIBRTE_EFD) := rte_efd.c
+
+# install this header file
+SYMLINK-$(CONFIG_RTE_LIBRTE_EFD)-include := rte_efd.h
+
+# this lib depends upon:
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_eal
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_ring
+
+include $(RTE_SDK)/mk/rte.lib.mk
diff --git a/lib/librte_efd/rte_efd.c b/lib/librte_efd/rte_efd.c
new file mode 100644
index 0000000..2bcfd62
--- /dev/null
+++ b/lib/librte_efd/rte_efd.c
@@ -0,0 +1,1324 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <math.h>
+#include <sys/queue.h>
+
+#include <rte_log.h>
+#include <rte_eal_memconfig.h>
+#include <rte_errno.h>
+#include <rte_malloc.h>
+#include <rte_memzone.h>
+#include <rte_prefetch.h>
+#include <rte_branch_prediction.h>
+#include <rte_memcpy.h>
+#include <rte_ring.h>
+#include <rte_jhash.h>
+#include <rte_hash_crc.h>
+
+#include "rte_efd.h"
+
+#define EFD_KEY(key_idx, table) (table->keys + ((key_idx) * table->key_len))
+/** Hash function used to determine chunk_id and bin_id for a group */
+#define EFD_HASH(key, table) \
+ (uint32_t)(rte_jhash(key, table->key_len, 0xbc9f1d34))
+/** Hash function used as constant component of perfect hash search */
+#define EFD_HASHFUNCA(key, table) \
+ (uint32_t)(rte_hash_crc(key, table->key_len, 0xbc9f1d35))
+/** Hash function used as multiplicative component of perfect hash search */
+#define EFD_HASHFUNCB(key, table) \
+ (uint32_t)(rte_hash_crc(key, table->key_len, 0xbc9f1d36))
+
+/*************************************************************************
+ * Fixed constants
+ *************************************************************************/
+
+/* These parameters are fixed by the efd_bin_to_group balancing table */
+#define EFD_CHUNK_NUM_GROUPS (64)
+#define EFD_CHUNK_NUM_BINS (256)
+#define EFD_CHUNK_NUM_BIN_TO_GROUP_SETS \
+ (EFD_CHUNK_NUM_BINS / EFD_CHUNK_NUM_GROUPS)
+
+/*
+ * Target number of rules that each chunk is created to handle.
+ * Used when initially allocating the table
+ */
+#define EFD_TARGET_CHUNK_NUM_RULES \
+ (EFD_CHUNK_NUM_GROUPS * EFD_TARGET_GROUP_NUM_RULES)
+/*
+ * Max number of rules that each chunk is created to handle.
+ * Used when initially allocating the table
+ */
+#define EFD_TARGET_CHUNK_MAX_NUM_RULES \
+ (EFD_CHUNK_NUM_GROUPS * EFD_MAX_GROUP_NUM_RULES)
+
+/** This is fixed based on the bin_to_group permutation array */
+#define EFD_MAX_GROUP_NUM_BINS (16)
+
+/**
+ * The end of the chunks array needs some extra padding to ensure
+ * that vectorization over-reads on the last online chunk stay within
+allocated memory
+ */
+#define EFD_NUM_CHUNK_PADDING_BYTES (256)
+
+/* All different internal lookup functions */
+enum efd_lookup_internal_function {
+ EFD_LOOKUP_SCALAR = 0,
+ EFD_LOOKUP_NUM
+};
+
+TAILQ_HEAD(rte_efd_list, rte_tailq_entry);
+
+static struct rte_tailq_elem rte_efd_tailq = {
+ .name = "RTE_EFD",
+};
+EAL_REGISTER_TAILQ(rte_efd_tailq);
+
+/** Internal permutation array used to shuffle bins into pseudorandom groups */
+const uint32_t efd_bin_to_group[EFD_CHUNK_NUM_BIN_TO_GROUP_SETS][EFD_CHUNK_NUM_BINS] = {
+ {
+ 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3,
+ 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
+ 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11,
+ 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15,
+ 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19,
+ 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23,
+ 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27,
+ 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31,
+ 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35,
+ 36, 36, 36, 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39,
+ 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, 42, 43, 43, 43, 43,
+ 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47,
+ 48, 48, 48, 48, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51,
+ 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, 54, 55, 55, 55, 55,
+ 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59,
+ 60, 60, 60, 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63
+ },
+ {
+ 34, 33, 48, 59, 0, 21, 36, 18, 9, 49, 54, 38, 51, 23, 31, 5,
+ 44, 23, 37, 52, 11, 4, 58, 20, 38, 40, 38, 22, 26, 28, 42, 6,
+ 46, 16, 31, 28, 46, 14, 60, 0, 35, 53, 16, 58, 16, 29, 39, 7,
+ 1, 54, 15, 11, 48, 3, 62, 9, 58, 5, 30, 43, 17, 7, 36, 34,
+ 6, 36, 2, 14, 10, 1, 47, 47, 20, 45, 62, 56, 34, 25, 39, 18,
+ 51, 41, 61, 25, 56, 40, 41, 37, 52, 35, 30, 57, 11, 42, 37, 27,
+ 54, 19, 26, 13, 48, 31, 46, 15, 12, 10, 16, 20, 43, 17, 12, 55,
+ 45, 18, 8, 41, 7, 31, 42, 63, 12, 14, 21, 57, 24, 40, 5, 41,
+ 13, 44, 23, 59, 25, 57, 52, 50, 62, 1, 2, 49, 32, 57, 26, 43,
+ 56, 60, 55, 5, 49, 6, 3, 50, 46, 39, 27, 33, 17, 4, 53, 13,
+ 2, 19, 36, 51, 63, 0, 22, 33, 59, 28, 29, 23, 45, 33, 53, 27,
+ 22, 21, 40, 56, 4, 18, 44, 47, 28, 17, 4, 50, 21, 62, 8, 39,
+ 0, 8, 15, 24, 29, 24, 9, 11, 48, 61, 35, 55, 43, 1, 54, 42,
+ 53, 60, 22, 3, 32, 52, 25, 8, 15, 60, 7, 55, 27, 63, 19, 10,
+ 63, 24, 61, 19, 12, 38, 6, 29, 13, 37, 10, 3, 45, 32, 32, 30,
+ 49, 61, 44, 14, 20, 58, 35, 30, 2, 26, 34, 51, 9, 59, 47, 50
+ },
+ {
+ 32, 35, 32, 34, 55, 5, 6, 23, 49, 11, 6, 23, 52, 37, 29, 54,
+ 55, 40, 63, 50, 29, 52, 61, 25, 12, 56, 39, 38, 29, 11, 46, 1,
+ 40, 11, 19, 56, 7, 28, 51, 16, 15, 48, 21, 51, 60, 31, 14, 22,
+ 41, 47, 59, 56, 53, 28, 58, 26, 43, 27, 41, 33, 24, 52, 44, 38,
+ 13, 59, 48, 51, 60, 15, 3, 30, 15, 0, 10, 62, 44, 14, 28, 51,
+ 38, 2, 41, 26, 25, 49, 10, 12, 55, 57, 27, 35, 19, 33, 0, 30,
+ 5, 36, 47, 53, 5, 53, 20, 43, 34, 37, 52, 41, 21, 63, 59, 9,
+ 24, 1, 45, 24, 39, 44, 45, 16, 9, 17, 7, 50, 57, 22, 18, 28,
+ 25, 45, 2, 40, 58, 15, 17, 3, 1, 27, 61, 39, 19, 0, 19, 21,
+ 57, 62, 54, 60, 54, 40, 48, 33, 36, 37, 4, 42, 1, 43, 58, 8,
+ 13, 42, 10, 56, 35, 22, 48, 61, 63, 10, 49, 9, 24, 9, 25, 57,
+ 33, 18, 13, 31, 42, 36, 36, 55, 30, 37, 53, 34, 59, 4, 4, 23,
+ 8, 16, 58, 14, 30, 11, 12, 63, 49, 62, 2, 39, 47, 22, 2, 60,
+ 18, 8, 46, 31, 6, 20, 32, 29, 46, 42, 20, 31, 32, 61, 34, 4,
+ 47, 26, 20, 43, 26, 21, 7, 3, 16, 35, 18, 44, 27, 62, 13, 23,
+ 6, 50, 12, 8, 45, 17, 3, 46, 50, 7, 14, 5, 17, 54, 38, 0
+ },
+ {
+ 29, 56, 5, 7, 54, 48, 23, 37, 35, 44, 52, 40, 33, 49, 60, 0,
+ 59, 51, 28, 12, 41, 26, 2, 23, 34, 5, 59, 40, 3, 19, 6, 26,
+ 35, 53, 45, 49, 29, 57, 28, 62, 58, 59, 19, 53, 59, 62, 6, 54,
+ 13, 15, 48, 50, 45, 21, 41, 12, 34, 40, 24, 56, 19, 21, 35, 18,
+ 55, 45, 9, 61, 47, 61, 19, 15, 16, 39, 17, 31, 3, 51, 21, 50,
+ 17, 25, 25, 11, 44, 16, 18, 28, 14, 2, 37, 61, 58, 27, 62, 4,
+ 14, 17, 1, 9, 46, 28, 37, 0, 53, 43, 57, 7, 57, 46, 21, 41,
+ 39, 14, 52, 60, 44, 53, 49, 60, 49, 63, 13, 11, 29, 1, 55, 47,
+ 55, 12, 60, 43, 54, 37, 13, 6, 42, 10, 36, 13, 9, 8, 34, 51,
+ 31, 32, 12, 7, 57, 2, 26, 14, 3, 30, 63, 3, 32, 1, 5, 11,
+ 27, 24, 26, 44, 31, 23, 56, 38, 62, 0, 40, 30, 6, 23, 38, 2,
+ 47, 5, 15, 27, 16, 10, 31, 25, 22, 63, 30, 25, 20, 33, 32, 50,
+ 29, 43, 55, 10, 50, 45, 56, 20, 4, 7, 27, 46, 11, 16, 22, 52,
+ 35, 20, 41, 54, 46, 33, 42, 18, 63, 8, 22, 58, 36, 4, 51, 42,
+ 38, 32, 38, 22, 17, 0, 47, 8, 48, 8, 48, 1, 61, 36, 33, 20,
+ 24, 39, 39, 18, 30, 36, 9, 43, 42, 24, 10, 58, 4, 15, 34, 52
+ },
+};
+
+/*************************************************************************
+ * Offline region structures
+ *************************************************************************/
+
+/** Online group containing number of rules, values, keys and their bins
+ * for EFD_MAX_GROUP_NUM_RULES rules.
+ */
+struct efd_offline_group_rules {
+ uint32_t num_rules;
+ /**< Sum of the number of rules in all bins assigned to this group. */
+
+ uint32_t key_idx[EFD_MAX_GROUP_NUM_RULES];
+ /**< Array with all keys of the group. */
+ efd_value_t value[EFD_MAX_GROUP_NUM_RULES];
+ /**< Array with all values of the keys of the group. */
+
+ uint8_t bin_id[EFD_MAX_GROUP_NUM_RULES];
+ /**< Stores the bin for each correspending key to
+ * avoid having to recompute it
+ */
+};
+
+/** Offline chunk record, containing EFD_TARGET_CHUNK_NUM_RULES rules.
+ * Those rules are split into EFD_CHUNK_NUM_GROUPS groups per chunk.
+ */
+struct efd_offline_chunk_rules {
+ uint16_t num_rules;
+ /**< Number of rules in the entire chunk;
+ * used to detect unbalanced groups
+ */
+
+ struct efd_offline_group_rules group_rules[EFD_CHUNK_NUM_GROUPS];
+ /**< Array of all groups in the chunk. */
+};
+
+/*************************************************************************
+ * Online region structures
+ *************************************************************************/
+
+/** Online group containing values for EFD_MAX_GROUP_NUM_RULES rules. */
+struct efd_online_group_entry {
+ efd_hashfunc_t hash_idx[RTE_EFD_VALUE_NUM_BITS];
+ efd_lookuptbl_t lookup_table[RTE_EFD_VALUE_NUM_BITS];
+} __attribute__((__packed__));
+
+/**
+ * A single chunk record, containing EFD_TARGET_CHUNK_NUM_RULES rules.
+ * Those rules are split into EFD_CHUNK_NUM_GROUPS groups per chunk.
+ */
+struct efd_online_chunk {
+ uint8_t bin_choice_list[(EFD_CHUNK_NUM_BINS * 2 + 7) / 8];
+ /**< This is a packed indirection index into the 'groups' array.
+ * Each byte contains four two-bit values which index into
+ * the efd_bin_to_group array.
+ * The efd_bin_to_group array returns the index into the groups array
+ */
+
+ struct efd_online_group_entry groups[EFD_CHUNK_NUM_GROUPS];
+ /**< Array of all the groups in the chunk. */
+} __attribute__((__packed__));
+
+/**
+ * EFD table structure
+ */
+struct rte_efd_table {
+ char name[RTE_EFD_NAMESIZE]; /**< Name of the efd table. */
+
+ uint32_t key_len; /**< Length of the key stored offline */
+
+ uint32_t max_num_rules;
+ /**< Static maximum number of entries the table was constructed to hold. */
+
+ uint32_t num_rules;
+ /**< Number of entries currently in the table . */
+
+ uint32_t num_chunks;
+ /**< Number of chunks in the table needed to support num_rules. */
+
+ uint32_t num_chunks_shift;
+ /**< Bits to shift to get chunk id, instead of dividing by num_chunk. */
+
+ enum efd_lookup_internal_function lookup_fn;
+ /**< Indicates which lookup function to use. */
+
+ struct efd_online_chunk *chunks[RTE_MAX_NUMA_NODES];
+ /**< Dynamic array of size num_chunks of chunk records. */
+
+ struct efd_offline_chunk_rules *offline_chunks;
+ /**< Dynamic array of size num_chunks of key-value pairs. */
+
+ struct rte_ring *free_slots;
+ /**< Ring that stores all indexes of the free slots in the key table */
+
+ uint8_t *keys; /**< Dynamic array of size max_num_rules of keys */
+};
+
+/**
+ * Computes the chunk ID for a given key hash
+ *
+ * @param table
+ * EFD table to reference
+ * @param hashed_key
+ * 32-bit key hash returned by EFD_HASH
+ *
+ * @return
+ * chunk ID containing this key hash
+ */
+static inline uint32_t
+efd_get_chunk_id(const struct rte_efd_table * const table,
+ const uint32_t hashed_key)
+{
+ return hashed_key & (table->num_chunks - 1);
+}
+
+/**
+ * Computes the bin ID for a given key hash
+ *
+ * @param table
+ * EFD table to reference
+ * @param hashed_key
+ * 32-bit key hash returned by EFD_HASH
+ *
+ * @return bin ID containing this key hash
+ */
+static inline uint32_t
+efd_get_bin_id(const struct rte_efd_table * const table,
+ const uint32_t hashed_key)
+{
+ return (hashed_key >> table->num_chunks_shift) & (EFD_CHUNK_NUM_BINS - 1);
+}
+
+/**
+ * Looks up the current permutation choice for a particular bin in the online table
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to look up existing values (ideally caller's socket id)
+ * @param chunk_id
+ * Chunk ID of bin to look up
+ * @param bin_id
+ * Bin ID to look up
+ *
+ * @return
+ * Currently active permutation choice in the online table
+ */
+static inline uint8_t
+efd_get_choice(const struct rte_efd_table * const table,
+ const unsigned int socket_id, const uint32_t chunk_id,
+ const uint32_t bin_id)
+{
+ struct efd_online_chunk *chunk = &table->chunks[socket_id][chunk_id];
+
+ /*
+ * Grab the chunk (byte) that contains the choices
+ * for four neighboring bins.
+ */
+ uint8_t choice_chunk =
+ chunk->bin_choice_list[bin_id / EFD_CHUNK_NUM_BIN_TO_GROUP_SETS];
+
+ /*
+ * Compute the offset into the chunk that contains
+ * the group_id lookup position
+ */
+ int offset = (bin_id & 0x3) * 2;
+
+ /* Extract from the byte just the desired lookup position */
+ return (uint8_t) ((choice_chunk >> offset) & 0x3);
+}
+
+/**
+ * Compute the chunk_id and bin_id for a given key
+ *
+ * @param table
+ * EFD table to reference
+ * @param key
+ * Key to hash and find location of
+ * @param chunk_id
+ * Computed chunk ID
+ * @param bin_id
+ * Computed bin ID
+ *
+ */
+static inline void
+efd_compute_ids(const struct rte_efd_table * const table,
+ const void *key, uint32_t * const chunk_id, uint32_t * const bin_id)
+{
+ /* Compute the position of the entry in the hash table */
+ uint32_t h = EFD_HASH(key, table);
+
+ /* Compute the chunk_id where that entry can be found */
+ *chunk_id = efd_get_chunk_id(table, h);
+
+ /*
+ * Compute the bin within that chunk where the entry
+ * can be found (0 - 255)
+ */
+ *bin_id = efd_get_bin_id(table, h);
+}
+
+/**
+ * Search for a hash function for a group that satisfies all group results
+ */
+static inline int
+efd_search_hash(struct rte_efd_table * const table,
+ const struct efd_offline_group_rules * const off_group,
+ struct efd_online_group_entry * const on_group)
+{
+ efd_hashfunc_t hash_idx;
+ efd_hashfunc_t start_hash_idx[RTE_EFD_VALUE_NUM_BITS];
+ efd_lookuptbl_t start_lookup_table[RTE_EFD_VALUE_NUM_BITS];
+
+ uint32_t i, j, rule_id;
+ uint32_t hash_val_a[EFD_MAX_GROUP_NUM_RULES];
+ uint32_t hash_val_b[EFD_MAX_GROUP_NUM_RULES];
+ uint32_t hash_val[EFD_MAX_GROUP_NUM_RULES];
+
+
+ rte_prefetch0(off_group->value);
+
+ /*
+ * Prepopulate the hash_val tables by running the two hash functions
+ * for each provided rule
+ */
+ for (i = 0; i < off_group->num_rules; i++) {
+ void *key_stored = EFD_KEY(off_group->key_idx[i], table);
+ hash_val_b[i] = EFD_HASHFUNCB(key_stored, table);
+ hash_val_a[i] = EFD_HASHFUNCA(key_stored, table);
+ }
+
+ for (i = 0; i < RTE_EFD_VALUE_NUM_BITS; i++) {
+ hash_idx = on_group->hash_idx[i];
+ start_hash_idx[i] = hash_idx;
+ start_lookup_table[i] = on_group->lookup_table[i];
+
+ do {
+ efd_lookuptbl_t lookup_table = 0;
+ efd_lookuptbl_t lookup_table_complement = 0;
+
+ for (rule_id = 0; rule_id < off_group->num_rules; rule_id++)
+ hash_val[rule_id] = hash_val_a[rule_id] + (hash_idx *
+ hash_val_b[rule_id]);
+
+ /*
+ * The goal here is to find a hash function for this
+ * particular bit entry that meets the following criteria:
+ * The most significant bits of the hash result define a
+ * shift into the lookup table where the bit will be stored
+ */
+
+ /* Iterate over each provided rule */
+ for (rule_id = 0; rule_id < off_group->num_rules;
+ rule_id++) {
+ /*
+ * Use the few most significant bits (number based on
+ * EFD_LOOKUPTBL_SIZE) to see what position the
+ * expected bit should be set in the lookup_table
+ */
+ uint32_t bucket_idx = hash_val[rule_id] >>
+ EFD_LOOKUPTBL_SHIFT;
+
+ /*
+ * Get the current bit of interest.
+ * This only find an appropriate hash function
+ * for one bit at a time of the rule
+ */
+ efd_lookuptbl_t expected =
+ (off_group->value[rule_id] >> i) & 0x1;
+
+ /*
+ * Add the expected bit (if set) to a map
+ * (lookup_table). Also set its complement
+ * in lookup_table_complement
+ */
+ lookup_table |= expected << bucket_idx;
+ lookup_table_complement |= (1 - expected)
+ << bucket_idx;
+
+ /*
+ * If ever the hash function of two different
+ * elements result in different values at the
+ * same location in the lookup_table,
+ * the current hash_idx is not valid.
+ */
+ if (lookup_table & lookup_table_complement)
+ break;
+ }
+
+ /*
+ * Check if the previous loop completed without
+ * breaking early
+ */
+ if (rule_id == off_group->num_rules) {
+ /*
+ * Current hash function worked, store it
+ * for the current group
+ */
+ on_group->hash_idx[i] = hash_idx;
+ on_group->lookup_table[i] = lookup_table;
+
+ /*
+ * Make sure that the hash function has changed
+ * from the starting value
+ */
+ hash_idx = start_hash_idx[i] + 1;
+ break;
+ }
+ hash_idx++;
+
+ } while (hash_idx != start_hash_idx[i]);
+
+ /* Failed to find perfect hash for this group */
+ if (hash_idx == start_hash_idx[i]) {
+ /*
+ * Restore previous hash_idx and lookup_table
+ * for all value bits
+ */
+ for (j = 0; j < i; j++) {
+ on_group->hash_idx[j] = start_hash_idx[j];
+ on_group->lookup_table[j] = start_lookup_table[j];
+ }
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+struct rte_efd_table *
+rte_efd_create(const char *name, uint32_t max_num_rules, uint32_t key_len,
+ uint8_t online_cpu_socket_bitmask, uint8_t offline_cpu_socket)
+{
+ struct rte_efd_table *table = NULL;
+ uint8_t *key_array = NULL;
+ uint32_t num_chunks, num_chunks_shift;
+ uint8_t socket_id;
+ struct rte_efd_list *efd_list = NULL;
+ struct rte_tailq_entry *te;
+ uint64_t offline_table_size;
+ char ring_name[RTE_RING_NAMESIZE];
+ struct rte_ring *r = NULL;
+ unsigned int i;
+
+ efd_list = RTE_TAILQ_CAST(rte_efd_tailq.head, rte_efd_list);
+
+ if (online_cpu_socket_bitmask == 0) {
+ RTE_LOG(ERR, EFD, "At least one CPU socket must be enabled "
+ "in the bitmask\n");
+ return NULL;
+ }
+
+ if (max_num_rules == 0) {
+ RTE_LOG(ERR, EFD, "Max num rules must be higher than 0\n");
+ return NULL;
+ }
+
+ /*
+ * Compute the minimum number of chunks (smallest power of 2)
+ * that can hold all of the rules
+ */
+ if (max_num_rules % EFD_TARGET_CHUNK_NUM_RULES == 0)
+ num_chunks = rte_align32pow2(max_num_rules /
+ EFD_TARGET_CHUNK_NUM_RULES);
+ else
+ num_chunks = rte_align32pow2((max_num_rules /
+ EFD_TARGET_CHUNK_NUM_RULES) + 1);
+
+ num_chunks_shift = log2(num_chunks);
+
+ rte_rwlock_write_lock(RTE_EAL_TAILQ_RWLOCK);
+
+ /*
+ * Guarantee there's no existing: this is normally already checked
+ * by ring creation above
+ */
+ TAILQ_FOREACH(te, efd_list, next)
+ {
+ table = (struct rte_efd_table *) te->data;
+ if (strncmp(name, table->name, RTE_EFD_NAMESIZE) == 0)
+ break;
+ }
+
+ table = NULL;
+ if (te != NULL) {
+ rte_errno = EEXIST;
+ te = NULL;
+ goto error_unlock_exit;
+ }
+
+ te = rte_zmalloc("EFD_TAILQ_ENTRY", sizeof(*te), 0);
+ if (te == NULL) {
+ RTE_LOG(ERR, EFD, "tailq entry allocation failed\n");
+ goto error_unlock_exit;
+ }
+
+ /* Create a new EFD table management structure */
+ table = (struct rte_efd_table *) rte_zmalloc_socket(NULL,
+ sizeof(struct rte_efd_table),
+ RTE_CACHE_LINE_SIZE,
+ offline_cpu_socket);
+ if (table == NULL) {
+ RTE_LOG(ERR, EFD, "Allocating EFD table management structure"
+ " on socket %u failed\n",
+ offline_cpu_socket);
+ goto error_unlock_exit;
+ }
+
+
+ RTE_LOG(DEBUG, EFD, "Allocated EFD table management structure "
+ "on socket %u\n", offline_cpu_socket);
+
+ table->max_num_rules = num_chunks * EFD_TARGET_CHUNK_MAX_NUM_RULES;
+ table->num_rules = 0;
+ table->num_chunks = num_chunks;
+ table->num_chunks_shift = num_chunks_shift;
+ table->key_len = key_len;
+
+ /* key_array */
+ key_array = (uint8_t *) rte_zmalloc_socket(NULL,
+ table->max_num_rules * table->key_len,
+ RTE_CACHE_LINE_SIZE,
+ offline_cpu_socket);
+ if (key_array == NULL) {
+ RTE_LOG(ERR, EFD, "Allocating key array"
+ " on socket %u failed\n",
+ offline_cpu_socket);
+ goto error_unlock_exit;
+ }
+ table->keys = key_array;
+ snprintf(table->name, sizeof(table->name), "%s", name);
+
+ RTE_LOG(DEBUG, EFD, "Creating an EFD table with %u chunks,"
+ " which potentially supports %u entries\n",
+ num_chunks, table->max_num_rules);
+
+ /* Make sure all the allocatable table pointers are NULL initially */
+ for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++)
+ table->chunks[socket_id] = NULL;
+ table->offline_chunks = NULL;
+
+ /*
+ * Allocate one online table per socket specified
+ * in the user-supplied bitmask
+ */
+ uint64_t online_table_size = num_chunks * sizeof(struct efd_online_chunk) +
+ EFD_NUM_CHUNK_PADDING_BYTES;
+
+ for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++) {
+ if ((online_cpu_socket_bitmask >> socket_id) & 0x01) {
+ /*
+ * Allocate all of the EFD table chunks (the online portion)
+ * as a continuous block
+ */
+ table->chunks[socket_id] =
+ (struct efd_online_chunk *) rte_zmalloc_socket(
+ NULL,
+ online_table_size,
+ RTE_CACHE_LINE_SIZE,
+ socket_id);
+ if (table->chunks[socket_id] == NULL) {
+ RTE_LOG(ERR, EFD,
+ "Allocating EFD online table on "
+ "socket %u failed\n",
+ socket_id);
+ goto error_unlock_exit;
+ }
+ RTE_LOG(DEBUG, EFD,
+ "Allocated EFD online table of size "
+ "%"PRIu64" bytes (%.2f MB) on socket %u\n",
+ online_table_size,
+ (float) online_table_size /
+ (1024.0F * 1024.0F),
+ socket_id);
+ }
+ }
+
+ table->lookup_fn = EFD_LOOKUP_SCALAR;
+
+ /*
+ * Allocate the EFD table offline portion (with the actual rules
+ * mapping keys to values) as a continuous block.
+ * This could be several gigabytes of memory.
+ */
+ offline_table_size = num_chunks * sizeof(struct efd_offline_chunk_rules);
+ table->offline_chunks =
+ (struct efd_offline_chunk_rules *) rte_zmalloc_socket(NULL,
+ offline_table_size,
+ RTE_CACHE_LINE_SIZE,
+ offline_cpu_socket);
+ if (table->offline_chunks == NULL) {
+ RTE_LOG(ERR, EFD, "Allocating EFD offline table on socket %u "
+ "failed\n", offline_cpu_socket);
+ goto error_unlock_exit;
+ }
+
+ RTE_LOG(DEBUG, EFD,
+ "Allocated EFD offline table of size %"PRIu64" bytes "
+ " (%.2f MB) on socket %u\n", offline_table_size,
+ (float) offline_table_size / (1024.0F * 1024.0F),
+ offline_cpu_socket);
+
+ te->data = (void *) table;
+ TAILQ_INSERT_TAIL(efd_list, te, next);
+ rte_rwlock_write_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+ snprintf(ring_name, sizeof(ring_name), "HT_%s", table->name);
+ /* Create ring (Dummy slot index is not enqueued) */
+ r = rte_ring_create(ring_name, rte_align32pow2(table->max_num_rules),
+ offline_cpu_socket, 0);
+ if (r == NULL) {
+ RTE_LOG(ERR, EFD, "memory allocation failed\n");
+ goto error_unlock_exit;
+ }
+
+ /* Populate free slots ring. Entry zero is reserved for key misses. */
+ for (i = 0; i < table->max_num_rules; i++)
+ rte_ring_sp_enqueue(r, (void *) ((uintptr_t) i));
+
+ table->free_slots = r;
+ return table;
+
+error_unlock_exit:
+ rte_rwlock_write_unlock(RTE_EAL_TAILQ_RWLOCK);
+ rte_efd_free(table);
+
+ return NULL;
+}
+
+struct rte_efd_table *
+rte_efd_find_existing(const char *name)
+{
+ struct rte_efd_table *table = NULL;
+ struct rte_tailq_entry *te;
+ struct rte_efd_list *efd_list;
+
+ efd_list = RTE_TAILQ_CAST(rte_efd_tailq.head, rte_efd_list);
+
+ rte_rwlock_read_lock(RTE_EAL_TAILQ_RWLOCK);
+
+ TAILQ_FOREACH(te, efd_list, next)
+ {
+ table = (struct rte_efd_table *) te->data;
+ if (strncmp(name, table->name, RTE_EFD_NAMESIZE) == 0)
+ break;
+ }
+ rte_rwlock_read_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+ if (te == NULL) {
+ rte_errno = ENOENT;
+ return NULL;
+ }
+ return table;
+}
+
+void
+rte_efd_free(struct rte_efd_table *table)
+{
+ uint8_t socket_id;
+
+ if (table == NULL)
+ return;
+
+ for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++)
+ rte_free(table->chunks[socket_id]);
+
+ rte_ring_free(table->free_slots);
+ rte_free(table->offline_chunks);
+ rte_free(table->keys);
+ rte_free(table);
+}
+
+/**
+ * Applies a previously computed table entry to the specified table for all
+ * socket-local copies of the online table.
+ * Intended to apply an update for only a single change
+ * to a key/value pair at a time
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing values (ideally caller's socket id)
+ * @param chunk_id
+ * Chunk index to update
+ * @param group_id
+ * Group index to update
+ * @param bin_id
+ * Bin within the group that this update affects
+ * @param new_bin_choice
+ * Newly chosen permutation which this bin should use - only lower 2 bits
+ * @param new_group_entry
+ * Previously computed updated chunk/group entry
+ */
+static inline void
+efd_apply_update(struct rte_efd_table * const table, const unsigned int socket_id,
+ const uint32_t chunk_id, const uint32_t group_id,
+ const uint32_t bin_id, const uint8_t new_bin_choice,
+ const struct efd_online_group_entry * const new_group_entry)
+{
+ int i;
+ struct efd_online_chunk *chunk = &table->chunks[socket_id][chunk_id];
+ uint8_t bin_index = bin_id / EFD_CHUNK_NUM_BIN_TO_GROUP_SETS;
+
+ /*
+ * Grab the current byte that contains the choices
+ * for four neighboring bins
+ */
+ uint8_t choice_chunk =
+ chunk->bin_choice_list[bin_index];
+
+
+ /* Compute the offset into the chunk that needs to be updated */
+ int offset = (bin_id & 0x3) * 2;
+
+ /* Zero the two bits of interest and set them to new_bin_choice */
+ choice_chunk = (choice_chunk & (~(0x03 << offset)))
+ | ((new_bin_choice & 0x03) << offset);
+
+ /* Update the online table with the new data across all sockets */
+ for (i = 0; i < RTE_MAX_NUMA_NODES; i++) {
+ if (table->chunks[i] != NULL) {
+ memcpy(&(table->chunks[i][chunk_id].groups[group_id]),
+ new_group_entry,
+ sizeof(struct efd_online_group_entry));
+ table->chunks[i][chunk_id].bin_choice_list[bin_index] =
+ choice_chunk;
+ }
+ }
+}
+
+/*
+ * Move the bin from prev group to the new group
+ */
+static inline void
+move_groups(uint32_t bin_id, uint8_t bin_size,
+ struct efd_offline_group_rules *new_group,
+ struct efd_offline_group_rules * const current_group)
+{
+
+ uint8_t empty_idx = 0;
+ unsigned int i;
+
+ if (new_group == current_group)
+ return;
+
+ for (i = 0; i < current_group->num_rules; i++) {
+ /*
+ * Move keys that belong to the same bin
+ * to the new group
+ */
+ if (current_group->bin_id[i] == bin_id) {
+ new_group->key_idx[new_group->num_rules] =
+ current_group->key_idx[i];
+ new_group->value[new_group->num_rules] =
+ current_group->value[i];
+ new_group->bin_id[new_group->num_rules] =
+ current_group->bin_id[i];
+ new_group->num_rules++;
+ } else {
+ if (i != empty_idx) {
+ /*
+ * Need to move this key towards
+ * the top of the array
+ */
+ current_group->key_idx[empty_idx] =
+ current_group->key_idx[i];
+ current_group->value[empty_idx] =
+ current_group->value[i];
+ current_group->bin_id[empty_idx] =
+ current_group->bin_id[i];
+ }
+ empty_idx++;
+ }
+
+ }
+ current_group->num_rules -= bin_size;
+}
+
+/*
+ * Revert group/s to their previous state before
+ * trying to insert/add a new key
+ */
+static inline void
+revert_groups(struct efd_offline_group_rules *previous_group,
+ struct efd_offline_group_rules *current_group, uint8_t bin_size)
+{
+ unsigned int i;
+
+ if (current_group == previous_group)
+ return;
+
+ /* Move keys back to previous group */
+ for (i = current_group->num_rules - bin_size;
+ i < current_group->num_rules; i++) {
+ previous_group->key_idx[previous_group->num_rules] =
+ current_group->key_idx[i];
+ previous_group->value[previous_group->num_rules] =
+ current_group->value[i];
+ previous_group->bin_id[previous_group->num_rules] =
+ current_group->bin_id[i];
+ previous_group->num_rules++;
+ }
+
+ /*
+ * Decrease number of rules after the move
+ * in the new group
+ */
+ current_group->num_rules -= bin_size;
+}
+
+/**
+ * Computes an updated table entry where the supplied key points to a new host.
+ * If no entry exists, one is inserted.
+ *
+ * This function does NOT modify the online table(s)
+ * This function DOES modify the offline table
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing values (ideally caller's socket id)
+ * @param key
+ * Key to insert
+ * @param value
+ * Value to associate with key
+ * @param chunk_id
+ * Chunk ID of the chunk that was modified
+ * @param group_id
+ * Group ID of the group that was modified
+ * @param bin_id
+ * Bin ID that was modified
+ * @param new_bin_choice
+ * Newly chosen permutation which this bin will use
+ * @param entry
+ * Newly computed online entry to apply later with efd_apply_update
+ *
+ * @return
+ * RTE_EFD_UPDATE_WARN_GROUP_FULL
+ * Operation is insert, and the last available space in the
+ * key's group was just used. Future inserts may fail as groups fill up.
+ * This operation was still successful, and entry contains a valid update
+ * RTE_EFD_UPDATE_FAILED
+ * Either the EFD failed to find a suitable perfect hash or the group was full
+ * This is a fatal error, and the table is now in an indeterminite state
+ * RTE_EFD_UPDATE_NO_CHANGE
+ * Operation resulted in no change to the table (same value already exists)
+ * 0
+ * Insert or update was successful, and the new efd_online_group_entry
+ * is stored in *entry
+ *
+ * @warning
+ * Note that entry will be UNCHANGED if the update has no effect, and thus any
+ * subsequent use of the entry content will likely be invalid
+ */
+static inline int
+efd_compute_update(struct rte_efd_table * const table,
+ const unsigned int socket_id, const void *key,
+ const efd_value_t value, uint32_t * const chunk_id,
+ uint32_t * const group_id, uint32_t * const bin_id,
+ uint8_t * const new_bin_choice,
+ struct efd_online_group_entry * const entry)
+{
+ unsigned int i;
+ int ret;
+ uint32_t new_idx;
+ void *new_k, *slot_id = NULL;
+ int status = EXIT_SUCCESS;
+ unsigned int found = 0;
+
+ efd_compute_ids(table, key, chunk_id, bin_id);
+
+ struct efd_offline_chunk_rules * const chunk =
+ &table->offline_chunks[*chunk_id];
+ struct efd_offline_group_rules *new_group;
+
+ uint8_t current_choice = efd_get_choice(table, socket_id,
+ *chunk_id, *bin_id);
+ uint32_t current_group_id = efd_bin_to_group[current_choice][*bin_id];
+ struct efd_offline_group_rules * const current_group =
+ &chunk->group_rules[current_group_id];
+ uint8_t bin_size = 0;
+ uint8_t key_changed_index = 0;
+ efd_value_t key_changed_previous_value = 0;
+ uint32_t key_idx_previous = 0;
+
+ /* Scan the current group and see if the key is already present */
+ for (i = 0; i < current_group->num_rules; i++) {
+ if (current_group->bin_id[i] == *bin_id)
+ bin_size++;
+ else
+ continue;
+
+ void *key_stored = EFD_KEY(current_group->key_idx[i], table);
+ if (found == 0 && unlikely(memcmp(key_stored, key,
+ table->key_len) == 0)) {
+ /* Key is already present */
+
+ /*
+ * If previous value is same as new value,
+ * no additional work is required
+ */
+ if (current_group->value[i] == value)
+ return RTE_EFD_UPDATE_NO_CHANGE;
+
+ key_idx_previous = current_group->key_idx[i];
+ key_changed_previous_value = current_group->value[i];
+ key_changed_index = i;
+ current_group->value[i] = value;
+ found = 1;
+ }
+ }
+
+ if (found == 0) {
+ /* Key does not exist. Insert the rule into the bin/group */
+ if (unlikely(current_group->num_rules >= EFD_MAX_GROUP_NUM_RULES)) {
+ RTE_LOG(ERR, EFD,
+ "Fatal: No room remaining for insert into "
+ "chunk %u group %u bin %u\n",
+ *chunk_id,
+ current_group_id, *bin_id);
+ return RTE_EFD_UPDATE_FAILED;
+ }
+
+ if (unlikely(current_group->num_rules ==
+ (EFD_MAX_GROUP_NUM_RULES - 1))) {
+ RTE_LOG(INFO, EFD, "Warn: Insert into last "
+ "available slot in chunk %u "
+ "group %u bin %u\n", *chunk_id,
+ current_group_id, *bin_id);
+ status = RTE_EFD_UPDATE_WARN_GROUP_FULL;
+ }
+
+ if (rte_ring_sc_dequeue(table->free_slots, &slot_id) != 0)
+ return RTE_EFD_UPDATE_FAILED;
+
+ new_k = RTE_PTR_ADD(table->keys, (uintptr_t) slot_id *
+ table->key_len);
+ rte_prefetch0(new_k);
+ new_idx = (uint32_t) ((uintptr_t) slot_id);
+
+ rte_memcpy(EFD_KEY(new_idx, table), key, table->key_len);
+ current_group->key_idx[current_group->num_rules] = new_idx;
+ current_group->value[current_group->num_rules] = value;
+ current_group->bin_id[current_group->num_rules] = *bin_id;
+ current_group->num_rules++;
+ table->num_rules++;
+ bin_size++;
+ } else {
+ uint32_t last = current_group->num_rules - 1;
+ /* Swap the key with the last key inserted*/
+ current_group->key_idx[key_changed_index] =
+ current_group->key_idx[last];
+ current_group->value[key_changed_index] =
+ current_group->value[last];
+ current_group->bin_id[key_changed_index] =
+ current_group->bin_id[last];
+
+ /*
+ * Key to be updated will always be available
+ * at the end of the group
+ */
+ current_group->key_idx[last] = key_idx_previous;
+ current_group->value[last] = value;
+ current_group->bin_id[last] = *bin_id;
+ }
+
+ *new_bin_choice = current_choice;
+ *group_id = current_group_id;
+ new_group = current_group;
+
+ /* Group need to be rebalanced when it starts to get loaded */
+ if (current_group->num_rules > EFD_MIN_BALANCED_NUM_RULES) {
+
+ /*
+ * Subtract the number of entries in the bin from
+ * the original group
+ */
+ current_group->num_rules -= bin_size;
+
+ /*
+ * Figure out which of the available groups that this bin
+ * can map to is the smallest (using the current group
+ * as baseline)
+ */
+ uint8_t smallest_choice = current_choice;
+ uint8_t smallest_size = current_group->num_rules;
+ uint32_t smallest_group_id = current_group_id;
+ unsigned char choice;
+
+ for (choice = 0; choice < EFD_CHUNK_NUM_BIN_TO_GROUP_SETS;
+ choice++) {
+ uint32_t test_group_id =
+ efd_bin_to_group[choice][*bin_id];
+ uint32_t num_rules =
+ chunk->group_rules[test_group_id].num_rules;
+ if (num_rules < smallest_size) {
+ smallest_choice = choice;
+ smallest_size = num_rules;
+ smallest_group_id = test_group_id;
+ }
+ }
+
+ *new_bin_choice = smallest_choice;
+ *group_id = smallest_group_id;
+ new_group = &chunk->group_rules[smallest_group_id];
+ current_group->num_rules += bin_size;
+
+ }
+
+ uint8_t choice = 0;
+ for (;;) {
+ if (current_group != new_group &&
+ new_group->num_rules + bin_size >
+ EFD_MAX_GROUP_NUM_RULES) {
+ RTE_LOG(DEBUG, EFD,
+ "Unable to move_groups to dest group "
+ "containing %u entries."
+ "bin_size:%u choice:%02x\n",
+ new_group->num_rules, bin_size,
+ choice - 1);
+ goto next_choice;
+ }
+ move_groups(*bin_id, bin_size, new_group, current_group);
+ /*
+ * Recompute the hash function for the modified group,
+ * and return it to the caller
+ */
+ ret = efd_search_hash(table, new_group, entry);
+
+ if (!ret)
+ return status;
+
+ RTE_LOG(DEBUG, EFD,
+ "Failed to find perfect hash for group "
+ "containing %u entries. bin_size:%u choice:%02x\n",
+ new_group->num_rules, bin_size, choice - 1);
+ /* Restore groups modified to their previous state */
+ revert_groups(current_group, new_group, bin_size);
+
+next_choice:
+ if (choice == EFD_CHUNK_NUM_BIN_TO_GROUP_SETS)
+ break;
+ *new_bin_choice = choice;
+ *group_id = efd_bin_to_group[choice][*bin_id];
+ new_group = &chunk->group_rules[*group_id];
+ choice++;
+ }
+
+ if (!found) {
+ current_group->num_rules--;
+ table->num_rules--;
+ } else
+ current_group->value[current_group->num_rules - 1] =
+ key_changed_previous_value;
+ return RTE_EFD_UPDATE_FAILED;
+}
+
+int
+rte_efd_update(struct rte_efd_table * const table, const unsigned int socket_id,
+ const void *key, const efd_value_t value)
+{
+ uint32_t chunk_id = 0, group_id = 0, bin_id = 0;
+ uint8_t new_bin_choice = 0;
+ struct efd_online_group_entry entry;
+
+ int status = efd_compute_update(table, socket_id, key, value,
+ &chunk_id, &group_id, &bin_id,
+ &new_bin_choice, &entry);
+
+ if (status == RTE_EFD_UPDATE_NO_CHANGE)
+ return EXIT_SUCCESS;
+
+ if (status == RTE_EFD_UPDATE_FAILED)
+ return status;
+
+ efd_apply_update(table, socket_id, chunk_id, group_id, bin_id,
+ new_bin_choice, &entry);
+ return status;
+}
+
+int
+rte_efd_delete(struct rte_efd_table * const table, const unsigned int socket_id,
+ const void *key, efd_value_t * const prev_value)
+{
+ unsigned int i;
+ uint32_t chunk_id, bin_id;
+ uint8_t not_found = 1;
+
+ efd_compute_ids(table, key, &chunk_id, &bin_id);
+
+ struct efd_offline_chunk_rules * const chunk =
+ &table->offline_chunks[chunk_id];
+
+ uint8_t current_choice = efd_get_choice(table, socket_id,
+ chunk_id, bin_id);
+ uint32_t current_group_id = efd_bin_to_group[current_choice][bin_id];
+ struct efd_offline_group_rules * const current_group =
+ &chunk->group_rules[current_group_id];
+
+ /*
+ * Search the current group for the specified key.
+ * If it exists, remove it and re-pack the other values
+ */
+ for (i = 0; i < current_group->num_rules; i++) {
+ if (not_found) {
+ /* Found key that needs to be removed */
+ if (memcmp(EFD_KEY(current_group->key_idx[i], table),
+ key, table->key_len) == 0) {
+ /* Store previous value if requested by caller */
+ if (prev_value != NULL)
+ *prev_value = current_group->value[i];
+
+ not_found = 0;
+ rte_ring_sp_enqueue(table->free_slots,
+ (void *)((uintptr_t)current_group->key_idx[i]));
+ }
+ } else {
+ /*
+ * If the desired key has been found,
+ * need to shift other values up one
+ */
+
+ /* Need to shift this entry back up one index */
+ current_group->key_idx[i - 1] = current_group->key_idx[i];
+ current_group->value[i - 1] = current_group->value[i];
+ current_group->bin_id[i - 1] = current_group->bin_id[i];
+ }
+ }
+
+ if (not_found == 0) {
+ table->num_rules--;
+ current_group->num_rules--;
+ }
+
+ return not_found;
+}
+
+static inline efd_value_t
+efd_lookup_internal_scalar(const efd_hashfunc_t *group_hash_idx,
+ const efd_lookuptbl_t *group_lookup_table,
+ const uint32_t hash_val_a, const uint32_t hash_val_b)
+{
+ efd_value_t value = 0;
+ uint32_t i;
+
+ for (i = 0; i < RTE_EFD_VALUE_NUM_BITS; i++) {
+ value <<= 1;
+ uint32_t h = hash_val_a + (hash_val_b *
+ group_hash_idx[RTE_EFD_VALUE_NUM_BITS - i - 1]);
+ uint16_t bucket_idx = h >> EFD_LOOKUPTBL_SHIFT;
+ value |= (group_lookup_table[
+ RTE_EFD_VALUE_NUM_BITS - i - 1] >>
+ bucket_idx) & 0x1;
+ }
+
+ return value;
+}
+
+
+static inline efd_value_t
+efd_lookup_internal(const struct efd_online_group_entry * const group,
+ const uint32_t hash_val_a, const uint32_t hash_val_b,
+ enum efd_lookup_internal_function lookup_fn)
+{
+ efd_value_t value = 0;
+
+ switch (lookup_fn) {
+
+ case EFD_LOOKUP_SCALAR:
+ /* Fall-through */
+ default:
+ return efd_lookup_internal_scalar(group->hash_idx,
+ group->lookup_table,
+ hash_val_a,
+ hash_val_b);
+ }
+
+ return value;
+}
+
+efd_value_t
+rte_efd_lookup(const struct rte_efd_table * const table,
+ const unsigned int socket_id, const void *key)
+{
+ uint32_t chunk_id, group_id, bin_id;
+ uint8_t bin_choice;
+ const struct efd_online_group_entry *group;
+ const struct efd_online_chunk * const chunks = table->chunks[socket_id];
+
+ /* Determine the chunk and group location for the given key */
+ efd_compute_ids(table, key, &chunk_id, &bin_id);
+ bin_choice = efd_get_choice(table, socket_id, chunk_id, bin_id);
+ group_id = efd_bin_to_group[bin_choice][bin_id];
+ group = &chunks[chunk_id].groups[group_id];
+
+ return efd_lookup_internal(group,
+ EFD_HASHFUNCA(key, table),
+ EFD_HASHFUNCB(key, table),
+ table->lookup_fn);
+}
+
+void rte_efd_lookup_bulk(const struct rte_efd_table * const table,
+ const unsigned int socket_id, const int num_keys,
+ const void **key_list, efd_value_t * const value_list)
+{
+ int i;
+ uint32_t chunk_id_list[RTE_EFD_BURST_MAX];
+ uint32_t bin_id_list[RTE_EFD_BURST_MAX];
+ uint8_t bin_choice_list[RTE_EFD_BURST_MAX];
+ uint32_t group_id_list[RTE_EFD_BURST_MAX];
+ struct efd_online_group_entry *group;
+
+ struct efd_online_chunk *chunks = table->chunks[socket_id];
+
+ for (i = 0; i < num_keys; i++) {
+ efd_compute_ids(table, key_list[i], &chunk_id_list[i],
+ &bin_id_list[i]);
+ rte_prefetch0(&chunks[chunk_id_list[i]].bin_choice_list);
+ }
+
+ for (i = 0; i < num_keys; i++) {
+ bin_choice_list[i] = efd_get_choice(table, socket_id,
+ chunk_id_list[i], bin_id_list[i]);
+ group_id_list[i] =
+ efd_bin_to_group[bin_choice_list[i]][bin_id_list[i]];
+ group = &chunks[chunk_id_list[i]].groups[group_id_list[i]];
+ rte_prefetch0(group);
+ }
+
+ for (i = 0; i < num_keys; i++) {
+ group = &chunks[chunk_id_list[i]].groups[group_id_list[i]];
+ value_list[i] = efd_lookup_internal(group,
+ EFD_HASHFUNCA(key_list[i], table),
+ EFD_HASHFUNCB(key_list[i], table),
+ table->lookup_fn);
+ }
+}
diff --git a/lib/librte_efd/rte_efd.h b/lib/librte_efd/rte_efd.h
new file mode 100644
index 0000000..1a1cb5b
--- /dev/null
+++ b/lib/librte_efd/rte_efd.h
@@ -0,0 +1,300 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _RTE_EFD_H_
+#define _RTE_EFD_H_
+
+/**
+ * @file
+ *
+ * RTE EFD Table
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*************************************************************************
+ * User selectable constants
+ *************************************************************************/
+
+/*
+ * If possible, best lookup performance will be achieved by ensuring that
+ * the entire table fits in the L3 cache.
+ *
+ * Some formulas for calculating various sizes are listed below:
+ *
+ * # of chunks =
+ * 2 ^ (ceiling(log2((requested # of rules) /
+ * (EFD_CHUNK_NUM_GROUPS * EFD_TARGET_GROUP_NUM_RULES))))
+ *
+ * Target # of rules = (# of chunks) * EFD_CHUNK_NUM_GROUPS *
+ * EFD_TARGET_GROUP_NUM_RULES
+ *
+ * Group Size (in bytes) = 4 (per value bit)
+ *
+ * Table size (in bytes) = RTE_EFD_VALUE_NUM_BITS * (# of chunks) *
+ * EFD_CHUNK_NUM_GROUPS * (group size)
+ */
+
+/**
+ * !!! This parameter should be adjusted for your application !!!
+ *
+ * This parameter adjusts the number of bits of value that can be
+ * stored in the table.
+ * For example, setting the number of bits to 3 will allow storing 8 values
+ * in the table (between 0 and 7).
+ *
+ * This number directly affects the performance of both lookups and insertion.
+ * In general, performance decreases as more bits are stored in the table.
+ *
+ * This number is directly proportional to the size of the online region
+ * used for lookups.
+ *
+ * Note that due to the way the CPU operates on memory, best lookup performance
+ * will be achieved when RTE_EFD_VALUE_NUM_BITS is a multiple of 8.
+ * These values align the hash indexes on 16-byte boundaries.
+ * The greatest performance drop is moving from 8->9 bits, 16->17 bits, etc.
+ *
+ * This value must be between 1 and 32
+ */
+#ifndef RTE_EFD_VALUE_NUM_BITS
+#define RTE_EFD_VALUE_NUM_BITS (8)
+#endif
+
+/*
+ * EFD_TARGET_GROUP_NUM_RULES:
+ * Adjusts how many groups/chunks are allocated at table creation time
+ * to support the requested number of rules. Higher values pack entries
+ * more tightly in memory, resulting in a smaller memory footprint
+ * for the online table.
+ * This comes at the cost of lower insert/update performance.
+ *
+ * EFD_MAX_GROUP_NUM_RULES:
+ * This adjusts the amount of offline memory allocated to store key/value
+ * pairs for the table. The recommended numbers are upper-bounds for
+ * this parameter
+ * - any higher and it becomes very unlikely that a perfect hash function
+ * can be found for that group size. This value should be at
+ * least 40% larger than EFD_TARGET_GROUP_NUM_RULES
+ *
+ * Recommended values for various lookuptable and hashfunc sizes are:
+ *
+ * HASH_FUNC_SIZE = 16, LOOKUPTBL_SIZE = 16:
+ * EFD_TARGET_GROUP_NUM_RULES = 22
+ * EFD_MAX_GROUP_NUM_RULES = 28
+ */
+#define EFD_TARGET_GROUP_NUM_RULES (22)
+#define EFD_MAX_GROUP_NUM_RULES (28LU)
+
+#define EFD_MIN_BALANCED_NUM_RULES 5
+
+/**
+ * Maximum number of keys that can be looked up in one call to efd_lookup_bulk
+ */
+#ifndef RTE_EFD_BURST_MAX
+#define RTE_EFD_BURST_MAX (32)
+#endif
+
+/** Maximum number of characters in efd name.*/
+#define RTE_EFD_NAMESIZE 32
+
+#if (RTE_EFD_VALUE_NUM_BITS > 0 && RTE_EFD_VALUE_NUM_BITS <= 8)
+typedef uint8_t efd_value_t;
+#elif (RTE_EFD_VALUE_NUM_BITS > 8 && RTE_EFD_VALUE_NUM_BITS <= 16)
+typedef uint16_t efd_value_t;
+#elif (RTE_EFD_VALUE_NUM_BITS > 16 && RTE_EFD_VALUE_NUM_BITS <= 32)
+typedef uint32_t efd_value_t;
+#else
+#error("RTE_EFD_VALUE_NUM_BITS must be in the range [1:32]")
+#endif
+
+#define EFD_LOOKUPTBL_SHIFT (32 - 4)
+typedef uint16_t efd_lookuptbl_t;
+typedef uint16_t efd_hashfunc_t;
+
+/**
+ * Creates an EFD table with a single offline region and multiple per-socket
+ * internally-managed copies of the online table used for lookups
+ *
+ * @param name
+ * EFD table name
+ * @param max_num_rules
+ * Minimum number of rules the table should be sized to hold.
+ * Will be rounded up to the next smallest valid table size
+ * @param key_len
+ * Length of the key
+ * @param online_cpu_socket_bitmask
+ * Bitmask specifying which sockets should get a copy of the online table.
+ * LSB = socket 0, etc.
+ * @param offline_cpu_socket
+ * Identifies the socket where the offline table will be allocated
+ * (and most efficiently accessed in the case of updates/insertions)
+ *
+ * @return
+ * EFD table, or NULL if table allocation failed or the bitmask is invalid
+ */
+struct rte_efd_table *
+rte_efd_create(const char *name, uint32_t max_num_rules, uint32_t key_len,
+ uint8_t online_cpu_socket_bitmask, uint8_t offline_cpu_socket);
+
+/**
+ * Releases the resources from an EFD table
+ *
+ * @param table
+ * Table to free
+ */
+void
+rte_efd_free(struct rte_efd_table *table);
+
+/**
+ * Find an existing EFD table object and return a pointer to it.
+ *
+ * @param name
+ * Name of the EFD table as passed to rte_efd_create()
+ * @return
+ * Pointer to EFD table or NULL if object not found
+ * with rte_errno set appropriately. Possible rte_errno values include:
+ * - ENOENT - value not available for return
+ */
+struct rte_efd_table*
+rte_efd_find_existing(const char *name);
+
+#define RTE_EFD_UPDATE_WARN_GROUP_FULL (1)
+#define RTE_EFD_UPDATE_NO_CHANGE (2)
+#define RTE_EFD_UPDATE_FAILED (3)
+
+/**
+ * Computes an updated table entry for the supplied key/value pair.
+ * The update is then immediately applied to the provided table and
+ * all socket-local copies of the chunks are updated.
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param key
+ * EFD table key to modify
+ * @param value
+ * Value to associate with the key
+ *
+ * @return
+ * RTE_EFD_UPDATE_WARN_GROUP_FULL
+ * Operation is insert, and the last available space in the
+ * key's group was just used
+ * Future inserts may fail as groups fill up
+ * This operation was still successful, and entry contains a valid update
+ * RTE_EFD_UPDATE_FAILED
+ * Either the EFD failed to find a suitable perfect hash or the group was full
+ * This is a fatal error, and the table is now in an indeterminite state
+ * RTE_EFD_UPDATE_NO_CHANGE
+ * Operation resulted in no change to the table (same value already exists)
+ * 0 - success
+ */
+int
+rte_efd_update(struct rte_efd_table *table, unsigned int socket_id,
+ const void *key, efd_value_t value);
+
+/**
+ * Removes any value currently associated with the specified key from the table
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param key
+ * EFD table key to delete
+ * @param prev_value
+ * If not NULL, will store the previous value here before deleting it
+ *
+ * @return
+ * 0 - successfully found and deleted the key
+ * nonzero otherwise
+ */
+int
+rte_efd_delete(struct rte_efd_table *table, unsigned int socket_id,
+ const void *key, efd_value_t *prev_value);
+
+/**
+ * Looks up the value associated with a key
+ *
+ * NOTE: Lookups will *always* succeed - this is a property of
+ * using a perfect hash table.
+ * If the specified key was never inserted, a pseudorandom answer will be returned.
+ * There is no way to know based on the lookup if the key was ever inserted
+ * originally, so this must be tracked elsewhere.
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param key
+ * EFD table key to look up
+ *
+ * @return
+ * Value associated with the key, or random junk if they key was never inserted
+ */
+efd_value_t
+rte_efd_lookup(const struct rte_efd_table *table, unsigned int socket_id,
+ const void *key);
+
+/**
+ * Looks up the value associated with several keys.
+ *
+ * NOTE: Lookups will *always* succeed - this is a property of
+ * using a perfect hash table.
+ * If the specified key was never inserted, a pseudorandom answer will be returned.
+ * There is no way to know based on the lookup if the key was ever inserted
+ * originally, so this must be tracked elsewhere.
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param num_keys
+ * Number of keys in the key_list array, must be less than RTE_EFD_BURST_MAX
+ * @param key_list
+ * Array of num_keys pointers which point to keys to look up
+ * @param value_list
+ * Array of size num_keys where lookup values will be stored
+ */
+void
+rte_efd_lookup_bulk(const struct rte_efd_table *table, unsigned int socket_id,
+ int num_keys, const void **key_list,
+ efd_value_t *value_list);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RTE_EFD_H_ */
diff --git a/lib/librte_efd/rte_efd_version.map b/lib/librte_efd/rte_efd_version.map
new file mode 100644
index 0000000..ae60a64
--- /dev/null
+++ b/lib/librte_efd/rte_efd_version.map
@@ -0,0 +1,13 @@
+DPDK_17.02 {
+ global:
+
+ rte_efd_create;
+ rte_efd_delete;
+ rte_efd_find_existing;
+ rte_efd_free;
+ rte_efd_lookup;
+ rte_efd_lookup_bulk;
+ rte_efd_update;
+
+ local: *;
+};
diff --git a/mk/rte.app.mk b/mk/rte.app.mk
index f75f0e2..ed1e68a 100644
--- a/mk/rte.app.mk
+++ b/mk/rte.app.mk
@@ -1,6 +1,6 @@
# BSD LICENSE
#
-# Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+# Copyright(c) 2010-2017 Intel Corporation. All rights reserved.
# Copyright(c) 2014-2015 6WIND S.A.
# All rights reserved.
#
@@ -86,6 +86,7 @@ _LDLIBS-y += --whole-archive
_LDLIBS-$(CONFIG_RTE_LIBRTE_TIMER) += -lrte_timer
_LDLIBS-$(CONFIG_RTE_LIBRTE_HASH) += -lrte_hash
+_LDLIBS-$(CONFIG_RTE_LIBRTE_EFD) += -lrte_efd
_LDLIBS-$(CONFIG_RTE_LIBRTE_VHOST) += -lrte_vhost
_LDLIBS-$(CONFIG_RTE_LIBRTE_KVARGS) += -lrte_kvargs
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v7 2/6] efd: add AVX2 vect lookup function
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 0/6] " Pablo de Lara
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 1/6] efd: new Elastic Flow Distributor library Pablo de Lara
@ 2017-01-17 22:10 ` Pablo de Lara
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 3/6] app/test: add EFD functional and perf tests Pablo de Lara
` (5 subsequent siblings)
7 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-17 22:10 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Byron Marohn, Saikrishna Edupuganti
Signed-off-by: Byron Marohn <byron.marohn@intel.com>
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
lib/librte_efd/rte_efd.c | 22 +++++++++++-
lib/librte_efd/rte_efd_x86.h | 86 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 107 insertions(+), 1 deletion(-)
create mode 100644 lib/librte_efd/rte_efd_x86.h
diff --git a/lib/librte_efd/rte_efd.c b/lib/librte_efd/rte_efd.c
index 2bcfd62..68e6dab 100644
--- a/lib/librte_efd/rte_efd.c
+++ b/lib/librte_efd/rte_efd.c
@@ -52,6 +52,9 @@
#include <rte_hash_crc.h>
#include "rte_efd.h"
+#if defined(RTE_ARCH_X86)
+#include "rte_efd_x86.h"
+#endif
#define EFD_KEY(key_idx, table) (table->keys + ((key_idx) * table->key_len))
/** Hash function used to determine chunk_id and bin_id for a group */
@@ -100,6 +103,7 @@ allocated memory
/* All different internal lookup functions */
enum efd_lookup_internal_function {
EFD_LOOKUP_SCALAR = 0,
+ EFD_LOOKUP_AVX2,
EFD_LOOKUP_NUM
};
@@ -662,7 +666,16 @@ rte_efd_create(const char *name, uint32_t max_num_rules, uint32_t key_len,
}
}
- table->lookup_fn = EFD_LOOKUP_SCALAR;
+#if defined(RTE_ARCH_X86)
+ /*
+ * For less than 4 bits, scalar function performs better
+ * than vectorised version
+ */
+ if (RTE_EFD_VALUE_NUM_BITS > 3 && rte_cpu_get_flag_enabled(RTE_CPUFLAG_AVX2))
+ table->lookup_fn = EFD_LOOKUP_AVX2;
+ else
+#endif
+ table->lookup_fn = EFD_LOOKUP_SCALAR;
/*
* Allocate the EFD table offline portion (with the actual rules
@@ -1253,6 +1266,13 @@ efd_lookup_internal(const struct efd_online_group_entry * const group,
switch (lookup_fn) {
+#if defined(RTE_ARCH_X86)
+ case EFD_LOOKUP_AVX2:
+ return efd_lookup_internal_avx2(group->hash_idx,
+ group->lookup_table,
+ hash_val_a,
+ hash_val_b);
+#endif
case EFD_LOOKUP_SCALAR:
/* Fall-through */
default:
diff --git a/lib/librte_efd/rte_efd_x86.h b/lib/librte_efd/rte_efd_x86.h
new file mode 100644
index 0000000..34f37d7
--- /dev/null
+++ b/lib/librte_efd/rte_efd_x86.h
@@ -0,0 +1,86 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* rte_efd_x86.h
+ * This file holds all x86 specific EFD functions
+ */
+#include <immintrin.h>
+
+#if (RTE_EFD_VALUE_NUM_BITS == 8 || RTE_EFD_VALUE_NUM_BITS == 16 || \
+ RTE_EFD_VALUE_NUM_BITS == 24 || RTE_EFD_VALUE_NUM_BITS == 32)
+#define EFD_LOAD_SI128(val) _mm_load_si128(val)
+#else
+#define EFD_LOAD_SI128(val) _mm_lddqu_si128(val)
+#endif
+
+static inline efd_value_t
+efd_lookup_internal_avx2(const efd_hashfunc_t *group_hash_idx,
+ const efd_lookuptbl_t *group_lookup_table,
+ const uint32_t hash_val_a, const uint32_t hash_val_b)
+{
+#ifdef RTE_MACHINE_CPUFLAG_AVX2
+ efd_value_t value = 0;
+ uint32_t i = 0;
+ __m256i vhash_val_a = _mm256_set1_epi32(hash_val_a);
+ __m256i vhash_val_b = _mm256_set1_epi32(hash_val_b);
+
+ for (; i < RTE_EFD_VALUE_NUM_BITS; i += 8) {
+ __m256i vhash_idx =
+ _mm256_cvtepu16_epi32(EFD_LOAD_SI128(
+ (__m128i const *) &group_hash_idx[i]));
+ __m256i vlookup_table = _mm256_cvtepu16_epi32(
+ EFD_LOAD_SI128((__m128i const *)
+ &group_lookup_table[i]));
+ __m256i vhash = _mm256_add_epi32(vhash_val_a,
+ _mm256_mullo_epi32(vhash_idx, vhash_val_b));
+ __m256i vbucket_idx = _mm256_srli_epi32(vhash,
+ EFD_LOOKUPTBL_SHIFT);
+ __m256i vresult = _mm256_srlv_epi32(vlookup_table,
+ vbucket_idx);
+
+ value |= (_mm256_movemask_ps(
+ (__m256) _mm256_slli_epi32(vresult, 31))
+ & ((1 << (RTE_EFD_VALUE_NUM_BITS - i)) - 1)) << i;
+ }
+
+ return value;
+#else
+ RTE_SET_USED(group_hash_idx);
+ RTE_SET_USED(group_lookup_table);
+ RTE_SET_USED(hash_val_a);
+ RTE_SET_USED(hash_val_b);
+ /* Return dummy value, only to avoid compilation breakage */
+ return 0;
+#endif
+
+}
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v7 3/6] app/test: add EFD functional and perf tests
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 0/6] " Pablo de Lara
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 1/6] efd: new Elastic Flow Distributor library Pablo de Lara
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 2/6] efd: add AVX2 vect lookup function Pablo de Lara
@ 2017-01-17 22:10 ` Pablo de Lara
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 4/6] examples/flow_distributor: sample app to demonstrate EFD usage Pablo de Lara
` (4 subsequent siblings)
7 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-17 22:10 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Byron Marohn, Karla Saur, Saikrishna Edupuganti
Signed-off-by: Byron Marohn <byron.marohn@intel.com>
Signed-off-by: Karla Saur <karla.saur@intel.com>
Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 1 +
app/test/Makefile | 5 +-
app/test/test_efd.c | 494 +++++++++++++++++++++++++++++++++++++++++++++++
app/test/test_efd_perf.c | 407 ++++++++++++++++++++++++++++++++++++++
4 files changed, 906 insertions(+), 1 deletion(-)
create mode 100644 app/test/test_efd.c
create mode 100644 app/test/test_efd_perf.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 9c60d67..d812962 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -532,6 +532,7 @@ EFD
M: Byron Marohn <byron.marohn@intel.com>
M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
F: lib/librte_efd/
+F: app/test/test_efd*
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
diff --git a/app/test/Makefile b/app/test/Makefile
index 5be023a..9de301f 100644
--- a/app/test/Makefile
+++ b/app/test/Makefile
@@ -1,6 +1,6 @@
# BSD LICENSE
#
-# Copyright(c) 2010-2015 Intel Corporation. All rights reserved.
+# Copyright(c) 2010-2017 Intel Corporation. All rights reserved.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -123,6 +123,9 @@ SRCS-y += test_logs.c
SRCS-y += test_memcpy.c
SRCS-y += test_memcpy_perf.c
+SRCS-$(CONFIG_RTE_LIBRTE_EFD) += test_efd.c
+SRCS-$(CONFIG_RTE_LIBRTE_EFD) += test_efd_perf.c
+
SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_hash.c
SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_thash.c
SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_hash_perf.c
diff --git a/app/test/test_efd.c b/app/test/test_efd.c
new file mode 100644
index 0000000..d5c3bd9
--- /dev/null
+++ b/app/test/test_efd.c
@@ -0,0 +1,494 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rte_memcpy.h>
+#include <rte_malloc.h>
+#include <rte_efd.h>
+#include <rte_byteorder.h>
+#include <rte_random.h>
+#include <rte_debug.h>
+#include <rte_ip.h>
+
+#include "test.h"
+
+#define EFD_TEST_KEY_LEN 8
+#define TABLE_SIZE (1 << 21)
+#define ITERATIONS 3
+static unsigned int test_socket_id;
+
+/* 5-tuple key type */
+struct flow_key {
+ uint32_t ip_src;
+ uint32_t ip_dst;
+ uint16_t port_src;
+ uint16_t port_dst;
+ uint8_t proto;
+} __attribute__((packed));
+/*
+ * Print out result of unit test efd operation.
+ */
+#if defined(UNIT_TEST_EFD_VERBOSE)
+
+static void print_key_info(const char *msg, const struct flow_key *key,
+ efd_value_t val)
+{
+ const uint8_t *p = (const uint8_t *) key;
+ unsigned int i;
+
+ printf("%s key:0x", msg);
+ for (i = 0; i < sizeof(struct flow_key); i++)
+ printf("%02X", p[i]);
+
+ printf(" @ val %d\n", val);
+}
+#else
+
+static void print_key_info(__attribute__((unused)) const char *msg,
+ __attribute__((unused)) const struct flow_key *key,
+ __attribute__((unused)) efd_value_t val)
+{
+}
+#endif
+
+/* Keys used by unit test functions */
+static struct flow_key keys[5] = {
+ {
+ .ip_src = IPv4(0x03, 0x02, 0x01, 0x00),
+ .ip_dst = IPv4(0x07, 0x06, 0x05, 0x04),
+ .port_src = 0x0908,
+ .port_dst = 0x0b0a,
+ .proto = 0x0c,
+ },
+ {
+ .ip_src = IPv4(0x13, 0x12, 0x11, 0x10),
+ .ip_dst = IPv4(0x17, 0x16, 0x15, 0x14),
+ .port_src = 0x1918,
+ .port_dst = 0x1b1a,
+ .proto = 0x1c,
+ },
+ {
+ .ip_src = IPv4(0x23, 0x22, 0x21, 0x20),
+ .ip_dst = IPv4(0x27, 0x26, 0x25, 0x24),
+ .port_src = 0x2928,
+ .port_dst = 0x2b2a,
+ .proto = 0x2c,
+ },
+ {
+ .ip_src = IPv4(0x33, 0x32, 0x31, 0x30),
+ .ip_dst = IPv4(0x37, 0x36, 0x35, 0x34),
+ .port_src = 0x3938,
+ .port_dst = 0x3b3a,
+ .proto = 0x3c,
+ },
+ {
+ .ip_src = IPv4(0x43, 0x42, 0x41, 0x40),
+ .ip_dst = IPv4(0x47, 0x46, 0x45, 0x44),
+ .port_src = 0x4948,
+ .port_dst = 0x4b4a,
+ .proto = 0x4c,
+ }
+};
+/* Array to store the data */
+efd_value_t data[5];
+
+static inline uint8_t efd_get_all_sockets_bitmask(void)
+{
+ uint8_t all_cpu_sockets_bitmask = 0;
+ unsigned int i;
+ unsigned int next_lcore = rte_get_master_lcore();
+ const int val_true = 1, val_false = 0;
+ for (i = 0; i < rte_lcore_count(); i++) {
+ all_cpu_sockets_bitmask |= 1 << rte_lcore_to_socket_id(next_lcore);
+ next_lcore = rte_get_next_lcore(next_lcore, val_false, val_true);
+ }
+
+ return all_cpu_sockets_bitmask;
+}
+
+/*
+ * Basic sequence of operations for a single key:
+ * - add
+ * - lookup (hit)
+ * - delete
+ * Note: lookup (miss) is not applicable since this is a filter
+ */
+static int test_add_delete(void)
+{
+ struct rte_efd_table *handle;
+ /* test with standard add/lookup/delete functions */
+ efd_value_t prev_value;
+ printf("Entering %s\n", __func__);
+
+ handle = rte_efd_create("test_add_delete",
+ TABLE_SIZE, sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the EFD table\n");
+
+ data[0] = mrand48() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id, &keys[0],
+ data[0]),
+ "Error inserting the key");
+ print_key_info("Add", &keys[0], data[0]);
+
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id, &keys[0]),
+ data[0],
+ "failed to find key");
+
+ TEST_ASSERT_SUCCESS(rte_efd_delete(handle, test_socket_id, &keys[0],
+ &prev_value),
+ "failed to delete key");
+ TEST_ASSERT_EQUAL(prev_value, data[0],
+ "failed to delete the expected value, got %d, "
+ "expected %d", prev_value, data[0]);
+ print_key_info("Del", &keys[0], data[0]);
+
+ rte_efd_free(handle);
+
+ return 0;
+}
+
+/*
+ * Sequence of operations for a single key:
+ * - add
+ * - lookup: hit
+ * - add: update
+ * - lookup: hit (updated data)
+ * - delete: hit
+ */
+static int test_add_update_delete(void)
+{
+ struct rte_efd_table *handle;
+ printf("Entering %s\n", __func__);
+ /* test with standard add/lookup/delete functions */
+ efd_value_t prev_value;
+ data[1] = mrand48() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+
+ handle = rte_efd_create("test_add_update_delete", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the efd table\n");
+
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id, &keys[1],
+ data[1]), "Error inserting the key");
+ print_key_info("Add", &keys[1], data[1]);
+
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id, &keys[1]),
+ data[1], "failed to find key");
+ print_key_info("Lkp", &keys[1], data[1]);
+
+ data[1] = data[1] + 1;
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id, &keys[1],
+ data[1]), "Error re-inserting the key");
+ print_key_info("Add", &keys[1], data[1]);
+
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id, &keys[1]),
+ data[1], "failed to find key");
+ print_key_info("Lkp", &keys[1], data[1]);
+
+ TEST_ASSERT_SUCCESS(rte_efd_delete(handle, test_socket_id, &keys[1],
+ &prev_value), "failed to delete key");
+ TEST_ASSERT_EQUAL(prev_value, data[1],
+ "failed to delete the expected value, got %d, "
+ "expected %d", prev_value, data[1]);
+ print_key_info("Del", &keys[1], data[1]);
+
+
+ rte_efd_free(handle);
+ return 0;
+}
+
+/*
+ * Sequence of operations for find existing EFD table
+ *
+ * - create table
+ * - find existing table: hit
+ * - find non-existing table: miss
+ *
+ */
+static int test_efd_find_existing(void)
+{
+ struct rte_efd_table *handle = NULL, *result = NULL;
+
+ printf("Entering %s\n", __func__);
+
+ /* Create EFD table. */
+ handle = rte_efd_create("efd_find_existing", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the efd table\n");
+
+ /* Try to find existing EFD table */
+ result = rte_efd_find_existing("efd_find_existing");
+ TEST_ASSERT_EQUAL(result, handle, "could not find existing efd table");
+
+ /* Try to find non-existing EFD table */
+ result = rte_efd_find_existing("efd_find_non_existing");
+ TEST_ASSERT_NULL(result, "found table that shouldn't exist");
+
+ /* Cleanup. */
+ rte_efd_free(handle);
+
+ return 0;
+}
+
+/*
+ * Sequence of operations for 5 keys
+ * - add keys
+ * - lookup keys: hit (bulk)
+ * - add keys (update)
+ * - lookup keys: hit (updated data)
+ * - delete keys : hit
+ */
+static int test_five_keys(void)
+{
+ struct rte_efd_table *handle;
+ const void *key_array[5] = {0};
+ efd_value_t result[5] = {0};
+ efd_value_t prev_value;
+ unsigned int i;
+ printf("Entering %s\n", __func__);
+
+ handle = rte_efd_create("test_five_keys", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the efd table\n");
+
+ /* Setup data */
+ for (i = 0; i < 5; i++)
+ data[i] = mrand48() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+
+ /* Add */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id,
+ &keys[i], data[i]),
+ "Error inserting the key");
+ print_key_info("Add", &keys[i], data[i]);
+ }
+
+ /* Lookup */
+ for (i = 0; i < 5; i++)
+ key_array[i] = &keys[i];
+
+ rte_efd_lookup_bulk(handle, test_socket_id, 5,
+ (const void **) (void *) &key_array, result);
+
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_EQUAL(result[i], data[i],
+ "bulk: failed to find key. Expected %d, got %d",
+ data[i], result[i]);
+ print_key_info("Lkp", &keys[i], data[i]);
+ }
+
+ /* Modify data (bulk) */
+ for (i = 0; i < 5; i++)
+ data[i] = data[i] + 1;
+
+ /* Add - update */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id,
+ &keys[i], data[i]),
+ "Error inserting the key");
+ print_key_info("Add", &keys[i], data[i]);
+ }
+
+ /* Lookup */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id,
+ &keys[i]), data[i],
+ "failed to find key");
+ print_key_info("Lkp", &keys[i], data[i]);
+ }
+
+ /* Delete */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_SUCCESS(rte_efd_delete(handle, test_socket_id,
+ &keys[i], &prev_value),
+ "failed to delete key");
+ TEST_ASSERT_EQUAL(prev_value, data[i],
+ "failed to delete the expected value, got %d, "
+ "expected %d", prev_value, data[i]);
+ print_key_info("Del", &keys[i], data[i]);
+ }
+
+
+ rte_efd_free(handle);
+
+ return 0;
+}
+
+/*
+ * Test to see the average table utilization (entries added/max entries)
+ * before hitting a random entry that cannot be added
+ */
+static int test_average_table_utilization(void)
+{
+ struct rte_efd_table *handle = NULL;
+ uint32_t num_rules_in = TABLE_SIZE;
+ uint8_t simple_key[EFD_TEST_KEY_LEN];
+ unsigned int i, j;
+ unsigned int added_keys, average_keys_added = 0;
+
+ printf("Evaluating table utilization and correctness, please wait\n");
+ fflush(stdout);
+
+ for (j = 0; j < ITERATIONS; j++) {
+ handle = rte_efd_create("test_efd", num_rules_in,
+ EFD_TEST_KEY_LEN, efd_get_all_sockets_bitmask(),
+ test_socket_id);
+ if (handle == NULL) {
+ printf("efd table creation failed\n");
+ return -1;
+ }
+
+ unsigned int succeeded = 0;
+ unsigned int lost_keys = 0;
+
+ /* Add random entries until key cannot be added */
+ for (added_keys = 0; added_keys < num_rules_in; added_keys++) {
+
+ for (i = 0; i < EFD_TEST_KEY_LEN; i++)
+ simple_key[i] = rte_rand() & 0xFF;
+
+ efd_value_t val = simple_key[0];
+
+ if (rte_efd_update(handle, test_socket_id, simple_key,
+ val))
+ break; /* continue;*/
+ if (rte_efd_lookup(handle, test_socket_id, simple_key)
+ != val)
+ lost_keys++;
+ else
+ succeeded++;
+ }
+
+ average_keys_added += succeeded;
+
+ /* Reset the table */
+ rte_efd_free(handle);
+
+ /* Print progress on operations */
+ printf("Added %10u Succeeded %10u Lost %10u\n",
+ added_keys, succeeded, lost_keys);
+ fflush(stdout);
+ }
+
+ average_keys_added /= ITERATIONS;
+
+ printf("\nAverage table utilization = %.2f%% (%u/%u)\n",
+ ((double) average_keys_added / num_rules_in * 100),
+ average_keys_added, num_rules_in);
+
+ return 0;
+}
+
+/*
+ * Do tests for EFD creation with bad parameters.
+ */
+static int test_efd_creation_with_bad_parameters(void)
+{
+ struct rte_efd_table *handle, *tmp;
+ printf("Entering %s, **Errors are expected **\n", __func__);
+
+ handle = rte_efd_create("creation_with_bad_parameters_0", TABLE_SIZE, 0,
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ if (handle != NULL) {
+ rte_efd_free(handle);
+ printf("Impossible creating EFD table successfully "
+ "if key_len in parameter is zero\n");
+ return -1;
+ }
+
+ handle = rte_efd_create("creation_with_bad_parameters_1", TABLE_SIZE,
+ sizeof(struct flow_key), 0, test_socket_id);
+ if (handle != NULL) {
+ rte_efd_free(handle);
+ printf("Impossible creating EFD table successfully "
+ "with invalid socket bitmask\n");
+ return -1;
+ }
+
+ handle = rte_efd_create("creation_with_bad_parameters_2", TABLE_SIZE,
+ sizeof(struct flow_key), efd_get_all_sockets_bitmask(),
+ 255);
+ if (handle != NULL) {
+ rte_efd_free(handle);
+ printf("Impossible creating EFD table successfully "
+ "with invalid socket\n");
+ return -1;
+ }
+
+ /* test with same name should fail */
+ handle = rte_efd_create("same_name", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), 0);
+ if (handle == NULL) {
+ printf("Cannot create first EFD table with 'same_name'\n");
+ return -1;
+ }
+ tmp = rte_efd_create("same_name", TABLE_SIZE, sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), 0);
+ if (tmp != NULL) {
+ printf("Creation of EFD table with same name should fail\n");
+ rte_efd_free(handle);
+ rte_efd_free(tmp);
+ return -1;
+ }
+ rte_efd_free(handle);
+
+ printf("# Test successful. No more errors expected\n");
+
+ return 0;
+}
+
+static int
+test_efd(void)
+{
+
+ /* Unit tests */
+ if (test_add_delete() < 0)
+ return -1;
+ if (test_efd_find_existing() < 0)
+ return -1;
+ if (test_add_update_delete() < 0)
+ return -1;
+ if (test_five_keys() < 0)
+ return -1;
+ if (test_efd_creation_with_bad_parameters() < 0)
+ return -1;
+ if (test_average_table_utilization() < 0)
+ return -1;
+
+ return 0;
+}
+
+REGISTER_TEST_COMMAND(efd_autotest, test_efd);
diff --git a/app/test/test_efd_perf.c b/app/test/test_efd_perf.c
new file mode 100644
index 0000000..998a25b
--- /dev/null
+++ b/app/test/test_efd_perf.c
@@ -0,0 +1,407 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <inttypes.h>
+
+#include <rte_lcore.h>
+#include <rte_cycles.h>
+#include <rte_malloc.h>
+#include <rte_random.h>
+#include <rte_efd.h>
+#include <rte_memcpy.h>
+#include <rte_thash.h>
+
+#include "test.h"
+#define NUM_KEYSIZES 10
+#define NUM_SHUFFLES 10
+#define MAX_KEYSIZE 64
+#define MAX_ENTRIES (1 << 19)
+#define KEYS_TO_ADD (MAX_ENTRIES * 3 / 4) /* 75% table utilization */
+#define NUM_LOOKUPS (KEYS_TO_ADD * 5) /* Loop among keys added, several times */
+static unsigned int test_socket_id;
+
+static inline uint8_t efd_get_all_sockets_bitmask(void)
+{
+ uint8_t all_cpu_sockets_bitmask = 0;
+ unsigned int i;
+ unsigned int next_lcore = rte_get_master_lcore();
+ const int val_true = 1, val_false = 0;
+ for (i = 0; i < rte_lcore_count(); i++) {
+ all_cpu_sockets_bitmask |= 1 << rte_lcore_to_socket_id(next_lcore);
+ next_lcore = rte_get_next_lcore(next_lcore, val_false, val_true);
+ }
+
+ return all_cpu_sockets_bitmask;
+}
+
+enum operations {
+ ADD = 0,
+ LOOKUP,
+ LOOKUP_MULTI,
+ DELETE,
+ NUM_OPERATIONS
+};
+
+struct efd_perf_params {
+ struct rte_efd_table *efd_table;
+ uint32_t key_size;
+ unsigned int cycle;
+};
+
+static uint32_t hashtest_key_lens[] = {
+ /* standard key sizes */
+ 4, 8, 16, 32, 48, 64,
+ /* IPv4 SRC + DST + protocol, unpadded */
+ 9,
+ /* IPv4 5-tuple, unpadded */
+ 13,
+ /* IPv6 5-tuple, unpadded */
+ 37,
+ /* IPv6 5-tuple, padded to 8-byte boundary */
+ 40
+};
+
+/* Array to store number of cycles per operation */
+uint64_t cycles[NUM_KEYSIZES][NUM_OPERATIONS];
+
+/* Array to store the data */
+efd_value_t data[KEYS_TO_ADD];
+
+/* Array to store all input keys */
+uint8_t keys[KEYS_TO_ADD][MAX_KEYSIZE];
+
+/* Shuffle the keys that have been added, so lookups will be totally random */
+static void
+shuffle_input_keys(struct efd_perf_params *params)
+{
+ efd_value_t temp_data;
+ unsigned int i;
+ uint32_t swap_idx;
+ uint8_t temp_key[MAX_KEYSIZE];
+
+ for (i = KEYS_TO_ADD - 1; i > 0; i--) {
+ swap_idx = rte_rand() % i;
+
+ memcpy(temp_key, keys[i], hashtest_key_lens[params->cycle]);
+ temp_data = data[i];
+
+ memcpy(keys[i], keys[swap_idx], hashtest_key_lens[params->cycle]);
+ data[i] = data[swap_idx];
+
+ memcpy(keys[swap_idx], temp_key, hashtest_key_lens[params->cycle]);
+ data[swap_idx] = temp_data;
+ }
+}
+
+static int key_compare(const void *key1, const void *key2)
+{
+ return memcmp(key1, key2, MAX_KEYSIZE);
+}
+
+/*
+ * TODO: we could "error proof" these as done in test_hash_perf.c ln 165:
+ *
+ * The current setup may give errors if too full in some cases which we check
+ * for. However, since EFD allows for ~99% capacity, these errors are rare for
+ * #"KEYS_TO_ADD" which is 75% capacity.
+ */
+static int
+setup_keys_and_data(struct efd_perf_params *params, unsigned int cycle)
+{
+ unsigned int i, j;
+ int num_duplicates;
+
+ params->key_size = hashtest_key_lens[cycle];
+ params->cycle = cycle;
+
+ /* Reset all arrays */
+ for (i = 0; i < params->key_size; i++)
+ keys[0][i] = 0;
+
+ /* Generate a list of keys, some of which may be duplicates */
+ for (i = 0; i < KEYS_TO_ADD; i++) {
+ for (j = 0; j < params->key_size; j++)
+ keys[i][j] = rte_rand() & 0xFF;
+
+ data[i] = rte_rand() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+ }
+
+ /* Remove duplicates from the keys array */
+ do {
+ num_duplicates = 0;
+
+ /* Sort the list of keys to make it easier to find duplicates */
+ qsort(keys, KEYS_TO_ADD, MAX_KEYSIZE, key_compare);
+
+ /* Sift through the list of keys and look for duplicates */
+ int num_duplicates = 0;
+ for (i = 0; i < KEYS_TO_ADD - 1; i++) {
+ if (memcmp(keys[i], keys[i + 1], params->key_size) == 0) {
+ /* This key already exists, try again */
+ num_duplicates++;
+ for (j = 0; j < params->key_size; j++)
+ keys[i][j] = rte_rand() & 0xFF;
+ }
+ }
+ } while (num_duplicates != 0);
+
+ /* Shuffle the random values again */
+ shuffle_input_keys(params);
+
+ params->efd_table = rte_efd_create("test_efd_perf",
+ MAX_ENTRIES, params->key_size,
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(params->efd_table, "Error creating the efd table\n");
+
+ return 0;
+}
+
+static int
+timed_adds(struct efd_perf_params *params)
+{
+ const uint64_t start_tsc = rte_rdtsc();
+ unsigned int i, a;
+ int32_t ret;
+
+ for (i = 0; i < KEYS_TO_ADD; i++) {
+ ret = rte_efd_update(params->efd_table, test_socket_id, keys[i],
+ data[i]);
+ if (ret != 0) {
+ printf("Error %d in rte_efd_update - key=0x", ret);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x", keys[i][a]);
+ printf(" value=%d\n", data[i]);
+
+ return -1;
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][ADD] = time_taken / KEYS_TO_ADD;
+ return 0;
+}
+
+static int
+timed_lookups(struct efd_perf_params *params)
+{
+ unsigned int i, j, a;
+ const uint64_t start_tsc = rte_rdtsc();
+ efd_value_t ret_data;
+
+ for (i = 0; i < NUM_LOOKUPS / KEYS_TO_ADD; i++) {
+ for (j = 0; j < KEYS_TO_ADD; j++) {
+ ret_data = rte_efd_lookup(params->efd_table,
+ test_socket_id, keys[j]);
+ if (ret_data != data[j]) {
+ printf("Value mismatch using rte_efd_lookup: "
+ "key #%d (0x", i);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x", keys[i][a]);
+ printf(")\n");
+ printf(" Expected %d, got %d\n", data[i],
+ ret_data);
+
+ return -1;
+ }
+
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][LOOKUP] = time_taken / NUM_LOOKUPS;
+
+ return 0;
+}
+
+static int
+timed_lookups_multi(struct efd_perf_params *params)
+{
+ unsigned int i, j, k, a;
+ efd_value_t result[RTE_EFD_BURST_MAX] = {0};
+ const void *keys_burst[RTE_EFD_BURST_MAX];
+ const uint64_t start_tsc = rte_rdtsc();
+
+ for (i = 0; i < NUM_LOOKUPS / KEYS_TO_ADD; i++) {
+ for (j = 0; j < KEYS_TO_ADD / RTE_EFD_BURST_MAX; j++) {
+ for (k = 0; k < RTE_EFD_BURST_MAX; k++)
+ keys_burst[k] = keys[j * RTE_EFD_BURST_MAX + k];
+
+ rte_efd_lookup_bulk(params->efd_table, test_socket_id,
+ RTE_EFD_BURST_MAX,
+ keys_burst, result);
+
+ for (k = 0; k < RTE_EFD_BURST_MAX; k++) {
+ uint32_t data_idx = j * RTE_EFD_BURST_MAX + k;
+ if (result[k] != data[data_idx]) {
+ printf("Value mismatch using "
+ "rte_efd_lookup_bulk: key #%d "
+ "(0x", i);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x",
+ keys[data_idx][a]);
+ printf(")\n");
+ printf(" Expected %d, got %d\n",
+ data[data_idx], result[k]);
+
+ return -1;
+ }
+ }
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][LOOKUP_MULTI] = time_taken / NUM_LOOKUPS;
+
+ return 0;
+}
+
+static int
+timed_deletes(struct efd_perf_params *params)
+{
+ unsigned int i, a;
+ const uint64_t start_tsc = rte_rdtsc();
+ int32_t ret;
+
+ for (i = 0; i < KEYS_TO_ADD; i++) {
+ ret = rte_efd_delete(params->efd_table, test_socket_id, keys[i],
+ NULL);
+
+ if (ret != 0) {
+ printf("Error %d in rte_efd_delete - key=0x", ret);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x", keys[i][a]);
+ printf("\n");
+
+ return -1;
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][DELETE] = time_taken / KEYS_TO_ADD;
+
+ return 0;
+}
+
+static void
+perform_frees(struct efd_perf_params *params)
+{
+ if (params->efd_table != NULL) {
+ rte_efd_free(params->efd_table);
+ params->efd_table = NULL;
+ }
+}
+
+static int
+exit_with_fail(const char *testname, struct efd_perf_params *params,
+ unsigned int i)
+{
+
+ printf("<<<<<Test %s failed at keysize %d iteration %d >>>>>\n",
+ testname, hashtest_key_lens[params->cycle], i);
+ perform_frees(params);
+ return -1;
+}
+
+static int
+run_all_tbl_perf_tests(void)
+{
+ unsigned int i, j;
+ struct efd_perf_params params;
+
+ printf("Measuring performance, please wait\n");
+ fflush(stdout);
+
+ test_socket_id = rte_socket_id();
+
+ for (i = 0; i < NUM_KEYSIZES; i++) {
+
+ if (setup_keys_and_data(¶ms, i) < 0) {
+ printf("Could not create keys/data/table\n");
+ return -1;
+ }
+
+ if (timed_adds(¶ms) < 0)
+ return exit_with_fail("timed_adds", ¶ms, i);
+
+ for (j = 0; j < NUM_SHUFFLES; j++)
+ shuffle_input_keys(¶ms);
+
+ if (timed_lookups(¶ms) < 0)
+ return exit_with_fail("timed_lookups", ¶ms, i);
+
+ if (timed_lookups_multi(¶ms) < 0)
+ return exit_with_fail("timed_lookups_multi", ¶ms, i);
+
+ if (timed_deletes(¶ms) < 0)
+ return exit_with_fail("timed_deletes", ¶ms, i);
+
+ /* Print a dot to show progress on operations */
+ printf(".");
+ fflush(stdout);
+
+ perform_frees(¶ms);
+ }
+
+ printf("\nResults (in CPU cycles/operation)\n");
+ printf("-----------------------------------\n");
+ printf("\n%-18s%-18s%-18s%-18s%-18s\n",
+ "Keysize", "Add", "Lookup", "Lookup_bulk", "Delete");
+ for (i = 0; i < NUM_KEYSIZES; i++) {
+ printf("%-18d", hashtest_key_lens[i]);
+ for (j = 0; j < NUM_OPERATIONS; j++)
+ printf("%-18"PRIu64, cycles[i][j]);
+ printf("\n");
+ }
+ return 0;
+}
+
+static int
+test_efd_perf(void)
+{
+
+ if (run_all_tbl_perf_tests() < 0)
+ return -1;
+
+ return 0;
+}
+
+REGISTER_TEST_COMMAND(efd_perf_autotest, test_efd_perf);
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v7 4/6] examples/flow_distributor: sample app to demonstrate EFD usage
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 0/6] " Pablo de Lara
` (2 preceding siblings ...)
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 3/6] app/test: add EFD functional and perf tests Pablo de Lara
@ 2017-01-17 22:10 ` Pablo de Lara
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 5/6] doc: add EFD library section in Programmers guide Pablo de Lara
` (3 subsequent siblings)
7 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-17 22:10 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Saikrishna Edupuganti
This new sample app, based on the client/server sample app,
shows the user an scenario using the EFD library.
It consists of:
- A front-end server which has an EFD table that stores the
node id for each flow key, which will distribute the incoming
packets to the different nodes
- A back-end node, which has a hash table where node checks,
after reading packets coming from the server, whether the packet
is meant to be used in such node, in which case it will be TXed,
or not, in which case, packet will be dropped.
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 1 +
doc/api/examples.dox | 4 +
examples/Makefile | 1 +
examples/flow_distributor/Makefile | 44 +++
examples/flow_distributor/distributor/Makefile | 57 ++++
examples/flow_distributor/distributor/args.c | 200 ++++++++++++
examples/flow_distributor/distributor/args.h | 39 +++
examples/flow_distributor/distributor/init.c | 371 ++++++++++++++++++++++
examples/flow_distributor/distributor/init.h | 76 +++++
examples/flow_distributor/distributor/main.c | 362 +++++++++++++++++++++
examples/flow_distributor/node/Makefile | 48 +++
examples/flow_distributor/node/node.c | 417 +++++++++++++++++++++++++
examples/flow_distributor/shared/common.h | 99 ++++++
13 files changed, 1719 insertions(+)
create mode 100644 examples/flow_distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/args.c
create mode 100644 examples/flow_distributor/distributor/args.h
create mode 100644 examples/flow_distributor/distributor/init.c
create mode 100644 examples/flow_distributor/distributor/init.h
create mode 100644 examples/flow_distributor/distributor/main.c
create mode 100644 examples/flow_distributor/node/Makefile
create mode 100644 examples/flow_distributor/node/node.c
create mode 100644 examples/flow_distributor/shared/common.h
diff --git a/MAINTAINERS b/MAINTAINERS
index d812962..b124f6e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -533,6 +533,7 @@ M: Byron Marohn <byron.marohn@intel.com>
M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
F: lib/librte_efd/
F: app/test/test_efd*
+F: examples/flow_distributor/
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
diff --git a/doc/api/examples.dox b/doc/api/examples.dox
index 1626852..c13e574 100644
--- a/doc/api/examples.dox
+++ b/doc/api/examples.dox
@@ -52,6 +52,10 @@
@example load_balancer/init.c
@example load_balancer/main.c
@example load_balancer/runtime.c
+@example flow_distributor/distributor/args.c
+@example flow_distributor/distributor/init.c
+@example flow_distributor/distributor/main.c
+@example flow_distributor/node/node.c
@example multi_process/client_server_mp/mp_client/client.c
@example multi_process/client_server_mp/mp_server/args.c
@example multi_process/client_server_mp/mp_server/init.c
diff --git a/examples/Makefile b/examples/Makefile
index d49c7f2..b404982 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -45,6 +45,7 @@ DIRS-y += dpdk_qat
endif
DIRS-y += ethtool
DIRS-y += exception_path
+DIRS-$(CONFIG_RTE_LIBRTE_EFD) += flow_distributor
DIRS-y += helloworld
DIRS-$(CONFIG_RTE_LIBRTE_PIPELINE) += ip_pipeline
ifeq ($(CONFIG_RTE_LIBRTE_LPM),y)
diff --git a/examples/flow_distributor/Makefile b/examples/flow_distributor/Makefile
new file mode 100644
index 0000000..5bae706
--- /dev/null
+++ b/examples/flow_distributor/Makefile
@@ -0,0 +1,44 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overridden by command line or environment
+RTE_TARGET ?= x86_64-native-linuxapp-gcc
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+DIRS-$(CONFIG_RTE_EXEC_ENV_LINUXAPP) += distributor
+DIRS-$(CONFIG_RTE_EXEC_ENV_LINUXAPP) += node
+
+include $(RTE_SDK)/mk/rte.extsubdir.mk
diff --git a/examples/flow_distributor/distributor/Makefile b/examples/flow_distributor/distributor/Makefile
new file mode 100644
index 0000000..8714151
--- /dev/null
+++ b/examples/flow_distributor/distributor/Makefile
@@ -0,0 +1,57 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overridden by command line or environment
+RTE_TARGET ?= x86_64-native-linuxapp-gcc
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+ifneq ($(CONFIG_RTE_EXEC_ENV), "linuxapp")
+$(error This application can only operate in a linuxapp environment, \
+please change the definition of the RTE_TARGET environment variable)
+endif
+
+# binary name
+APP = distributor
+
+# all source are stored in SRCS-y
+SRCS-y := main.c init.c args.c
+
+INC := $(wildcard *.h)
+
+CFLAGS += $(WERROR_FLAGS) -O3
+CFLAGS += -I$(SRCDIR)/../shared
+
+include $(RTE_SDK)/mk/rte.extapp.mk
diff --git a/examples/flow_distributor/distributor/args.c b/examples/flow_distributor/distributor/args.c
new file mode 100644
index 0000000..ee29203
--- /dev/null
+++ b/examples/flow_distributor/distributor/args.c
@@ -0,0 +1,200 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <getopt.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include <rte_memory.h>
+#include <rte_string_fns.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+/* 1M flows by default */
+#define DEFAULT_NUM_FLOWS 0x100000
+
+/* global var for number of nodes - extern in header */
+uint8_t num_nodes;
+/* global var for number of flows - extern in header */
+uint32_t num_flows = DEFAULT_NUM_FLOWS;
+
+static const char *progname;
+
+/**
+ * Prints out usage information to stdout
+ */
+static void
+usage(void)
+{
+ printf("%s [EAL options] -- -p PORTMASK -n NUM_NODES -f NUM_FLOWS\n"
+ " -p PORTMASK: hexadecimal bitmask of ports to use\n"
+ " -n NUM_NODES: number of node processes to use\n"
+ " -f NUM_FLOWS: number of flows to be added in the EFD table\n",
+ progname);
+}
+
+/**
+ * The ports to be used by the application are passed in
+ * the form of a bitmask. This function parses the bitmask
+ * and places the port numbers to be used into the port[]
+ * array variable
+ */
+static int
+parse_portmask(uint8_t max_ports, const char *portmask)
+{
+ char *end = NULL;
+ unsigned long pm;
+ uint8_t count = 0;
+
+ if (portmask == NULL || *portmask == '\0')
+ return -1;
+
+ /* convert parameter to a number and verify */
+ pm = strtoul(portmask, &end, 16);
+ if (end == NULL || *end != '\0' || pm == 0)
+ return -1;
+
+ /* loop through bits of the mask and mark ports */
+ while (pm != 0) {
+ if (pm & 0x01) { /* bit is set in mask, use port */
+ if (count >= max_ports)
+ printf("WARNING: requested port %u not present"
+ " - ignoring\n", (unsigned int)count);
+ else
+ info->id[info->num_ports++] = count;
+ }
+ pm = (pm >> 1);
+ count++;
+ }
+
+ return 0;
+}
+
+/**
+ * Take the number of nodes parameter passed to the app
+ * and convert to a number to store in the num_nodes variable
+ */
+static int
+parse_num_nodes(const char *nodes)
+{
+ char *end = NULL;
+ unsigned long temp;
+
+ if (nodes == NULL || *nodes == '\0')
+ return -1;
+
+ temp = strtoul(nodes, &end, 10);
+ if (end == NULL || *end != '\0' || temp == 0)
+ return -1;
+
+ num_nodes = (uint8_t)temp;
+ return 0;
+}
+
+static int
+parse_num_flows(const char *flows)
+{
+ char *end = NULL;
+
+ /* parse hexadecimal string */
+ num_flows = strtoul(flows, &end, 16);
+ if ((flows[0] == '\0') || (end == NULL) || (*end != '\0'))
+ return -1;
+
+ if (num_flows == 0)
+ return -1;
+
+ return 0;
+}
+
+/**
+ * The application specific arguments follow the DPDK-specific
+ * arguments which are stripped by the DPDK init. This function
+ * processes these application arguments, printing usage info
+ * on error.
+ */
+int
+parse_app_args(uint8_t max_ports, int argc, char *argv[])
+{
+ int option_index, opt;
+ char **argvopt = argv;
+ static struct option lgopts[] = { /* no long options */
+ {NULL, 0, 0, 0 }
+ };
+ progname = argv[0];
+
+ while ((opt = getopt_long(argc, argvopt, "n:f:p:", lgopts,
+ &option_index)) != EOF) {
+ switch (opt) {
+ case 'p':
+ if (parse_portmask(max_ports, optarg) != 0) {
+ usage();
+ return -1;
+ }
+ break;
+ case 'n':
+ if (parse_num_nodes(optarg) != 0) {
+ usage();
+ return -1;
+ }
+ break;
+ case 'f':
+ if (parse_num_flows(optarg) != 0) {
+ usage();
+ return -1;
+ }
+ break;
+ default:
+ printf("ERROR: Unknown option '%c'\n", opt);
+ usage();
+ return -1;
+ }
+ }
+
+ if (info->num_ports == 0 || num_nodes == 0) {
+ usage();
+ return -1;
+ }
+
+ if (info->num_ports % 2 != 0) {
+ printf("ERROR: application requires an even "
+ "number of ports to use\n");
+ return -1;
+ }
+ return 0;
+}
diff --git a/examples/flow_distributor/distributor/args.h b/examples/flow_distributor/distributor/args.h
new file mode 100644
index 0000000..cacf395
--- /dev/null
+++ b/examples/flow_distributor/distributor/args.h
@@ -0,0 +1,39 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _ARGS_H_
+#define _ARGS_H_
+
+int parse_app_args(uint8_t max_ports, int argc, char *argv[]);
+
+#endif /* ifndef _ARGS_H_ */
diff --git a/examples/flow_distributor/distributor/init.c b/examples/flow_distributor/distributor/init.c
new file mode 100644
index 0000000..3b0aa85
--- /dev/null
+++ b/examples/flow_distributor/distributor/init.c
@@ -0,0 +1,371 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/queue.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <inttypes.h>
+
+#include <rte_common.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_byteorder.h>
+#include <rte_atomic.h>
+#include <rte_launch.h>
+#include <rte_per_lcore.h>
+#include <rte_lcore.h>
+#include <rte_branch_prediction.h>
+#include <rte_debug.h>
+#include <rte_ring.h>
+#include <rte_log.h>
+#include <rte_mempool.h>
+#include <rte_memcpy.h>
+#include <rte_mbuf.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ether.h>
+#include <rte_ethdev.h>
+#include <rte_malloc.h>
+#include <rte_string_fns.h>
+#include <rte_cycles.h>
+#include <rte_efd.h>
+#include <rte_hash.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+#define MBUFS_PER_NODE 1536
+#define MBUFS_PER_PORT 1536
+#define MBUF_CACHE_SIZE 512
+
+#define RTE_MP_RX_DESC_DEFAULT 512
+#define RTE_MP_TX_DESC_DEFAULT 512
+#define NODE_QUEUE_RINGSIZE 128
+
+#define NO_FLAGS 0
+
+/* The mbuf pool for packet rx */
+struct rte_mempool *pktmbuf_pool;
+
+/* array of info/queues for nodes */
+struct node *nodes;
+
+/* Flow distributor table */
+struct rte_efd_table *efd_table;
+
+/* Shared info between distributor and nodes */
+struct shared_info *info;
+
+/**
+ * Initialise the mbuf pool for packet reception for the NIC, and any other
+ * buffer pools needed by the app - currently none.
+ */
+static int
+init_mbuf_pools(void)
+{
+ const unsigned int num_mbufs = (num_nodes * MBUFS_PER_NODE) +
+ (info->num_ports * MBUFS_PER_PORT);
+
+ /*
+ * Don't pass single-producer/single-consumer flags to mbuf create as it
+ * seems faster to use a cache instead
+ */
+ printf("Creating mbuf pool '%s' [%u mbufs] ...\n",
+ PKTMBUF_POOL_NAME, num_mbufs);
+ pktmbuf_pool = rte_pktmbuf_pool_create(PKTMBUF_POOL_NAME, num_mbufs,
+ MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
+
+ return pktmbuf_pool == NULL; /* 0 on success */
+}
+
+/**
+ * Initialise an individual port:
+ * - configure number of rx and tx rings
+ * - set up each rx ring, to pull from the main mbuf pool
+ * - set up each tx ring
+ * - start the port and report its status to stdout
+ */
+static int
+init_port(uint8_t port_num)
+{
+ /* for port configuration all features are off by default */
+ const struct rte_eth_conf port_conf = {
+ .rxmode = {
+ .mq_mode = ETH_MQ_RX_RSS
+ }
+ };
+ const uint16_t rx_rings = 1, tx_rings = num_nodes;
+ const uint16_t rx_ring_size = RTE_MP_RX_DESC_DEFAULT;
+ const uint16_t tx_ring_size = RTE_MP_TX_DESC_DEFAULT;
+
+ uint16_t q;
+ int retval;
+
+ printf("Port %u init ... ", (unsigned int)port_num);
+ fflush(stdout);
+
+ /*
+ * Standard DPDK port initialisation - config port, then set up
+ * rx and tx rings.
+ */
+ retval = rte_eth_dev_configure(port_num, rx_rings, tx_rings, &port_conf);
+ if (retval != 0)
+ return retval;
+
+ for (q = 0; q < rx_rings; q++) {
+ retval = rte_eth_rx_queue_setup(port_num, q, rx_ring_size,
+ rte_eth_dev_socket_id(port_num),
+ NULL, pktmbuf_pool);
+ if (retval < 0)
+ return retval;
+ }
+
+ for (q = 0; q < tx_rings; q++) {
+ retval = rte_eth_tx_queue_setup(port_num, q, tx_ring_size,
+ rte_eth_dev_socket_id(port_num),
+ NULL);
+ if (retval < 0)
+ return retval;
+ }
+
+ rte_eth_promiscuous_enable(port_num);
+
+ retval = rte_eth_dev_start(port_num);
+ if (retval < 0)
+ return retval;
+
+ printf("done:\n");
+
+ return 0;
+}
+
+/**
+ * Set up the DPDK rings which will be used to pass packets, via
+ * pointers, between the multi-process distributor and node processes.
+ * Each node needs one RX queue.
+ */
+static int
+init_shm_rings(void)
+{
+ unsigned int i;
+ unsigned int socket_id;
+ const char *q_name;
+ const unsigned int ringsize = NODE_QUEUE_RINGSIZE;
+
+ nodes = rte_malloc("node details",
+ sizeof(*nodes) * num_nodes, 0);
+ if (nodes == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot allocate memory for "
+ "node program details\n");
+
+ for (i = 0; i < num_nodes; i++) {
+ /* Create an RX queue for each node */
+ socket_id = rte_socket_id();
+ q_name = get_rx_queue_name(i);
+ nodes[i].rx_q = rte_ring_create(q_name,
+ ringsize, socket_id,
+ RING_F_SP_ENQ | RING_F_SC_DEQ);
+ if (nodes[i].rx_q == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot create rx ring queue "
+ "for node %u\n", i);
+ }
+ return 0;
+}
+
+/*
+ * Create flow distributor table which will contain all the flows
+ * that will be distributed among the nodes
+ */
+static void
+create_flow_distributor_table(void)
+{
+ uint8_t socket_id = rte_socket_id();
+
+ /* create table */
+ efd_table = rte_efd_create("flow table", num_flows * 2, sizeof(uint32_t),
+ 1 << socket_id, socket_id);
+
+ if (efd_table == NULL)
+ rte_exit(EXIT_FAILURE, "Problem creating the flow table\n");
+}
+
+static void
+populate_flow_distributor_table(void)
+{
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint8_t socket_id = rte_socket_id();
+ uint64_t node_id;
+
+ /* Add flows in table */
+ for (i = 0; i < num_flows; i++) {
+ node_id = i % num_nodes;
+
+ ip_dst = rte_cpu_to_be_32(i);
+ ret = rte_efd_update(efd_table, socket_id,
+ (void *)&ip_dst, (efd_value_t)node_id);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u in "
+ "flow distributor table\n", i);
+ }
+
+ printf("EFD table: Adding 0x%x keys\n", num_flows);
+}
+
+/* Check the link status of all ports in up to 9s, and print them finally */
+static void
+check_all_ports_link_status(uint8_t port_num, uint32_t port_mask)
+{
+#define CHECK_INTERVAL 100 /* 100ms */
+#define MAX_CHECK_TIME 90 /* 9s (90 * 100ms) in total */
+ uint8_t portid, count, all_ports_up, print_flag = 0;
+ struct rte_eth_link link;
+
+ printf("\nChecking link status");
+ fflush(stdout);
+ for (count = 0; count <= MAX_CHECK_TIME; count++) {
+ all_ports_up = 1;
+ for (portid = 0; portid < port_num; portid++) {
+ if ((port_mask & (1 << info->id[portid])) == 0)
+ continue;
+ memset(&link, 0, sizeof(link));
+ rte_eth_link_get_nowait(info->id[portid], &link);
+ /* print link status if flag set */
+ if (print_flag == 1) {
+ if (link.link_status)
+ printf("Port %d Link Up - speed %u "
+ "Mbps - %s\n", info->id[portid],
+ (unsigned int)link.link_speed,
+ (link.link_duplex == ETH_LINK_FULL_DUPLEX) ?
+ ("full-duplex") : ("half-duplex\n"));
+ else
+ printf("Port %d Link Down\n",
+ (uint8_t)info->id[portid]);
+ continue;
+ }
+ /* clear all_ports_up flag if any link down */
+ if (link.link_status == ETH_LINK_DOWN) {
+ all_ports_up = 0;
+ break;
+ }
+ }
+ /* after finally printing all link status, get out */
+ if (print_flag == 1)
+ break;
+
+ if (all_ports_up == 0) {
+ printf(".");
+ fflush(stdout);
+ rte_delay_ms(CHECK_INTERVAL);
+ }
+
+ /* set the print_flag if all ports up or timeout */
+ if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
+ print_flag = 1;
+ printf("done\n");
+ }
+ }
+}
+
+/**
+ * Main init function for the multi-process distributor app,
+ * calls subfunctions to do each stage of the initialisation.
+ */
+int
+init(int argc, char *argv[])
+{
+ int retval;
+ const struct rte_memzone *mz;
+ uint8_t i, total_ports;
+
+ /* init EAL, parsing EAL args */
+ retval = rte_eal_init(argc, argv);
+ if (retval < 0)
+ return -1;
+ argc -= retval;
+ argv += retval;
+
+ /* get total number of ports */
+ total_ports = rte_eth_dev_count();
+
+ /* set up array for port data */
+ mz = rte_memzone_reserve(MZ_SHARED_INFO, sizeof(*info),
+ rte_socket_id(), NO_FLAGS);
+ if (mz == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot reserve memory zone "
+ "for port information\n");
+ memset(mz->addr, 0, sizeof(*info));
+ info = mz->addr;
+
+ /* parse additional, application arguments */
+ retval = parse_app_args(total_ports, argc, argv);
+ if (retval != 0)
+ return -1;
+
+ /* initialise mbuf pools */
+ retval = init_mbuf_pools();
+ if (retval != 0)
+ rte_exit(EXIT_FAILURE, "Cannot create needed mbuf pools\n");
+
+ /* now initialise the ports we will use */
+ for (i = 0; i < info->num_ports; i++) {
+ retval = init_port(info->id[i]);
+ if (retval != 0)
+ rte_exit(EXIT_FAILURE, "Cannot initialise port %u\n",
+ (unsigned int) i);
+ }
+
+ check_all_ports_link_status(info->num_ports, (~0x0));
+
+ /* initialise the node queues/rings for inter-eu comms */
+ init_shm_rings();
+
+ /* Create the flow distributor table */
+ create_flow_distributor_table();
+
+ /* Populate the flow distributor table */
+ populate_flow_distributor_table();
+
+ /* Share the total number of nodes */
+ info->num_nodes = num_nodes;
+
+ /* Share the total number of flows */
+ info->num_flows = num_flows;
+ return 0;
+}
diff --git a/examples/flow_distributor/distributor/init.h b/examples/flow_distributor/distributor/init.h
new file mode 100644
index 0000000..d11aacf
--- /dev/null
+++ b/examples/flow_distributor/distributor/init.h
@@ -0,0 +1,76 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _INIT_H_
+#define _INIT_H_
+
+/*
+ * #include <rte_ring.h>
+ * #include "args.h"
+ */
+
+/*
+ * Define a node structure with all needed info, including
+ * stats from the nodes.
+ */
+struct node {
+ struct rte_ring *rx_q;
+ unsigned int node_id;
+ /* these stats hold how many packets the node will actually receive,
+ * and how many packets were dropped because the node's queue was full.
+ * The port-info stats, in contrast, record how many packets were received
+ * or transmitted on an actual NIC port.
+ */
+ struct {
+ uint64_t rx;
+ uint64_t rx_drop;
+ } stats;
+};
+
+extern struct rte_efd_table *efd_table;
+extern struct node *nodes;
+
+/*
+ * shared information between distributor and nodes: number of clients,
+ * port numbers, rx and tx stats etc.
+ */
+extern struct shared_info *info;
+
+extern struct rte_mempool *pktmbuf_pool;
+extern uint8_t num_nodes;
+extern unsigned int num_sockets;
+extern uint32_t num_flows;
+
+int init(int argc, char *argv[]);
+
+#endif /* ifndef _INIT_H_ */
diff --git a/examples/flow_distributor/distributor/main.c b/examples/flow_distributor/distributor/main.c
new file mode 100644
index 0000000..f97f003
--- /dev/null
+++ b/examples/flow_distributor/distributor/main.c
@@ -0,0 +1,362 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <inttypes.h>
+#include <sys/queue.h>
+#include <errno.h>
+#include <netinet/ip.h>
+
+#include <rte_common.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_byteorder.h>
+#include <rte_launch.h>
+#include <rte_per_lcore.h>
+#include <rte_lcore.h>
+#include <rte_branch_prediction.h>
+#include <rte_atomic.h>
+#include <rte_ring.h>
+#include <rte_log.h>
+#include <rte_debug.h>
+#include <rte_mempool.h>
+#include <rte_memcpy.h>
+#include <rte_mbuf.h>
+#include <rte_ether.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ethdev.h>
+#include <rte_byteorder.h>
+#include <rte_malloc.h>
+#include <rte_string_fns.h>
+#include <rte_efd.h>
+#include <rte_ip.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+/*
+ * When doing reads from the NIC or the node queues,
+ * use this batch size
+ */
+#define PACKET_READ_SIZE 32
+
+/*
+ * Local buffers to put packets in, used to send packets in bursts to the
+ * nodes
+ */
+struct node_rx_buf {
+ struct rte_mbuf *buffer[PACKET_READ_SIZE];
+ uint16_t count;
+};
+
+struct flow_distributor_stats {
+ uint64_t distributed;
+ uint64_t drop;
+} flow_dist_stats;
+
+/* One buffer per node rx queue - dynamically allocate array */
+static struct node_rx_buf *cl_rx_buf;
+
+static const char *
+get_printable_mac_addr(uint8_t port)
+{
+ static const char err_address[] = "00:00:00:00:00:00";
+ static char addresses[RTE_MAX_ETHPORTS][sizeof(err_address)];
+ struct ether_addr mac;
+
+ if (unlikely(port >= RTE_MAX_ETHPORTS))
+ return err_address;
+ if (unlikely(addresses[port][0] == '\0')) {
+ rte_eth_macaddr_get(port, &mac);
+ snprintf(addresses[port], sizeof(addresses[port]),
+ "%02x:%02x:%02x:%02x:%02x:%02x\n",
+ mac.addr_bytes[0], mac.addr_bytes[1],
+ mac.addr_bytes[2], mac.addr_bytes[3],
+ mac.addr_bytes[4], mac.addr_bytes[5]);
+ }
+ return addresses[port];
+}
+
+/*
+ * This function displays the recorded statistics for each port
+ * and for each node. It uses ANSI terminal codes to clear
+ * screen when called. It is called from a single non-master
+ * thread in the distributor process, when the process is run with more
+ * than one lcore enabled.
+ */
+static void
+do_stats_display(void)
+{
+ unsigned int i, j;
+ const char clr[] = {27, '[', '2', 'J', '\0'};
+ const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
+ uint64_t port_tx[RTE_MAX_ETHPORTS], port_tx_drop[RTE_MAX_ETHPORTS];
+ uint64_t node_tx[MAX_NODES], node_tx_drop[MAX_NODES];
+
+ /* to get TX stats, we need to do some summing calculations */
+ memset(port_tx, 0, sizeof(port_tx));
+ memset(port_tx_drop, 0, sizeof(port_tx_drop));
+ memset(node_tx, 0, sizeof(node_tx));
+ memset(node_tx_drop, 0, sizeof(node_tx_drop));
+
+ for (i = 0; i < num_nodes; i++) {
+ const struct tx_stats *tx = &info->tx_stats[i];
+
+ for (j = 0; j < info->num_ports; j++) {
+ const uint64_t tx_val = tx->tx[info->id[j]];
+ const uint64_t drop_val = tx->tx_drop[info->id[j]];
+
+ port_tx[j] += tx_val;
+ port_tx_drop[j] += drop_val;
+ node_tx[i] += tx_val;
+ node_tx_drop[i] += drop_val;
+ }
+ }
+
+ /* Clear screen and move to top left */
+ printf("%s%s", clr, topLeft);
+
+ printf("PORTS\n");
+ printf("-----\n");
+ for (i = 0; i < info->num_ports; i++)
+ printf("Port %u: '%s'\t", (unsigned int)info->id[i],
+ get_printable_mac_addr(info->id[i]));
+ printf("\n\n");
+ for (i = 0; i < info->num_ports; i++) {
+ printf("Port %u - rx: %9"PRIu64"\t"
+ "tx: %9"PRIu64"\n",
+ (unsigned int)info->id[i], info->rx_stats.rx[i],
+ port_tx[i]);
+ }
+
+ printf("\nFLOW DISTRIBUTOR\n");
+ printf("-----\n");
+ printf("distributed: %9"PRIu64", drop: %9"PRIu64"\n",
+ flow_dist_stats.distributed, flow_dist_stats.drop);
+
+ printf("\nNODES\n");
+ printf("-------\n");
+ for (i = 0; i < num_nodes; i++) {
+ const unsigned long long rx = nodes[i].stats.rx;
+ const unsigned long long rx_drop = nodes[i].stats.rx_drop;
+ const struct filter_stats *filter = &info->filter_stats[i];
+
+ printf("Node %2u - rx: %9llu, rx_drop: %9llu\n"
+ " tx: %9"PRIu64", tx_drop: %9"PRIu64"\n"
+ " filter_passed: %9"PRIu64", "
+ "filter_drop: %9"PRIu64"\n",
+ i, rx, rx_drop, node_tx[i], node_tx_drop[i],
+ filter->passed, filter->drop);
+ }
+
+ printf("\n");
+}
+
+/*
+ * The function called from each non-master lcore used by the process.
+ * The test_and_set function is used to randomly pick a single lcore on which
+ * the code to display the statistics will run. Otherwise, the code just
+ * repeatedly sleeps.
+ */
+static int
+sleep_lcore(__attribute__((unused)) void *dummy)
+{
+ /* Used to pick a display thread - static, so zero-initialised */
+ static rte_atomic32_t display_stats;
+
+ /* Only one core should display stats */
+ if (rte_atomic32_test_and_set(&display_stats)) {
+ const unsigned int sleeptime = 1;
+
+ printf("Core %u displaying statistics\n", rte_lcore_id());
+
+ /* Longer initial pause so above printf is seen */
+ sleep(sleeptime * 3);
+
+ /* Loop forever: sleep always returns 0 or <= param */
+ while (sleep(sleeptime) <= sleeptime)
+ do_stats_display();
+ }
+ return 0;
+}
+
+/*
+ * Function to set all the node statistic values to zero.
+ * Called at program startup.
+ */
+static void
+clear_stats(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_nodes; i++)
+ nodes[i].stats.rx = nodes[i].stats.rx_drop = 0;
+}
+
+/*
+ * send a burst of traffic to a node, assuming there are packets
+ * available to be sent to this node
+ */
+static void
+flush_rx_queue(uint16_t node)
+{
+ uint16_t j;
+ struct node *cl;
+
+ if (cl_rx_buf[node].count == 0)
+ return;
+
+ cl = &nodes[node];
+ if (rte_ring_enqueue_bulk(cl->rx_q, (void **)cl_rx_buf[node].buffer,
+ cl_rx_buf[node].count) != 0){
+ for (j = 0; j < cl_rx_buf[node].count; j++)
+ rte_pktmbuf_free(cl_rx_buf[node].buffer[j]);
+ cl->stats.rx_drop += cl_rx_buf[node].count;
+ } else
+ cl->stats.rx += cl_rx_buf[node].count;
+
+ cl_rx_buf[node].count = 0;
+}
+
+/*
+ * marks a packet down to be sent to a particular node process
+ */
+static inline void
+enqueue_rx_packet(uint8_t node, struct rte_mbuf *buf)
+{
+ cl_rx_buf[node].buffer[cl_rx_buf[node].count++] = buf;
+}
+
+/*
+ * This function takes a group of packets and routes them
+ * individually to the node process. Very simply round-robins the packets
+ * without checking any of the packet contents.
+ */
+static void
+process_packets(uint32_t port_num __rte_unused, struct rte_mbuf *pkts[],
+ uint16_t rx_count, unsigned int socket_id)
+{
+ uint16_t i;
+ uint8_t node;
+ efd_value_t data[RTE_EFD_BURST_MAX];
+ const void *key_ptrs[RTE_EFD_BURST_MAX];
+
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[RTE_EFD_BURST_MAX];
+
+ for (i = 0; i < rx_count; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(pkts[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = (void *)&ipv4_dst_ip[i];
+ }
+
+ rte_efd_lookup_bulk(efd_table, socket_id, rx_count,
+ (const void **) key_ptrs, data);
+ for (i = 0; i < rx_count; i++) {
+ node = (uint8_t) ((uintptr_t)data[i]);
+
+ if (node >= num_nodes) {
+ /*
+ * Node is out of range, which means that
+ * flow has not been inserted
+ */
+ flow_dist_stats.drop++;
+ rte_pktmbuf_free(pkts[i]);
+ } else {
+ flow_dist_stats.distributed++;
+ enqueue_rx_packet(node, pkts[i]);
+ }
+ }
+
+ for (i = 0; i < num_nodes; i++)
+ flush_rx_queue(i);
+}
+
+/*
+ * Function called by the master lcore of the DPDK process.
+ */
+static void
+do_packet_forwarding(void)
+{
+ unsigned int port_num = 0; /* indexes the port[] array */
+ unsigned int socket_id = rte_socket_id();
+
+ for (;;) {
+ struct rte_mbuf *buf[PACKET_READ_SIZE];
+ uint16_t rx_count;
+
+ /* read a port */
+ rx_count = rte_eth_rx_burst(info->id[port_num], 0,
+ buf, PACKET_READ_SIZE);
+ info->rx_stats.rx[port_num] += rx_count;
+
+ /* Now process the NIC packets read */
+ if (likely(rx_count > 0))
+ process_packets(port_num, buf, rx_count, socket_id);
+
+ /* move to next port */
+ if (++port_num == info->num_ports)
+ port_num = 0;
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ /* initialise the system */
+ if (init(argc, argv) < 0)
+ return -1;
+ RTE_LOG(INFO, APP, "Finished Process Init.\n");
+
+ cl_rx_buf = calloc(num_nodes, sizeof(cl_rx_buf[0]));
+
+ /* clear statistics */
+ clear_stats();
+
+ /* put all other cores to sleep bar master */
+ rte_eal_mp_remote_launch(sleep_lcore, NULL, SKIP_MASTER);
+
+ do_packet_forwarding();
+ return 0;
+}
diff --git a/examples/flow_distributor/node/Makefile b/examples/flow_distributor/node/Makefile
new file mode 100644
index 0000000..8cf7b65
--- /dev/null
+++ b/examples/flow_distributor/node/Makefile
@@ -0,0 +1,48 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overridden by command line or environment
+include $(RTE_SDK)/mk/rte.vars.mk
+
+# binary name
+APP = node
+
+# all source are stored in SRCS-y
+SRCS-y := node.c
+
+CFLAGS += $(WERROR_FLAGS) -O3
+CFLAGS += -I$(SRCDIR)/../shared
+
+include $(RTE_SDK)/mk/rte.extapp.mk
diff --git a/examples/flow_distributor/node/node.c b/examples/flow_distributor/node/node.c
new file mode 100644
index 0000000..1f1e7e7
--- /dev/null
+++ b/examples/flow_distributor/node/node.c
@@ -0,0 +1,417 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <sys/queue.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_malloc.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_atomic.h>
+#include <rte_branch_prediction.h>
+#include <rte_log.h>
+#include <rte_per_lcore.h>
+#include <rte_launch.h>
+#include <rte_lcore.h>
+#include <rte_ring.h>
+#include <rte_launch.h>
+#include <rte_lcore.h>
+#include <rte_debug.h>
+#include <rte_mempool.h>
+#include <rte_mbuf.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ether.h>
+#include <rte_ethdev.h>
+#include <rte_string_fns.h>
+#include <rte_ip.h>
+
+#include "common.h"
+
+/* Number of packets to attempt to read from queue */
+#define PKT_READ_SIZE ((uint16_t)32)
+
+/*
+ * Our node id number - tells us which rx queue to read, and NIC TX
+ * queue to write to.
+ */
+static uint8_t node_id;
+
+#define MBQ_CAPACITY 32
+
+/* maps input ports to output ports for packets */
+static uint8_t output_ports[RTE_MAX_ETHPORTS];
+
+/* buffers up a set of packet that are ready to send */
+struct rte_eth_dev_tx_buffer *tx_buffer[RTE_MAX_ETHPORTS];
+
+/* shared data from distributor. We update statistics here */
+static struct tx_stats *tx_stats;
+
+static struct filter_stats *filter_stats;
+
+/*
+ * print a usage message
+ */
+static void
+usage(const char *progname)
+{
+ printf("Usage: %s [EAL args] -- -n <node_id>\n\n", progname);
+}
+
+/*
+ * Convert the node id number from a string to an int.
+ */
+static int
+parse_node_num(const char *node)
+{
+ char *end = NULL;
+ unsigned long temp;
+
+ if (node == NULL || *node == '\0')
+ return -1;
+
+ temp = strtoul(node, &end, 10);
+ if (end == NULL || *end != '\0')
+ return -1;
+
+ node_id = (uint8_t)temp;
+ return 0;
+}
+
+/*
+ * Parse the application arguments to the node app.
+ */
+static int
+parse_app_args(int argc, char *argv[])
+{
+ int option_index, opt;
+ char **argvopt = argv;
+ const char *progname = NULL;
+ static struct option lgopts[] = { /* no long options */
+ {NULL, 0, 0, 0 }
+ };
+ progname = argv[0];
+
+ while ((opt = getopt_long(argc, argvopt, "n:", lgopts,
+ &option_index)) != EOF) {
+ switch (opt) {
+ case 'n':
+ if (parse_node_num(optarg) != 0) {
+ usage(progname);
+ return -1;
+ }
+ break;
+ default:
+ usage(progname);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Tx buffer error callback
+ */
+static void
+flush_tx_error_callback(struct rte_mbuf **unsent, uint16_t count,
+ void *userdata) {
+ int i;
+ uint8_t port_id = (uintptr_t)userdata;
+
+ tx_stats->tx_drop[port_id] += count;
+
+ /* free the mbufs which failed from transmit */
+ for (i = 0; i < count; i++)
+ rte_pktmbuf_free(unsent[i]);
+
+}
+
+static void
+configure_tx_buffer(uint8_t port_id, uint16_t size)
+{
+ int ret;
+
+ /* Initialize TX buffers */
+ tx_buffer[port_id] = rte_zmalloc_socket("tx_buffer",
+ RTE_ETH_TX_BUFFER_SIZE(size), 0,
+ rte_eth_dev_socket_id(port_id));
+ if (tx_buffer[port_id] == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot allocate buffer for tx "
+ "on port %u\n", (unsigned int) port_id);
+
+ rte_eth_tx_buffer_init(tx_buffer[port_id], size);
+
+ ret = rte_eth_tx_buffer_set_err_callback(tx_buffer[port_id],
+ flush_tx_error_callback, (void *)(intptr_t)port_id);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Cannot set error callback for "
+ "tx buffer on port %u\n", (unsigned int) port_id);
+}
+
+/*
+ * set up output ports so that all traffic on port gets sent out
+ * its paired port. Index using actual port numbers since that is
+ * what comes in the mbuf structure.
+ */
+static void
+configure_output_ports(const struct shared_info *info)
+{
+ int i;
+
+ if (info->num_ports > RTE_MAX_ETHPORTS)
+ rte_exit(EXIT_FAILURE, "Too many ethernet ports. "
+ "RTE_MAX_ETHPORTS = %u\n",
+ (unsigned int)RTE_MAX_ETHPORTS);
+ for (i = 0; i < info->num_ports - 1; i += 2) {
+ uint8_t p1 = info->id[i];
+ uint8_t p2 = info->id[i+1];
+
+ output_ports[p1] = p2;
+ output_ports[p2] = p1;
+
+ configure_tx_buffer(p1, MBQ_CAPACITY);
+ configure_tx_buffer(p2, MBQ_CAPACITY);
+
+ }
+}
+
+/*
+ * Create the hash table that will contain the flows that
+ * the node will handle, which will be used to decide if packet
+ * is transmitted or dropped.
+ */
+static struct rte_hash *
+create_hash_table(const struct shared_info *info)
+{
+ uint32_t num_flows_node = info->num_flows / info->num_nodes;
+ char name[RTE_HASH_NAMESIZE];
+ struct rte_hash *h;
+
+ /* create table */
+ struct rte_hash_parameters hash_params = {
+ .entries = num_flows_node * 2, /* table load = 50% */
+ .key_len = sizeof(uint32_t), /* Store IPv4 dest IP address */
+ .socket_id = rte_socket_id(),
+ .hash_func_init_val = 0,
+ };
+
+ snprintf(name, sizeof(name), "hash_table_%d", node_id);
+ hash_params.name = name;
+ h = rte_hash_create(&hash_params);
+
+ if (h == NULL)
+ rte_exit(EXIT_FAILURE,
+ "Problem creating the hash table for node %d\n",
+ node_id);
+ return h;
+}
+
+static void
+populate_hash_table(const struct rte_hash *h, const struct shared_info *info)
+{
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint32_t num_flows_node = 0;
+ uint64_t target_node;
+
+ /* Add flows in table */
+ for (i = 0; i < info->num_flows; i++) {
+ target_node = i % info->num_nodes;
+ if (target_node != node_id)
+ continue;
+
+ ip_dst = rte_cpu_to_be_32(i);
+
+ ret = rte_hash_add_key(h, (void *) &ip_dst);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u "
+ "in hash table\n", i);
+ else
+ num_flows_node++;
+
+ }
+
+ printf("Hash table: Adding 0x%x keys\n", num_flows_node);
+}
+
+/*
+ * This function performs routing of packets
+ * Just sends each input packet out an output port based solely on the input
+ * port it arrived on.
+ */
+static inline void
+transmit_packet(struct rte_mbuf *buf)
+{
+ int sent;
+ const uint8_t in_port = buf->port;
+ const uint8_t out_port = output_ports[in_port];
+ struct rte_eth_dev_tx_buffer *buffer = tx_buffer[out_port];
+
+ sent = rte_eth_tx_buffer(out_port, node_id, buffer, buf);
+ if (sent)
+ tx_stats->tx[out_port] += sent;
+
+}
+
+static inline void
+handle_packets(struct rte_hash *h, struct rte_mbuf **bufs, uint16_t num_packets)
+{
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[PKT_READ_SIZE];
+ const void *key_ptrs[PKT_READ_SIZE];
+ unsigned int i;
+ int32_t positions[PKT_READ_SIZE] = {0};
+
+ for (i = 0; i < num_packets; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(bufs[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = &ipv4_dst_ip[i];
+ }
+ /* Check if packets belongs to any flows handled by this node */
+ rte_hash_lookup_bulk(h, key_ptrs, num_packets, positions);
+
+ for (i = 0; i < num_packets; i++) {
+ if (likely(positions[i] >= 0)) {
+ filter_stats->passed++;
+ transmit_packet(bufs[i]);
+ } else {
+ filter_stats->drop++;
+ /* Drop packet, as flow is not handled by this node */
+ rte_pktmbuf_free(bufs[i]);
+ }
+ }
+}
+
+/*
+ * Application main function - loops through
+ * receiving and processing packets. Never returns
+ */
+int
+main(int argc, char *argv[])
+{
+ const struct rte_memzone *mz;
+ struct rte_ring *rx_ring;
+ struct rte_hash *h;
+ struct rte_mempool *mp;
+ struct shared_info *info;
+ int need_flush = 0; /* indicates whether we have unsent packets */
+ int retval;
+ void *pkts[PKT_READ_SIZE];
+ uint16_t sent;
+
+ retval = rte_eal_init(argc, argv);
+ if (retval < 0)
+ return -1;
+ argc -= retval;
+ argv += retval;
+
+ if (parse_app_args(argc, argv) < 0)
+ rte_exit(EXIT_FAILURE, "Invalid command-line arguments\n");
+
+ if (rte_eth_dev_count() == 0)
+ rte_exit(EXIT_FAILURE, "No Ethernet ports - bye\n");
+
+ rx_ring = rte_ring_lookup(get_rx_queue_name(node_id));
+ if (rx_ring == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get RX ring - "
+ "is distributor process running?\n");
+
+ mp = rte_mempool_lookup(PKTMBUF_POOL_NAME);
+ if (mp == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get mempool for mbufs\n");
+
+ mz = rte_memzone_lookup(MZ_SHARED_INFO);
+ if (mz == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get port info structure\n");
+ info = mz->addr;
+ tx_stats = &(info->tx_stats[node_id]);
+ filter_stats = &(info->filter_stats[node_id]);
+
+ configure_output_ports(info);
+
+ h = create_hash_table(info);
+
+ populate_hash_table(h, info);
+
+ RTE_LOG(INFO, APP, "Finished Process Init.\n");
+
+ printf("\nNode process %d handling packets\n", node_id);
+ printf("[Press Ctrl-C to quit ...]\n");
+
+ for (;;) {
+ uint16_t rx_pkts = PKT_READ_SIZE;
+ uint8_t port;
+
+ /*
+ * Try dequeuing max possible packets first, if that fails,
+ * get the most we can. Loop body should only execute once,
+ * maximum
+ */
+ while (rx_pkts > 0 &&
+ unlikely(rte_ring_dequeue_bulk(rx_ring, pkts,
+ rx_pkts) != 0))
+ rx_pkts = (uint16_t)RTE_MIN(rte_ring_count(rx_ring),
+ PKT_READ_SIZE);
+
+ if (unlikely(rx_pkts == 0)) {
+ if (need_flush)
+ for (port = 0; port < info->num_ports; port++) {
+ sent = rte_eth_tx_buffer_flush(
+ info->id[port],
+ node_id,
+ tx_buffer[port]);
+ if (unlikely(sent))
+ tx_stats->tx[port] += sent;
+ }
+ need_flush = 0;
+ continue;
+ }
+
+ handle_packets(h, (struct rte_mbuf **)pkts, rx_pkts);
+
+ need_flush = 1;
+ }
+}
diff --git a/examples/flow_distributor/shared/common.h b/examples/flow_distributor/shared/common.h
new file mode 100644
index 0000000..5dcffd6
--- /dev/null
+++ b/examples/flow_distributor/shared/common.h
@@ -0,0 +1,99 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _COMMON_H_
+#define _COMMON_H_
+
+#include <rte_hash_crc.h>
+#include <rte_hash.h>
+
+#define MAX_NODES 16
+/*
+ * Shared port info, including statistics information for display by distributor.
+ * Structure will be put in a memzone.
+ * - All port id values share one cache line as this data will be read-only
+ * during operation.
+ * - All rx statistic values share cache lines, as this data is written only
+ * by the distributor process. (rare reads by stats display)
+ * - The tx statistics have values for all ports per cache line, but the stats
+ * themselves are written by the nodes, so we have a distinct set, on different
+ * cache lines for each node to use.
+ */
+struct rx_stats {
+ uint64_t rx[RTE_MAX_ETHPORTS];
+} __rte_cache_aligned;
+
+struct tx_stats {
+ uint64_t tx[RTE_MAX_ETHPORTS];
+ uint64_t tx_drop[RTE_MAX_ETHPORTS];
+} __rte_cache_aligned;
+
+struct filter_stats {
+ uint64_t drop;
+ uint64_t passed;
+} __rte_cache_aligned;
+
+struct shared_info {
+ uint8_t num_nodes;
+ uint8_t num_ports;
+ uint32_t num_flows;
+ uint8_t id[RTE_MAX_ETHPORTS];
+ struct rx_stats rx_stats;
+ struct tx_stats tx_stats[MAX_NODES];
+ struct filter_stats filter_stats[MAX_NODES];
+};
+
+/* define common names for structures shared between distributor and node */
+#define MP_NODE_RXQ_NAME "MProc_Node_%u_RX"
+#define PKTMBUF_POOL_NAME "MProc_pktmbuf_pool"
+#define MZ_SHARED_INFO "MProc_shared_info"
+
+/*
+ * Given the rx queue name template above, get the queue name
+ */
+static inline const char *
+get_rx_queue_name(unsigned int id)
+{
+ /*
+ * Buffer for return value. Size calculated by %u being replaced
+ * by maximum 3 digits (plus an extra byte for safety)
+ */
+ static char buffer[sizeof(MP_NODE_RXQ_NAME) + 2];
+
+ snprintf(buffer, sizeof(buffer) - 1, MP_NODE_RXQ_NAME, id);
+ return buffer;
+}
+
+#define RTE_LOGTYPE_APP RTE_LOGTYPE_USER1
+
+#endif
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v7 5/6] doc: add EFD library section in Programmers guide
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 0/6] " Pablo de Lara
` (3 preceding siblings ...)
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 4/6] examples/flow_distributor: sample app to demonstrate EFD usage Pablo de Lara
@ 2017-01-17 22:10 ` Pablo de Lara
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 6/6] doc: add flow distributor guide Pablo de Lara
` (2 subsequent siblings)
7 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-17 22:10 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Sameh Gobriel
Signed-off-by: Sameh Gobriel <sameh.gobriel@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 1 +
doc/guides/prog_guide/efd_lib.rst | 440 +++++++++++
doc/guides/prog_guide/img/efd_i1.svg | 130 ++++
doc/guides/prog_guide/img/efd_i10.svg | 384 ++++++++++
doc/guides/prog_guide/img/efd_i11.svg | 319 ++++++++
doc/guides/prog_guide/img/efd_i12.svg | 1008 +++++++++++++++++++++++++
doc/guides/prog_guide/img/efd_i2.svg | 280 +++++++
doc/guides/prog_guide/img/efd_i3.svg | 634 ++++++++++++++++
doc/guides/prog_guide/img/efd_i4.svg | 203 ++++++
doc/guides/prog_guide/img/efd_i5.svg | 183 +++++
doc/guides/prog_guide/img/efd_i6.svg | 1254 ++++++++++++++++++++++++++++++++
doc/guides/prog_guide/img/efd_i7.svg | 790 ++++++++++++++++++++
doc/guides/prog_guide/img/efd_i8.svg | 182 +++++
doc/guides/prog_guide/img/efd_i9.svg | 390 ++++++++++
doc/guides/prog_guide/index.rst | 23 +
doc/guides/rel_notes/release_17_02.rst | 3 +
16 files changed, 6224 insertions(+)
create mode 100644 doc/guides/prog_guide/efd_lib.rst
create mode 100644 doc/guides/prog_guide/img/efd_i1.svg
create mode 100644 doc/guides/prog_guide/img/efd_i10.svg
create mode 100644 doc/guides/prog_guide/img/efd_i11.svg
create mode 100644 doc/guides/prog_guide/img/efd_i12.svg
create mode 100644 doc/guides/prog_guide/img/efd_i2.svg
create mode 100644 doc/guides/prog_guide/img/efd_i3.svg
create mode 100644 doc/guides/prog_guide/img/efd_i4.svg
create mode 100644 doc/guides/prog_guide/img/efd_i5.svg
create mode 100644 doc/guides/prog_guide/img/efd_i6.svg
create mode 100644 doc/guides/prog_guide/img/efd_i7.svg
create mode 100644 doc/guides/prog_guide/img/efd_i8.svg
create mode 100644 doc/guides/prog_guide/img/efd_i9.svg
diff --git a/MAINTAINERS b/MAINTAINERS
index b124f6e..66e9466 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -532,6 +532,7 @@ EFD
M: Byron Marohn <byron.marohn@intel.com>
M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
F: lib/librte_efd/
+F: doc/guides/prog_guide/efd_lib.rst
F: app/test/test_efd*
F: examples/flow_distributor/
diff --git a/doc/guides/prog_guide/efd_lib.rst b/doc/guides/prog_guide/efd_lib.rst
new file mode 100644
index 0000000..5b8e4e3
--- /dev/null
+++ b/doc/guides/prog_guide/efd_lib.rst
@@ -0,0 +1,440 @@
+.. BSD LICENSE
+ Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of Intel Corporation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+.. _Efd_Library:
+
+Elastic Flow Distributor Library
+================================
+
+Introduction
+------------
+
+In Data Centers today, clustering and scheduling of distributed workloads
+is a very common task. Many workloads require a deterministic
+partitioning of a flat key space among a cluster of machines. When a
+packet enters the cluster, the ingress node will direct the packet to
+its handling node. For example, data-centers with disaggregated storage
+use storage metadata tables to forward I/O requests to the correct back end
+storage cluster, stateful packet inspection will use match incoming
+flows to signatures in flow tables to send incoming packets to their
+intended deep packet inspection (DPI) devices, and so on.
+
+EFD is a distributor library that uses perfect hashing to determine a
+target/value for a given incoming flow key. It has the following
+advantages: first, because it uses perfect hashing it does not store the
+key itself and hence lookup performance is not dependent on the key
+size. Second, the target/value can be any arbitrary value hence the
+system designer and/or operator can better optimize service rates and
+inter-cluster network traffic locating. Third, since the storage
+requirement is much smaller than a hash-based flow table (i.e. better
+fit for CPU cache), EFD can scale to millions of flow keys. Finally,
+with the current optimized library implementation, performance is fully
+scalable with any number of CPU cores.
+
+Flow Based Distribution
+-----------------------
+
+Computation Based Schemes
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Flow distribution and/or load balancing can be simply done using a
+stateless computation, for instance using round-robin or a simple
+computation based on the flow key as an input. For example, a hash
+function can be used to direct a certain flow to a target based on
+the flow key (e.g. ``h(key) mod n``) where h(key) is the hash value of the
+flow key and n is the number of possible targets.
+
+.. _figure_efd1:
+
+.. figure:: img/efd_i1.*
+
+ Load Balancing Using Front End Node
+
+In this scheme (:numref:`figure_efd1`), the front end server/distributor/load balancer
+extracts the flow key from the input packet and applies a computation to determine where
+this flow should be directed. Intuitively, this scheme is very simple
+and requires no state to be kept at the front end node, and hence,
+storage requirements are minimum.
+
+.. _figure_efd2:
+
+.. figure:: img/efd_i2.*
+
+ Consistent Hashing
+
+A widely used flow distributor that belongs to the same category of
+computation-based schemes is ``consistent hashing``, shown in :numref:`figure_efd2`.
+Target destinations (shown in red) are hashed into the same space as the flow
+keys (shown in blue), and keys are mapped to the nearest target in a clockwise
+fashion. Dynamically adding and removing targets with consistent hashing
+requires only K/n keys to be remapped on average, where K is the number of
+keys, and n is the number of targets. In contrast, in a traditional hash-based
+scheme, a change in the number of targets causes nearly all keys to be
+remapped.
+
+Although computation-based schemes are simple and need very little
+storage requirement, they suffer from the drawback that the system
+designer/operator can’t fully control the target to assign a specific
+key, as this is dictated by the hash function.
+Deterministically co-locating of keys together (for example, to minimize
+inter-server traffic or to optimize for network traffic conditions,
+target load, etc.) is simply not possible.
+
+Flow-Table Based Schemes
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+When using a Flow-Table based scheme to handle flow distribution/load
+balancing, in contrast with computation-based schemes, the system designer
+has the flexibility of assigning a given flow to any given
+target. The flow table (e.g. DPDK RTE Hash Library) will simply store
+both the flow key and the target value.
+
+.. _figure_efd3:
+
+.. figure:: img/efd_i3.*
+
+ Table Based Flow Distribution
+
+As shown in :numref:`figure_efd3`, when doing a lookup, the flow-table
+is indexed with the hash of the flow key and the keys (more than one is possible,
+because of hash collision) stored in this index and corresponding values
+are retrieved. The retrieved key(s) is matched with the input flow key
+and if there is a match the value (target id) is returned.
+
+The drawback of using a hash table for flow distribution/load balancing
+is the storage requirement, since the flow table need to store keys,
+signatures and target values. This doesn't allow this scheme to scale to
+millions of flow keys. Large tables will usually not fit in
+the CPU cache, and hence, the lookup performance is degraded because of
+the latency to access the main memory.
+
+EFD Based Scheme
+~~~~~~~~~~~~~~~~
+
+EFD combines the advantages of both flow-table based and computation-based
+schemes. It doesn't require the large storage necessary for
+flow-table based schemes (because EFD doesn't store the key as explained
+below), and it supports any arbitrary value for any given key.
+
+.. _figure_efd4:
+
+.. figure:: img/efd_i4.*
+
+ Searching for Perfect Hash Function
+
+The basic idea of EFD is when a given key is to be inserted, a family of
+hash functions is searched until the correct hash function that maps the
+input key to the correct value is found, as shown in :numref:`figure_efd4`.
+However, rather than explicitly storing all keys and their associated values,
+EFD stores only indices of hash functions that map keys to values, and
+thereby consumes much less space than conventional flow-based tables.
+The lookup operation is very simple, similar to a computational-based
+scheme: given an input key the lookup operation is reduced to hashing
+that key with the correct hash function.
+
+.. _figure_efd5:
+
+.. figure:: img/efd_i5.*
+
+ Divide and Conquer for Millions of Keys
+
+Intuitively, finding a hash function that maps each of a large number
+(millions) of input keys to the correct output value is effectively
+impossible, as a result EFD, as shown in :numref:`figure_efd5`,
+breaks the problem into smaller pieces (divide and conquer).
+EFD divides the entire input key set into many small groups.
+Each group consists of approximately 20-28 keys (a configurable parameter
+for the library), then, for each small group, a brute force search to find
+a hash function that produces the correct outputs for each key in the group.
+
+It should be mentioned that, since the online lookup table for EFD
+doesn't store the key itself, the size of the EFD table is independent
+of the key size and hence EFD lookup performance which is almost
+constant irrespective of the length of the key which is a highly
+desirable feature especially for longer keys.
+
+In summary, EFD is a set separation data structure that supports millions of
+keys. It is used to distribute a given key to an intended target. By itself
+EFD is not a FIB data structure with an exact match the input flow key.
+
+.. _Efd_example:
+
+Example of EFD Library Usage
+----------------------------
+
+EFD can be used along the data path of many network functions and middleboxes.
+As previously mentioned, it can used as an index table for
+<key,value> pairs, meta-data for objects, a flow-level load balancer, etc.
+:numref:`figure_efd6` shows an example of using EFD as a flow-level load
+balancer, where flows are received at a front end server before being forwarded
+to the target back end server for processing. The system designer would
+deterministically co-locate flows together in order to minimize cross-server
+interaction.
+(For example, flows requesting certain webpage objects are co-located
+together, to minimize forwarding of common objects across servers).
+
+.. _figure_efd6:
+
+.. figure:: img/efd_i6.*
+
+ EFD as a Flow-Level Load Balancer
+
+As shown in :numref:`figure_efd6`, the front end server will have an EFD table that
+stores for each group what is the perfect hash index that satisfies the
+correct output. Because the table size is small and fits in cache (since
+keys are not stored), it sustains a large number of flows (N*X, where N
+is the maximum number of flows served by each back end server of the X
+possible targets).
+
+With an input flow key, the group id is computed (for example, using
+last few bits of CRC hash) and then the EFD table is indexed with the
+group id to retrieve the corresponding hash index to use. Once the index
+is retrieved the key is hashed using this hash function and the result
+will be the intended correct target where this flow is supposed to be
+processed.
+
+It should be noted that as a result of EFD not matching the exact key but
+rather distributing the flows to a target back end node based on the
+perfect hash index, a key that has not been inserted before
+will be distributed to a valid target. Hence, a local table which stores
+the flows served at each node is used and is
+exact matched with the input key to rule out new never seen before
+flows.
+
+.. _Efd_api:
+
+Library API Overview
+--------------------
+
+The EFD library API is created with a very similar semantics of a
+hash-index or a flow table. The application creates an EFD table for a
+given maximum number of flows, a function is called to insert a flow key
+with a specific target value, and another function is used to retrieve
+target values for a given individual flow key or a bulk of keys.
+
+EFD Table Create
+~~~~~~~~~~~~~~~~
+
+The function ``rte_efd_create()`` is used to create and return a pointer
+to an EFD table that is sized to hold up to num_flows key.
+The online version of the EFD table (the one that does
+not store the keys and is used for lookups) will be allocated and
+created in the last level cache (LLC) of the socket defined by the
+online_socket_bitmask, while the offline EFD table (the one that
+stores the keys and is used for key inserts and for computing the
+perfect hashing) is allocated and created in the LLC of the socket
+defined by offline_socket_bitmask. It should be noted, that for
+highest performance the socket id should match that where the thread is
+running, i.e. the online EFD lookup table should be created on the same
+socket as where the lookup thread is running.
+
+EFD Insert and Update
+~~~~~~~~~~~~~~~~~~~~~
+
+The EFD function to insert a key or update a key to a new value is
+``rte_efd_update()``. This function will update an existing key to
+a new value (target) if the key has already been inserted
+before, or will insert the <key,value> pair if this key has not been inserted
+before. It will return 0 upon success. It will return
+``EFD_UPDATE_WARN_GROUP_FULL (1)`` if the operation is insert, and the
+last available space in the key's group was just used. It will return
+``EFD_UPDATE_FAILED (2)`` when the insertion or update has failed (either it
+failed to find a suitable perfect hash or the group was full). The function
+will return ``EFD_UPDATE_NO_CHANGE (3)`` if there is no change to the EFD
+table (i.e, same value already exists).
+
+EFD Lookup
+~~~~~~~~~~
+
+To lookup a certain key in an EFD table, the function ``rte_efd_lookup()``
+is used to return the value associated with single key.
+As previously mentioned, if the key has been inserted, the correct value
+inserted is returned, if the key has not been inserted before,
+a ‘random’ value (based on hashing of the key) is returned.
+For better performance and to decrease the overhead of
+function calls per key, it is always recommended to use a bulk lookup
+function (simultaneous lookup of multiple keys) instead of a single key
+lookup function. ``rte_efd_lookup_bulk()`` is the bulk lookup function,
+that looks up num_keys simultaneously stored in the key_list and the
+corresponding return values will be returned in the value_list.
+
+EFD Delete
+~~~~~~~~~~
+
+To delete a certain key in an EFD table, the function
+``rte_efd_delete()`` can be used. The function returns zero upon success
+when the key has been found and deleted. Socket_id is the parameter to
+use to lookup the existing value, which is ideally the caller's socket id.
+The previous value associated with this key will be returned
+in the prev_value argument.
+
+.. _Efd_internals:
+
+Library Internals
+-----------------
+
+This section provides the brief high-level idea and an overview
+of the library internals to accompany the RFC. The intent of this
+section is to explain to readers the high-level implementation of
+insert, lookup and group rebalancing in the EFD library.
+
+Insert Function Internals
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As previously mentioned the EFD divides the whole set of keys into
+groups of a manageable size (e.g. 28 keys) and then searches for the
+perfect hash that satisfies the intended target value for each key. EFD
+stores two version of the <key,value> table:
+
+- Offline Version (in memory): Only used for the insertion/update
+ operation, which is less frequent than the lookup operation. In the
+ offline version the exact keys for each group is stored. When a new
+ key is added, the hash function is updated that will satisfy the
+ value for the new key together with the all old keys already inserted
+ in this group.
+
+- Online Version (in cache): Used for the frequent lookup operation. In
+ the online version, as previously mentioned, the keys are not stored
+ but rather only the hash index for each group.
+
+.. _figure_efd7:
+
+.. figure:: img/efd_i7.*
+
+ Group Assignment
+
+:numref:`figure_efd7` depicts the group assignment for 7 flow keys as an example.
+Given a flow key, a hash function (in our implementation CRC hash) is
+used to get the group id. As shown in the figure, the groups can be
+unbalanced. (We highlight group rebalancing further below).
+
+.. _figure_efd8:
+
+.. figure:: img/efd_i8.*
+
+ Perfect Hash Search - Assigned Keys & Target Value
+
+Focusing on one group that has four keys, :numref:`figure_efd8` depicts the search
+algorithm to find the perfect hash function. Assuming that the target
+value bit for the keys is as shown in the figure, then the online EFD
+table will store a 16 bit hash index and 16 bit lookup table per group
+per value bit.
+
+.. _figure_efd9:
+
+.. figure:: img/efd_i9.*
+
+ Perfect Hash Search - Satisfy Target Values
+
+For a given keyX, a hash function ``(h(keyX, seed1) + index * h(keyX, seed2))``
+is used to point to certain bit index in the 16bit lookup_table value,
+as shown in :numref:`figure_efd9`.
+The insert function will brute force search for all possible values for the
+hash index until a non conflicting lookup_table is found.
+
+.. _figure_efd10:
+
+.. figure:: img/efd_i10.*
+
+ Finding Hash Index for Conflict Free lookup_table
+
+For example, since both key3 and key7 have a target bit value of 1, it
+is okay if the hash function of both keys point to the same bit in the
+lookup table. A conflict will occur if a hash index is used that maps
+both Key4 and Key7 to the same index in the lookup_table,
+as shown in :numref:`figure_efd10`, since their target value bit are not the same.
+Once a hash index is found that produces a lookup_table with no
+contradictions, this index is stored for this group. This procedure is
+repeated for each bit of target value.
+
+Lookup Function Internals
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The design principle of EFD is that lookups are much more frequent than
+inserts, and hence, EFD's design optimizes for the lookups which are
+faster and much simpler than the slower insert procedure (inserts are
+slow, because of perfect hash search as previously discussed).
+
+.. _figure_efd11:
+
+.. figure:: img/efd_i11.*
+
+ EFD Lookup Operation
+
+:numref:`figure_efd11` depicts the lookup operation for EFD. Given an input key,
+the group id is computed (using CRC hash) and then the hash index for this
+group is retrieved from the EFD table. Using the retrieved hash index,
+the hash function ``h(key, seed1) + index *h(key, seed2)`` is used which will
+result in an index in the lookup_table, the bit corresponding to this
+index will be the target value bit. This procedure is repeated for each
+bit of the target value.
+
+Group Rebalancing Function Internals
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When discussing EFD inserts and lookups, the discussion is simplified by
+assuming that a group id is simply a result of hash function. However,
+since hashing in general is not perfect and will not always produce a
+uniform output, this simplified assumption will lead to unbalanced
+groups, i.e., some group will have more keys than other groups.
+Typically, and to minimize insert time with an increasing number of keys,
+it is preferable that all groups will have a balanced number of keys, so
+the brute force search for the perfect hash terminates with a valid hash
+index. In order to achieve this target, groups are rebalanced during
+runtime inserts, and keys are moved around from a busy group to a less
+crowded group as the more keys are inserted.
+
+.. _figure_efd12:
+
+.. figure:: img/efd_i12.*
+
+ Runtime Group Rebalancing
+
+:numref:`figure_efd12` depicts the high level idea of group rebalancing, given an
+input key the hash result is split into two parts a chunk id and 8-bit
+bin id. A chunk contains 64 different groups and 256 bins (i.e. for any
+given bin it can map to 4 distinct groups). When a key is inserted, the
+bin id is computed, for example in :numref:`figure_efd12` bin_id=2,
+and since each bin can be mapped to one of four different groups (2 bit storage),
+the four possible mappings are evaluated and the one that will result in a
+balanced key distribution across these four is selected the mapping result
+is stored in these two bits.
+
+
+.. _Efd_references:
+
+References
+-----------
+
+1- EFD is based on collaborative research work between Intel and
+Carnegie Mellon University (CMU), interested readers can refer to the paper
+“Scaling Up Clustered Network Appliances with ScaleBricks;” Dong Zhou et al.
+at SIGCOMM 2015 (`http://conferences.sigcomm.org/sigcomm/2015/pdf/papers/p241.pdf`)
+for more information.
diff --git a/doc/guides/prog_guide/img/efd_i1.svg b/doc/guides/prog_guide/img/efd_i1.svg
new file mode 100644
index 0000000..7f8fcb3
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i1.svg
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i1.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="3.25609in" height="3.375in"
+ viewBox="0 0 234.439 243" xml:space="preserve" color-interpolation-filters="sRGB" class="st10">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st5 {marker-end:url(#mrkr5-12);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:6}
+ .st6 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.70422535211268}
+ .st7 {stroke:#5b9bd5;stroke-dasharray:2.25,4.5;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.25}
+ .st8 {marker-end:url(#mrkr5-39);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st9 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st10 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-12" class="st6" v:arrowType="5" v:arrowSize="2" v:setback="2.485" refX="-2.485" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-1.42,-1.42) "/>
+ </marker>
+ <marker id="mrkr5-39" class="st9" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(77.718,-113.348)">
+ <title>Square</title>
+ <desc>LB</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="18" cy="225" width="36" height="36"/>
+ <g id="shadow2-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="207" width="36" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="207" width="36" height="36" class="st3"/>
+ <text x="13.18" y="228" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>LB</text> </g>
+ <g id="shape3-7" v:mID="3" v:groupContext="shape" transform="translate(37.0513,-131.348)">
+ <title>Sheet.3</title>
+ <path d="M0 243 L25.76 243" class="st5"/>
+ </g>
+ <g id="shape4-13" v:mID="4" v:groupContext="shape" transform="translate(167.718,-178.598)">
+ <title>Square.4</title>
+ <desc>Target 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="220.5" width="45" height="45"/>
+ <g id="shadow4-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="198" width="45" height="45" class="st2"/>
+ </g>
+ <rect x="0" y="198" width="45" height="45" class="st3"/>
+ <text x="5.74" y="223.5" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target 1</text> </g>
+ <g id="shape5-19" v:mID="5" v:groupContext="shape" transform="translate(167.718,-121.005)">
+ <title>Square.5</title>
+ <desc>Target 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="220.5" width="45" height="45"/>
+ <g id="shadow5-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="198" width="45" height="45" class="st2"/>
+ </g>
+ <rect x="0" y="198" width="45" height="45" class="st3"/>
+ <text x="5.74" y="223.5" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target 2</text> </g>
+ <g id="shape7-25" v:mID="7" v:groupContext="shape" transform="translate(167.718,-23.3478)">
+ <title>Square.7</title>
+ <desc>Target N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="220.5" width="45" height="45"/>
+ <g id="shadow7-26" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="198" width="45" height="45" class="st2"/>
+ </g>
+ <rect x="0" y="198" width="45" height="45" class="st3"/>
+ <text x="5.05" y="223.5" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target N</text> </g>
+ <g id="shape8-31" v:mID="8" v:groupContext="shape" transform="translate(433.218,132.402) rotate(90)">
+ <title>Sheet.8</title>
+ <path d="M0 243 L34.59 243" class="st7"/>
+ </g>
+ <g id="shape9-34" v:mID="9" v:groupContext="shape" transform="translate(-78.4279,-37.1059) rotate(-52.2532)">
+ <title>Sheet.9</title>
+ <path d="M0 243 L81.18 243" class="st8"/>
+ </g>
+ <g id="shape11-40" v:mID="11" v:groupContext="shape" transform="translate(60.3469,-125.414) rotate(-12.6875)">
+ <title>Sheet.11</title>
+ <path d="M0 243 L48.32 243" class="st8"/>
+ </g>
+ <g id="shape12-45" v:mID="12" v:groupContext="shape" transform="translate(319.172,-18.1081) rotate(57.7244)">
+ <title>Sheet.12</title>
+ <path d="M0 243 L94.09 243" class="st8"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i10.svg b/doc/guides/prog_guide/img/efd_i10.svg
new file mode 100644
index 0000000..d26ec61
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i10.svg
@@ -0,0 +1,384 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i11.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="9.76715in" height="2.82917in"
+ viewBox="0 0 703.234 203.701" xml:space="preserve" color-interpolation-filters="sRGB" class="st15">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st4 {fill:#000000;font-family:Arial;font-size:0.998566em}
+ .st5 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st6 {fill:#000000;font-family:Arial;font-size:0.918686em;font-style:italic}
+ .st7 {fill:#000000;font-family:Arial;font-size:0.918686em}
+ .st8 {fill:#7e8d96;font-family:Arial;font-size:0.998566em;font-weight:bold}
+ .st9 {fill:#00b050;font-family:Arial;font-size:0.998566em;font-weight:bold}
+ .st10 {fill:#ff0000;font-family:Arial;font-size:0.998566em;font-weight:bold}
+ .st11 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st12 {fill:#ffffff;font-family:Arial;font-size:1.49785em}
+ .st13 {stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st14 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st15 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(19.0195,-93.4328)">
+ <title>Sheet.3</title>
+ <path d="M0 182.93 C0 180.64 1.87 178.78 4.16 178.78 L109.18 178.78 C111.47 178.78 113.33 180.64 113.33 182.93 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.16 203.7 C1.87 203.7 0 201.84 0 199.55 L0 182.93 Z"
+ class="st1"/>
+ </g>
+ <g id="shape4-3" v:mID="4" v:groupContext="shape" transform="translate(19.0195,-93.4328)">
+ <title>Sheet.4</title>
+ <path d="M0 182.93 C0 180.64 1.87 178.78 4.16 178.78 L109.18 178.78 C111.47 178.78 113.33 180.64 113.33 182.93 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.16 203.7 C1.87 203.7 0 201.84 0 199.55 L0 182.93 Z"
+ class="st2"/>
+ </g>
+ <g id="shape5-5" v:mID="5" v:groupContext="shape" transform="translate(19.0195,-96.9057)">
+ <title>Sheet.5</title>
+ <desc>Key1: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.0575" cy="196.509" width="116.12" height="14.3829"/>
+ <path d="M116.11 189.32 L0 189.32 L0 203.7 L116.11 203.7 L116.11 189.32" class="st3"/>
+ <text x="15.59" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1: Value = 0</text> </g>
+ <g id="shape6-9" v:mID="6" v:groupContext="shape" transform="translate(19.0195,-68.6284)">
+ <title>Sheet.6</title>
+ <path d="M0 182.93 C0 180.64 1.87 178.78 4.16 178.78 L109.18 178.78 C111.47 178.78 113.33 180.64 113.33 182.93 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.16 203.7 C1.87 203.7 0 201.84 0 199.55 L0 182.93 Z"
+ class="st1"/>
+ </g>
+ <g id="shape7-11" v:mID="7" v:groupContext="shape" transform="translate(19.0195,-68.6284)">
+ <title>Sheet.7</title>
+ <path d="M0 182.93 C0 180.64 1.87 178.78 4.16 178.78 L109.18 178.78 C111.47 178.78 113.33 180.64 113.33 182.93 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.16 203.7 C1.87 203.7 0 201.84 0 199.55 L0 182.93 Z"
+ class="st2"/>
+ </g>
+ <g id="shape8-13" v:mID="8" v:groupContext="shape" transform="translate(19.0195,-72.0832)">
+ <title>Sheet.8</title>
+ <desc>Key3: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.0575" cy="196.509" width="116.12" height="14.3829"/>
+ <path d="M116.11 189.32 L0 189.32 L0 203.7 L116.11 203.7 L116.11 189.32" class="st3"/>
+ <text x="15.59" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3: Value = 1</text> </g>
+ <g id="shape9-17" v:mID="9" v:groupContext="shape" transform="translate(19.0195,-43.5843)">
+ <title>Sheet.9</title>
+ <path d="M0 182.84 C-0 180.53 1.88 178.66 4.19 178.66 L109.15 178.66 C111.46 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.53 C113.33 201.84 111.46 203.7 109.15 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.53 L0 182.84 Z"
+ class="st1"/>
+ </g>
+ <g id="shape10-19" v:mID="10" v:groupContext="shape" transform="translate(19.0195,-43.5843)">
+ <title>Sheet.10</title>
+ <path d="M0 182.84 C-0 180.53 1.88 178.66 4.19 178.66 L109.15 178.66 C111.46 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.53 C113.33 201.84 111.46 203.7 109.15 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.53 L0 182.84 Z"
+ class="st2"/>
+ </g>
+ <g id="shape11-21" v:mID="11" v:groupContext="shape" transform="translate(19.0195,-47.1109)">
+ <title>Sheet.11</title>
+ <desc>Key4: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.0575" cy="196.509" width="116.12" height="14.3829"/>
+ <path d="M116.11 189.32 L0 189.32 L0 203.7 L116.11 203.7 L116.11 189.32" class="st3"/>
+ <text x="15.59" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4: Value = 0</text> </g>
+ <g id="shape12-25" v:mID="12" v:groupContext="shape" transform="translate(19.0195,-19.0195)">
+ <title>Sheet.12</title>
+ <path d="M0 182.84 C-0 180.53 1.88 178.66 4.19 178.66 L109.15 178.66 C111.46 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.53 C113.33 201.84 111.46 203.7 109.15 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.53 L0 182.84 Z"
+ class="st1"/>
+ </g>
+ <g id="shape13-27" v:mID="13" v:groupContext="shape" transform="translate(19.0195,-19.0195)">
+ <title>Sheet.13</title>
+ <path d="M0 182.84 C-0 180.53 1.88 178.66 4.19 178.66 L109.15 178.66 C111.46 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.53 C113.33 201.84 111.46 203.7 109.15 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.53 L0 182.84 Z"
+ class="st2"/>
+ </g>
+ <g id="shape14-29" v:mID="14" v:groupContext="shape" transform="translate(19.0195,-22.5475)">
+ <title>Sheet.14</title>
+ <desc>Key7: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.0575" cy="196.509" width="116.12" height="14.3829"/>
+ <path d="M116.11 189.32 L0 189.32 L0 203.7 L116.11 203.7 L116.11 189.32" class="st3"/>
+ <text x="15.59" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7: Value = 1</text> </g>
+ <g id="shape15-33" v:mID="15" v:groupContext="shape" transform="translate(141.656,-45.5615)">
+ <title>Sheet.15</title>
+ <path d="M0 169.01 L22.75 169.01 L22.75 157.45 L45.5 180.57 L22.75 203.7 L22.75 192.14 L0 192.14 L0 169.01 Z"
+ class="st5"/>
+ </g>
+ <g id="shape16-35" v:mID="16" v:groupContext="shape" transform="translate(193.22,-56.0464)">
+ <title>Sheet.16</title>
+ <path d="M0 182.84 C0 180.53 1.88 178.66 4.19 178.66 L96.55 178.66 C98.87 178.66 100.73 180.53 100.73 182.84 L100.73
+ 199.54 C100.73 201.84 98.87 203.7 96.55 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.54 L0 182.84 Z"
+ class="st1"/>
+ </g>
+ <g id="shape17-37" v:mID="17" v:groupContext="shape" transform="translate(193.22,-56.0464)">
+ <title>Sheet.17</title>
+ <path d="M0 182.84 C0 180.53 1.88 178.66 4.19 178.66 L96.55 178.66 C98.87 178.66 100.73 180.53 100.73 182.84 L100.73
+ 199.54 C100.73 201.84 98.87 203.7 96.55 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.54 L0 182.84 Z"
+ class="st2"/>
+ </g>
+ <g id="shape18-39" v:mID="18" v:groupContext="shape" transform="translate(228.157,-66.9545)">
+ <title>Sheet.18</title>
+ <desc>F</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.63538" cy="197.084" width="11.28" height="13.2327"/>
+ <path d="M11.27 190.47 L0 190.47 L0 203.7 L11.27 203.7 L11.27 190.47" class="st3"/>
+ <text x="2.27" y="200.39" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>F</text> </g>
+ <g id="shape19-43" v:mID="19" v:groupContext="shape" transform="translate(234.88,-66.9545)">
+ <title>Sheet.19</title>
+ <desc>(key,</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.261" cy="197.084" width="34.53" height="13.2327"/>
+ <path d="M34.52 190.47 L0 190.47 L0 203.7 L34.52 203.7 L34.52 190.47" class="st3"/>
+ <text x="5.32" y="200.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(key, </text> </g>
+ <g id="shape20-47" v:mID="20" v:groupContext="shape" transform="translate(198.215,-53.7734)">
+ <title>Sheet.20</title>
+ <desc>hash_index =</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="41.4128" cy="197.084" width="82.83" height="13.2327"/>
+ <path d="M82.83 190.47 L0 190.47 L0 203.7 L82.83 203.7 L82.83 190.47" class="st3"/>
+ <text x="8.47" y="200.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash_index = </text> </g>
+ <g id="shape21-51" v:mID="21" v:groupContext="shape" transform="translate(274.858,-53.7734)">
+ <title>Sheet.21</title>
+ <desc>i)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.28241" cy="197.084" width="10.57" height="13.2327"/>
+ <path d="M10.56 190.47 L0 190.47 L0 203.7 L10.56 203.7 L10.56 190.47" class="st3"/>
+ <text x="2.22" y="200.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>i)</text> </g>
+ <g id="shape22-55" v:mID="22" v:groupContext="shape" transform="translate(351.453,-93.7923)">
+ <title>Sheet.22</title>
+ <path d="M0 182.84 C0 180.53 1.88 178.66 4.19 178.66 L109.16 178.66 C111.47 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.54 C113.33 201.84 111.47 203.7 109.16 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.54 L0 182.84 Z"
+ class="st1"/>
+ </g>
+ <g id="shape23-57" v:mID="23" v:groupContext="shape" transform="translate(351.453,-93.7923)">
+ <title>Sheet.23</title>
+ <path d="M0 182.84 C0 180.53 1.88 178.66 4.19 178.66 L109.16 178.66 C111.47 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.54 C113.33 201.84 111.47 203.7 109.16 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.54 L0 182.84 Z"
+ class="st2"/>
+ </g>
+ <g id="shape24-59" v:mID="24" v:groupContext="shape" transform="translate(355.798,-97.3147)">
+ <title>Sheet.24</title>
+ <desc>Key1: Position 4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="51.7083" cy="196.509" width="103.42" height="14.3829"/>
+ <path d="M103.42 189.32 L0 189.32 L0 203.7 L103.42 203.7 L103.42 189.32" class="st3"/>
+ <text x="8.41" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1: Position 4</text> </g>
+ <g id="shape25-63" v:mID="25" v:groupContext="shape" transform="translate(351.453,-68.9879)">
+ <title>Sheet.25</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape26-65" v:mID="26" v:groupContext="shape" transform="translate(351.453,-68.9879)">
+ <title>Sheet.26</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape27-67" v:mID="27" v:groupContext="shape" transform="translate(355.798,-72.4921)">
+ <title>Sheet.27</title>
+ <desc>Key3: Position 6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="51.7083" cy="196.509" width="103.42" height="14.3829"/>
+ <path d="M103.42 189.32 L0 189.32 L0 203.7 L103.42 203.7 L103.42 189.32" class="st3"/>
+ <text x="8.41" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3: Position 6</text> </g>
+ <g id="shape28-71" v:mID="28" v:groupContext="shape" transform="translate(351.453,-44.0636)">
+ <title>Sheet.28</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape29-73" v:mID="29" v:groupContext="shape" transform="translate(351.453,-44.0636)">
+ <title>Sheet.29</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape30-75" v:mID="30" v:groupContext="shape" transform="translate(351.215,-47.5198)">
+ <title>Sheet.30</title>
+ <desc>Key4: Position 14</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="55.5403" cy="196.509" width="111.09" height="14.3829"/>
+ <path d="M111.08 189.32 L0 189.32 L0 203.7 L111.08 203.7 L111.08 189.32" class="st3"/>
+ <text x="8.91" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4: Position 14</text> </g>
+ <g id="shape31-79" v:mID="31" v:groupContext="shape" transform="translate(351.453,-19.4988)">
+ <title>Sheet.31</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape32-81" v:mID="32" v:groupContext="shape" transform="translate(351.453,-19.4988)">
+ <title>Sheet.32</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape33-83" v:mID="33" v:groupContext="shape" transform="translate(351.215,-22.9565)">
+ <title>Sheet.33</title>
+ <desc>Key7: Position 14</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="55.5403" cy="196.509" width="111.09" height="14.3829"/>
+ <path d="M111.08 189.32 L0 189.32 L0 203.7 L111.08 203.7 L111.08 189.32" class="st3"/>
+ <text x="8.91" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7: Position 14</text> </g>
+ <g id="shape34-87" v:mID="34" v:groupContext="shape" transform="translate(299.89,-46.0408)">
+ <title>Sheet.34</title>
+ <path d="M0 169.01 L22.75 169.01 L22.75 157.45 L45.5 180.57 L22.75 203.7 L22.75 192.14 L0 192.14 L0 169.01 Z"
+ class="st5"/>
+ </g>
+ <g id="shape35-89" v:mID="35" v:groupContext="shape" transform="translate(528.896,-117.518)">
+ <title>Sheet.35</title>
+ <path d="M0 182.94 C0 180.66 1.89 178.78 4.17 178.78 L137.64 178.78 C139.92 178.78 141.79 180.66 141.79 182.94 L141.79
+ 199.57 C141.79 201.84 139.92 203.7 137.64 203.7 L4.17 203.7 C1.89 203.7 0 201.84 0 199.57 L0 182.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape36-91" v:mID="36" v:groupContext="shape" transform="translate(528.896,-117.518)">
+ <title>Sheet.36</title>
+ <path d="M0 182.94 C0 180.66 1.89 178.78 4.17 178.78 L137.64 178.78 C139.92 178.78 141.79 180.66 141.79 182.94 L141.79
+ 199.57 C141.79 201.84 139.92 203.7 137.64 203.7 L4.17 203.7 C1.89 203.7 0 201.84 0 199.57 L0 182.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape37-93" v:mID="37" v:groupContext="shape" transform="translate(530.056,-121.017)">
+ <title>Sheet.37</title>
+ <desc>0000</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="19.1585" cy="196.509" width="38.32" height="14.3829"/>
+ <path d="M38.32 189.32 L0 189.32 L0 203.7 L38.32 203.7 L38.32 189.32" class="st3"/>
+ <text x="5.83" y="200.1" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0000 </text> </g>
+ <g id="shape38-97" v:mID="38" v:groupContext="shape" transform="translate(567.215,-121.017)">
+ <title>Sheet.38</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.7483" cy="196.509" width="11.5" height="14.3829"/>
+ <path d="M11.5 189.32 L0 189.32 L0 203.7 L11.5 203.7 L11.5 189.32" class="st3"/>
+ <text x="2.42" y="200.1" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape39-101" v:mID="39" v:groupContext="shape" transform="translate(576.215,-121.017)">
+ <title>Sheet.39</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.7483" cy="196.509" width="11.5" height="14.3829"/>
+ <path d="M11.5 189.32 L0 189.32 L0 203.7 L11.5 203.7 L11.5 189.32" class="st3"/>
+ <text x="2.42" y="200.1" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape40-105" v:mID="40" v:groupContext="shape" transform="translate(584.486,-121.017)">
+ <title>Sheet.40</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.7483" cy="196.509" width="11.5" height="14.3829"/>
+ <path d="M11.5 189.32 L0 189.32 L0 203.7 L11.5 203.7 L11.5 189.32" class="st3"/>
+ <text x="2.42" y="200.1" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape41-109" v:mID="41" v:groupContext="shape" transform="translate(588.646,-121.017)">
+ <title>Sheet.41</title>
+ <desc>0 0000 00</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="32.5687" cy="196.509" width="65.14" height="14.3829"/>
+ <path d="M65.14 189.32 L0 189.32 L0 203.7 L65.14 203.7 L65.14 189.32" class="st3"/>
+ <text x="5.91" y="200.1" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0 0000 00</text> </g>
+ <g id="shape42-113" v:mID="42" v:groupContext="shape" transform="translate(644.965,-121.017)">
+ <title>Sheet.42</title>
+ <desc>?</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="6.12511" cy="196.509" width="12.26" height="14.3829"/>
+ <path d="M12.25 189.32 L0 189.32 L0 203.7 L12.25 203.7 L12.25 189.32" class="st3"/>
+ <text x="2.47" y="200.1" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>?</text> </g>
+ <g id="shape43-117" v:mID="43" v:groupContext="shape" transform="translate(654.718,-121.017)">
+ <title>Sheet.43</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.7483" cy="196.509" width="11.5" height="14.3829"/>
+ <path d="M11.5 189.32 L0 189.32 L0 203.7 L11.5 203.7 L11.5 189.32" class="st3"/>
+ <text x="2.42" y="200.1" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape44-121" v:mID="44" v:groupContext="shape" transform="translate(464.786,-105.296)">
+ <title>Sheet.44</title>
+ <path d="M0 203.7 L108.29 203.7 C108.86 203.7 109.31 203.22 109.31 202.68 L109.31 189.5 L107.27 189.5 L107.27 202.68
+ L108.29 201.66 L0 201.66 L0 203.7 ZM111.35 190.52 L108.29 184.41 L105.23 190.55 L111.35 190.52 Z"
+ class="st11"/>
+ </g>
+ <g id="shape45-123" v:mID="45" v:groupContext="shape" transform="translate(464.786,-80.4315)">
+ <title>Sheet.45</title>
+ <path d="M0 203.7 L123.63 203.7 C124.2 203.7 124.65 203.25 124.65 202.68 L124.65 164.28 L122.61 164.28 L122.61 202.68
+ L123.63 201.66 L0 201.66 L0 203.7 ZM126.69 165.3 L123.6 159.18 L120.57 165.33 L126.69 165.3 Z"
+ class="st11"/>
+ </g>
+ <g id="shape46-125" v:mID="46" v:groupContext="shape" transform="translate(464.786,-55.4772)">
+ <title>Sheet.46</title>
+ <path d="M0 203.7 L186.48 203.7 C186.75 203.7 186.99 203.61 187.2 203.4 C187.38 203.22 187.5 202.95 187.5 202.68 L187.41
+ 139.32 L185.37 139.32 L185.46 202.68 L186.48 201.66 L0 201.66 L0 203.7 ZM189.51 140.07 L185.94 134.23 L183.41
+ 140.61 L189.51 140.07 Z" class="st11"/>
+ </g>
+ <g id="shape47-127" v:mID="47" v:groupContext="shape" transform="translate(464.786,-30.9125)">
+ <title>Sheet.47</title>
+ <path d="M0 203.7 L186.48 203.7 C186.75 203.7 186.99 203.61 187.2 203.4 C187.38 203.22 187.5 202.95 187.5 202.68 L187.41
+ 114.76 L185.37 114.76 L185.46 202.68 L186.48 201.66 L0 201.66 L0 203.7 ZM189.51 115.51 L185.94 109.67 L183.41
+ 116.05 L189.51 115.51 Z" class="st11"/>
+ </g>
+ <g id="shape48-129" v:mID="48" v:groupContext="shape" transform="translate(442.996,-151.106)">
+ <title>Sheet.48</title>
+ <path d="M0 179.56 C0 176.89 2.19 174.7 4.86 174.7 L70.8 174.7 C73.47 174.7 75.64 176.89 75.64 179.56 L75.64 198.88 C75.64
+ 201.54 73.47 203.7 70.8 203.7 L4.86 203.7 C2.19 203.7 0 201.54 0 198.88 L0 179.56 Z" class="st5"/>
+ </g>
+ <g id="shape49-131" v:mID="49" v:groupContext="shape" transform="translate(443.529,-155.018)">
+ <title>Sheet.49</title>
+ <desc>Values</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="37.8175" cy="192.914" width="75.64" height="21.5726"/>
+ <path d="M75.64 182.13 L0 182.13 L0 203.7 L75.64 203.7 L75.64 182.13" class="st3"/>
+ <text x="10.34" y="198.31" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Values</text> </g>
+ <g id="shape50-135" v:mID="50" v:groupContext="shape" transform="translate(102.458,-122.192)">
+ <title>Sheet.50</title>
+ <path d="M0 203.7 C-0 199.21 0.62 195.55 1.37 195.55 L11.67 195.55 C12.42 195.55 13.03 191.9 13.03 187.4 C13.03 191.9
+ 13.64 195.55 14.39 195.55 L24.69 195.55 C25.44 195.55 26.05 199.21 26.05 203.7" class="st13"/>
+ </g>
+ <g id="shape51-138" v:mID="51" v:groupContext="shape" transform="translate(115.454,-137.5)">
+ <title>Sheet.51</title>
+ <path d="M0.2 203.7 L322.66 174.12 L322.48 172.1 L0 201.68 L0.2 203.7 L0.2 203.7 ZM321.84 176.24 L327.66 172.64 L321.28
+ 170.16 L321.84 176.24 L321.84 176.24 Z" class="st14"/>
+ </g>
+ <g id="shape52-140" v:mID="52" v:groupContext="shape" transform="translate(518.211,-142.473)">
+ <title>Sheet.52</title>
+ <path d="M0.99 176.74 L44.78 200.38 L43.82 202.17 L0 178.51 L0.99 176.74 L0.99 176.74 ZM44.87 198.1 L48.8 203.7 L41.96
+ 203.46 L44.87 198.1 L44.87 198.1 Z" class="st11"/>
+ </g>
+ <g id="shape53-142" v:mID="53" v:groupContext="shape" transform="translate(518.331,-141.963)">
+ <title>Sheet.53</title>
+ <path d="M0.75 176.17 L60.09 200.32 L59.34 202.2 L0 178.06 L0.75 176.17 L0.75 176.17 ZM59.91 198.04 L64.44 203.19 L57.6
+ 203.7 L59.91 198.04 L59.91 198.04 Z" class="st11"/>
+ </g>
+ <g id="shape54-144" v:mID="54" v:groupContext="shape" transform="translate(576.558,-153.706)">
+ <title>Sheet.54</title>
+ <path d="M0 177.04 C0 174.1 2.4 171.71 5.34 171.71 L101.51 171.71 C104.48 171.71 106.85 174.1 106.85 177.04 L106.85 198.37
+ C106.85 201.33 104.48 203.7 101.51 203.7 L5.34 203.7 C2.4 203.7 0 201.33 0 198.37 L0 177.04 Z" class="st1"/>
+ </g>
+ <g id="shape55-146" v:mID="55" v:groupContext="shape" transform="translate(577.365,-151.611)">
+ <title>Sheet.55</title>
+ <path d="M0 177.04 C0 174.1 2.4 171.71 5.34 171.71 L101.51 171.71 C104.48 171.71 106.85 174.1 106.85 177.04 L106.85 198.37
+ C106.85 201.33 104.48 203.7 101.51 203.7 L5.34 203.7 C2.4 203.7 0 201.33 0 198.37 L0 177.04 Z" class="st2"/>
+ </g>
+ <g id="shape56-148" v:mID="56" v:groupContext="shape" transform="translate(593.952,-167.894)">
+ <title>Sheet.56</title>
+ <desc>Lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.2942" cy="196.509" width="86.59" height="14.3829"/>
+ <path d="M86.59 189.32 L0 189.32 L0 203.7 L86.59 203.7 L86.59 189.32" class="st3"/>
+ <text x="7.31" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Lookup_table</text> </g>
+ <g id="shape57-152" v:mID="57" v:groupContext="shape" transform="translate(608.239,-153.515)">
+ <title>Sheet.57</title>
+ <desc>(16 bits)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="26.8054" cy="196.509" width="53.62" height="14.3829"/>
+ <path d="M53.61 189.32 L0 189.32 L0 203.7 L53.61 203.7 L53.61 189.32" class="st3"/>
+ <text x="5.16" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(16 bits)</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i11.svg b/doc/guides/prog_guide/img/efd_i11.svg
new file mode 100644
index 0000000..f2cc656
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i11.svg
@@ -0,0 +1,319 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i12.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="10.2783in" height="4.28958in"
+ viewBox="0 0 740.039 308.85" xml:space="preserve" color-interpolation-filters="sRGB" class="st21">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st4 {fill:#000000;font-family:Arial;font-size:0.998566em}
+ .st5 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st6 {stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st7 {stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st8 {fill:#000000;font-family:Arial;font-size:0.918686em;font-weight:bold}
+ .st9 {fill:#00b050;font-size:1em}
+ .st10 {fill:#c00000;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st11 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.00749637}
+ .st12 {fill:#ffffff;font-family:Arial;font-size:1.16833em}
+ .st13 {fill:#2e75b5;stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st14 {fill:#ffffff;font-family:Arial;font-size:1.16666em}
+ .st15 {font-size:1em}
+ .st16 {fill:none;stroke:none;stroke-width:0.25}
+ .st17 {fill:#000000;font-family:Calibri;font-size:1.00001em}
+ .st18 {marker-end:url(#mrkr5-121);stroke:#5b9bd5;stroke-dasharray:1.5,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st19 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st20 {fill:#000000;font-family:Calibri;font-size:1.16666em}
+ .st21 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-121" class="st19" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape5-1" v:mID="5" v:groupContext="shape" transform="translate(36.0674,-256.878)">
+ <title>Sheet.5</title>
+ <path d="M0 291.88 C0 290 1.52 288.48 3.41 288.48 L68.51 288.48 C70.4 288.48 71.91 290 71.91 291.88 L71.91 305.46 C71.91
+ 307.33 70.4 308.85 68.51 308.85 L3.41 308.85 C1.52 308.85 0 307.33 0 305.46 L0 291.88 Z" class="st1"/>
+ </g>
+ <g id="shape6-3" v:mID="6" v:groupContext="shape" transform="translate(36.0674,-256.878)">
+ <title>Sheet.6</title>
+ <path d="M0 291.88 C0 290 1.52 288.48 3.41 288.48 L68.51 288.48 C70.4 288.48 71.91 290 71.91 291.88 L71.91 305.46 C71.91
+ 307.33 70.4 308.85 68.51 308.85 L3.41 308.85 C1.52 308.85 0 307.33 0 305.46 L0 291.88 Z" class="st2"/>
+ </g>
+ <g id="shape7-5" v:mID="7" v:groupContext="shape" transform="translate(61.6502,-258.089)">
+ <title>Sheet.7</title>
+ <desc>Key</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="13.7891" cy="301.658" width="27.58" height="14.3829"/>
+ <path d="M27.58 294.47 L0 294.47 L0 308.85 L27.58 308.85 L27.58 294.47" class="st3"/>
+ <text x="3.46" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key</text> </g>
+ <g id="shape8-9" v:mID="8" v:groupContext="shape" transform="translate(51.9748,-236.328)">
+ <title>Sheet.8</title>
+ <path d="M0 298.54 L9.81 298.54 L9.81 288.24 L29.44 288.24 L29.44 298.54 L39.26 298.54 L19.63 308.85 L0 298.54 Z"
+ class="st5"/>
+ </g>
+ <g id="shape9-11" v:mID="9" v:groupContext="shape" transform="translate(36.0674,-215.298)">
+ <title>Sheet.9</title>
+ <path d="M0 291.77 C0 289.89 1.54 288.36 3.42 288.36 L68.49 288.36 C70.38 288.36 71.91 289.89 71.91 291.77 L71.91 305.43
+ C71.91 307.32 70.38 308.85 68.49 308.85 L3.42 308.85 C1.54 308.85 0 307.32 0 305.43 L0 291.77 Z"
+ class="st1"/>
+ </g>
+ <g id="shape10-13" v:mID="10" v:groupContext="shape" transform="translate(36.0674,-215.298)">
+ <title>Sheet.10</title>
+ <path d="M0 291.77 C0 289.89 1.54 288.36 3.42 288.36 L68.49 288.36 C70.38 288.36 71.91 289.89 71.91 291.77 L71.91 305.43
+ C71.91 307.32 70.38 308.85 68.49 308.85 L3.42 308.85 C1.54 308.85 0 307.32 0 305.43 L0 291.77 Z"
+ class="st2"/>
+ </g>
+ <g id="shape11-15" v:mID="11" v:groupContext="shape" transform="translate(58.8889,-216.57)">
+ <title>Sheet.11</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.8573" cy="301.658" width="33.72" height="14.3829"/>
+ <path d="M33.71 294.47 L0 294.47 L0 308.85 L33.71 308.85 L33.71 294.47" class="st3"/>
+ <text x="3.86" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape12-19" v:mID="12" v:groupContext="shape" transform="translate(27.3033,-174.437)">
+ <title>Sheet.12</title>
+ <path d="M0 292.58 C0 290.78 1.46 289.32 3.26 289.32 L87.15 289.32 C88.95 289.32 90.4 290.78 90.4 292.58 L90.4 305.6
+ C90.4 307.4 88.95 308.85 87.15 308.85 L3.26 308.85 C1.46 308.85 0 307.4 0 305.6 L0 292.58 Z" class="st1"/>
+ </g>
+ <g id="shape13-21" v:mID="13" v:groupContext="shape" transform="translate(27.3033,-174.437)">
+ <title>Sheet.13</title>
+ <path d="M0 292.58 C0 290.78 1.46 289.32 3.26 289.32 L87.15 289.32 C88.95 289.32 90.4 290.78 90.4 292.58 L90.4 305.6
+ C90.4 307.4 88.95 308.85 87.15 308.85 L3.26 308.85 C1.46 308.85 0 307.4 0 305.6 L0 292.58 Z" class="st2"/>
+ </g>
+ <g id="shape14-23" v:mID="14" v:groupContext="shape" transform="translate(36.0515,-175.256)">
+ <title>Sheet.14</title>
+ <desc>0x0102ABCD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.6644" cy="301.658" width="87.33" height="14.3829"/>
+ <path d="M87.33 294.47 L0 294.47 L0 308.85 L87.33 308.85 L87.33 294.47" class="st3"/>
+ <text x="7.36" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102ABCD</text> </g>
+ <g id="shape15-27" v:mID="15" v:groupContext="shape" transform="translate(51.9748,-194.029)">
+ <title>Sheet.15</title>
+ <path d="M0 298.48 L9.81 298.48 L9.81 288.12 L29.44 288.12 L29.44 298.48 L39.26 298.48 L19.63 308.85 L0 298.48 Z"
+ class="st5"/>
+ </g>
+ <g id="shape16-29" v:mID="16" v:groupContext="shape" transform="translate(48.9133,-159.818)">
+ <title>Sheet.16</title>
+ <path d="M26.41 296.87 C26.41 300.18 25.97 302.86 25.41 302.86 L14.21 302.86 C13.66 302.86 13.21 305.55 13.21 308.85
+ C13.21 305.55 12.76 302.86 12.21 302.86 L1.01 302.86 C0.45 302.86 0 300.18 0 296.87" class="st6"/>
+ </g>
+ <g id="shape17-32" v:mID="17" v:groupContext="shape" transform="translate(19.0195,-19.0195)">
+ <title>Sheet.17</title>
+ <path d="M0 196.93 L0 308.85 L145.15 308.85 L145.15 196.93 L0 196.93 L0 196.93 Z" class="st1"/>
+ </g>
+ <g id="shape18-34" v:mID="18" v:groupContext="shape" transform="translate(19.0195,-19.0195)">
+ <title>Sheet.18</title>
+ <path d="M0 196.93 L145.15 196.93 L145.15 308.85 L0 308.85 L0 196.93" class="st7"/>
+ </g>
+ <g id="shape19-37" v:mID="19" v:groupContext="shape" transform="translate(28.2638,-70.6655)">
+ <title>Sheet.19</title>
+ <path d="M0 280.69 C0 277.58 2.53 275.06 5.64 275.06 L124.14 275.06 C127.26 275.06 129.78 277.58 129.78 280.69 L129.78
+ 303.22 C129.78 306.33 127.26 308.85 124.14 308.85 L5.64 308.85 C2.53 308.85 0 306.33 0 303.22 L0 280.69
+ Z" class="st1"/>
+ </g>
+ <g id="shape20-39" v:mID="20" v:groupContext="shape" transform="translate(28.2638,-70.6655)">
+ <title>Sheet.20</title>
+ <path d="M0 280.69 C0 277.58 2.53 275.06 5.64 275.06 L124.14 275.06 C127.26 275.06 129.78 277.58 129.78 280.69 L129.78
+ 303.22 C129.78 306.33 127.26 308.85 124.14 308.85 L5.64 308.85 C2.53 308.85 0 306.33 0 303.22 L0 280.69
+ Z" class="st2"/>
+ </g>
+ <g id="shape21-41" v:mID="21" v:groupContext="shape" transform="translate(57.4514,-85.7513)">
+ <title>Sheet.21</title>
+ <desc>hash_index =</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="45.0133" cy="301.658" width="90.03" height="14.3829"/>
+ <path d="M90.03 294.47 L0 294.47 L0 308.85 L90.03 308.85 L90.03 294.47" class="st3"/>
+ <text x="9.2" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash_index = </text> </g>
+ <g id="shape22-45" v:mID="22" v:groupContext="shape" transform="translate(76.3001,-71.3719)">
+ <title>Sheet.22</title>
+ <desc>38123</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.0762" cy="301.658" width="42.16" height="14.3829"/>
+ <path d="M42.15 294.47 L0 294.47 L0 308.85 L42.15 308.85 L42.15 294.47" class="st3"/>
+ <text x="4.42" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>38123</text> </g>
+ <g id="shape23-49" v:mID="23" v:groupContext="shape" transform="translate(28.2638,-27.048)">
+ <title>Sheet.23</title>
+ <path d="M0 280.69 C0 277.59 2.54 275.06 5.64 275.06 L124.14 275.06 C127.26 275.06 129.78 277.59 129.78 280.69 L129.78
+ 303.22 C129.78 306.33 127.26 308.85 124.14 308.85 L5.64 308.85 C2.54 308.85 0 306.33 0 303.22 L0 280.69
+ Z" class="st1"/>
+ </g>
+ <g id="shape24-51" v:mID="24" v:groupContext="shape" transform="translate(28.2638,-27.048)">
+ <title>Sheet.24</title>
+ <path d="M0 280.69 C0 277.59 2.54 275.06 5.64 275.06 L124.14 275.06 C127.26 275.06 129.78 277.59 129.78 280.69 L129.78
+ 303.22 C129.78 306.33 127.26 308.85 124.14 308.85 L5.64 308.85 C2.54 308.85 0 306.33 0 303.22 L0 280.69
+ Z" class="st2"/>
+ </g>
+ <g id="shape25-53" v:mID="25" v:groupContext="shape" transform="translate(54.0924,-41.564)">
+ <title>Sheet.25</title>
+ <desc>lookup_table =</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="46.931" cy="301.658" width="93.87" height="14.3829"/>
+ <path d="M93.86 294.47 L0 294.47 L0 308.85 L93.86 308.85 L93.86 294.47" class="st3"/>
+ <text x="7.79" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table =</text> </g>
+ <g id="shape26-57" v:mID="26" v:groupContext="shape" transform="translate(28.0195,-28.5506)">
+ <title>Sheet.26</title>
+ <desc>0110 1100 0101 1101</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="64.89" cy="302.233" width="129.79" height="13.2327"/>
+ <path d="M129.78 295.62 L0 295.62 L0 308.85 L129.78 308.85 L129.78 295.62" class="st3"/>
+ <text x="11.25" y="305.54" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0110 11<tspan
+ class="st9">0</tspan>0 0101 1101</text> </g>
+ <g id="shape27-62" v:mID="27" v:groupContext="shape" transform="translate(26.2461,-113.863)">
+ <title>Sheet.27</title>
+ <desc>Group ID: 0x0102</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="48.6286" cy="302.881" width="97.26" height="11.9384"/>
+ <path d="M97.26 296.91 L0 296.91 L0 308.85 L97.26 308.85 L97.26 296.91" class="st3"/>
+ <text x="7.73" y="305.86" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group ID: 0x0102</text> </g>
+ <g id="shape28-66" v:mID="28" v:groupContext="shape" transform="translate(42.3703,-135.313)">
+ <title>Sheet.28</title>
+ <path d="M0 298.48 L9.84 298.48 L9.84 288.12 L29.53 288.12 L29.53 298.48 L39.38 298.48 L19.69 308.85 L0 298.48 Z"
+ class="st5"/>
+ </g>
+ <g id="shape29-68" v:mID="29" v:groupContext="shape" transform="translate(117.645,-244.476)">
+ <title>Sheet.29</title>
+ <path d="M0 274.07 L22.75 274.07 L22.75 262.48 L45.5 285.66 L22.75 308.85 L22.75 297.26 L0 297.26 L0 274.07 Z"
+ class="st5"/>
+ </g>
+ <g id="shape30-70" v:mID="30" v:groupContext="shape" transform="translate(169.209,-251.966)">
+ <title>Sheet.30</title>
+ <path d="M0 283.69 C0 280.91 2.27 278.65 5.04 278.65 L111.77 278.65 C114.56 278.65 116.81 280.91 116.81 283.69 L116.81
+ 303.82 C116.81 306.6 114.56 308.85 111.77 308.85 L5.04 308.85 C2.27 308.85 0 306.6 0 303.82 L0 283.69 Z"
+ class="st1"/>
+ </g>
+ <g id="shape31-72" v:mID="31" v:groupContext="shape" transform="translate(169.209,-251.966)">
+ <title>Sheet.31</title>
+ <path d="M0 283.69 C0 280.91 2.27 278.65 5.04 278.65 L111.77 278.65 C114.56 278.65 116.81 280.91 116.81 283.69 L116.81
+ 303.82 C116.81 306.6 114.56 308.85 111.77 308.85 L5.04 308.85 C2.27 308.85 0 306.6 0 303.82 L0 283.69 Z"
+ class="st2"/>
+ </g>
+ <g id="shape35-74" v:mID="35" v:groupContext="shape" transform="translate(291.966,-244.476)">
+ <title>Sheet.35</title>
+ <path d="M0 274.07 L22.69 274.07 L22.69 262.48 L45.38 285.66 L22.69 308.85 L22.69 297.26 L0 297.26 L0 274.07 Z"
+ class="st5"/>
+ </g>
+ <g id="shape36-76" v:mID="36" v:groupContext="shape" transform="translate(343.17,-254.482)">
+ <title>Sheet.36</title>
+ <path d="M0 288.09 C0 285.8 1.88 283.93 4.17 283.93 L109.18 283.93 C111.47 283.93 113.33 285.8 113.33 288.09 L113.33
+ 304.7 C113.33 306.99 111.47 308.85 109.18 308.85 L4.17 308.85 C1.88 308.85 0 306.99 0 304.7 L0 288.09 Z"
+ class="st1"/>
+ </g>
+ <g id="shape37-78" v:mID="37" v:groupContext="shape" transform="translate(343.17,-254.482)">
+ <title>Sheet.37</title>
+ <path d="M0 288.09 C0 285.8 1.88 283.93 4.17 283.93 L109.18 283.93 C111.47 283.93 113.33 285.8 113.33 288.09 L113.33
+ 304.7 C113.33 306.99 111.47 308.85 109.18 308.85 L4.17 308.85 C1.88 308.85 0 306.99 0 304.7 L0 288.09 Z"
+ class="st2"/>
+ </g>
+ <g id="shape38-80" v:mID="38" v:groupContext="shape" transform="translate(368.337,-257.958)">
+ <title>Sheet.38</title>
+ <desc>Position = 6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="38.1131" cy="301.658" width="76.23" height="14.3829"/>
+ <path d="M76.23 294.47 L0 294.47 L0 308.85 L76.23 308.85 L76.23 294.47" class="st3"/>
+ <text x="6.64" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Position = 6</text> </g>
+ <g id="shape39-84" v:mID="39" v:groupContext="shape" transform="translate(158.044,-86.5202)">
+ <title>Sheet.39</title>
+ <path d="M0 308.85 L69.59 308.85 C70.16 308.85 70.62 308.39 70.62 307.83 L70.62 148.5 L68.57 148.5 L68.57 307.83 L69.59
+ 306.81 L0 306.81 L0 308.85 ZM72.66 149.52 L69.59 143.4 L66.53 149.52 L72.66 149.52 Z" class="st11"/>
+ </g>
+ <g id="shape41-86" v:mID="41" v:groupContext="shape" transform="translate(335.112,-199.647)">
+ <title>Sheet.41</title>
+ <desc>Apply the equation</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="71.2648" cy="300.436" width="142.53" height="16.8275"/>
+ <path d="M142.53 292.02 L0 292.02 L0 308.85 L142.53 308.85 L142.53 292.02" class="st3"/>
+ <text x="13.19" y="304.64" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Apply the equation </text> </g>
+ <g id="shape42-90" v:mID="42" v:groupContext="shape" transform="translate(341.115,-182.871)">
+ <title>Sheet.42</title>
+ <desc>to retrieve the bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="64.5256" cy="300.436" width="129.06" height="16.8275"/>
+ <path d="M129.05 292.02 L0 292.02 L0 308.85 L129.05 308.85 L129.05 292.02" class="st3"/>
+ <text x="12.31" y="304.64" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>to retrieve the bit </text> </g>
+ <g id="shape43-94" v:mID="43" v:groupContext="shape" transform="translate(349.999,-166.095)">
+ <title>Sheet.43</title>
+ <desc>position in the</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="54.2285" cy="300.436" width="108.46" height="16.8275"/>
+ <path d="M108.46 292.02 L0 292.02 L0 308.85 L108.46 308.85 L108.46 292.02" class="st3"/>
+ <text x="10.97" y="304.64" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>position in the </text> </g>
+ <g id="shape44-98" v:mID="44" v:groupContext="shape" transform="translate(353.361,-149.319)">
+ <title>Sheet.44</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="47.9619" cy="300.436" width="95.93" height="16.8275"/>
+ <path d="M95.92 292.02 L0 292.02 L0 308.85 L95.92 308.85 L95.92 292.02" class="st3"/>
+ <text x="8.21" y="304.64" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape47-102" v:mID="47" v:groupContext="shape" transform="translate(115.17,255.2) rotate(-90)">
+ <title>1-D word balloon</title>
+ <desc>Retrieve the value “0' from the specified location in the loo...</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ <v:ud v:nameU="Scale" v:val="VT0(1):26"/>
+ <v:ud v:nameU="AntiScale" v:val="VT0(1):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="29.2016" cy="218.85" width="180" height="58.4032" transform="rotate(90)"/>
+ <path d="M0 308.85 L58.4 308.85 L58.4 128.85 L0 128.85 L0 204.67 L-11.87 38.85 L-7.09 233.03 L0 233.03 L0 308.85 Z"
+ class="st13"/>
+ <text x="136.98" y="-41.8" transform="rotate(90)" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Retrieve the value “0' from <tspan
+ x="134.41" dy="1.2em" class="st15">the specified location in the </tspan><tspan x="181.1" dy="1.2em"
+ class="st15">lookup table</tspan></text> </g>
+ <g id="shape48-107" v:mID="48" v:groupContext="shape" transform="translate(169.209,-251.966)">
+ <title>Sheet.48</title>
+ <desc>F(Key, hash_index = 38123</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54.2285" cy="295.35" width="108.46" height="27"/>
+ <rect x="0" y="281.85" width="108.457" height="27" class="st16"/>
+ <text x="5.86" y="291.75" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>F(Key, hash_index = <tspan
+ x="39.02" dy="1.2em" class="st15">38123</tspan></text> </g>
+ <g id="shape49-111" v:mID="49" v:groupContext="shape" transform="translate(553.962,99) rotate(90)">
+ <title>1-D word balloon.49</title>
+ <desc>Apply the equation to retrieve the bit position in the lookup...</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ <v:ud v:nameU="Scale" v:val="VT0(1):26"/>
+ <v:ud v:nameU="AntiScale" v:val="VT0(1):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="29.2016" cy="218.85" width="180" height="58.4032" transform="rotate(-90)"/>
+ <path d="M0 308.85 L58.4 308.85 L58.4 128.85 L0 128.85 L0 204.67 L-51.13 299.85 L0 233.03 L0 308.85 Z" class="st13"/>
+ <text x="-284.62" y="16.6" transform="rotate(-90)" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Apply the equation to <tspan
+ x="-296.67" dy="1.2em" class="st15">retrieve the bit position in </tspan><tspan x="-270.22" dy="1.2em"
+ class="st15">the lookup</tspan>_table</text> </g>
+ <g id="shape50-116" v:mID="50" v:groupContext="shape" transform="translate(640.132,-104.709) rotate(44.1224)">
+ <title>Sheet.50</title>
+ <path d="M0 308.85 L54.13 308.85" class="st18"/>
+ </g>
+ <g id="shape51-122" v:mID="51" v:groupContext="shape" transform="translate(433.02,-122.267)">
+ <title>Sheet.51</title>
+ <desc>(Hash(key,seed1)+38123*hash(key,seed2))%16</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="144" cy="295.35" width="288" height="27"/>
+ <rect x="0" y="281.85" width="288" height="27" class="st2"/>
+ <text x="9.86" y="299.55" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(Hash(key,seed1)+38123*hash(key,seed2))%16</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i12.svg b/doc/guides/prog_guide/img/efd_i12.svg
new file mode 100644
index 0000000..a309d58
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i12.svg
@@ -0,0 +1,1008 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i13.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="10.2932in" height="5.27505in"
+ viewBox="0 0 741.108 379.804" xml:space="preserve" color-interpolation-filters="sRGB" class="st30">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st2 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st3 {fill:#004280;font-family:Arial;font-size:0.828804em}
+ .st4 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st5 {stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st6 {fill:#7030a0;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st7 {fill:#d0d6d9;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st8 {fill:#006fc5;stroke:#006fc5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.00749637}
+ .st9 {fill:#006fc5;stroke:#006fc5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st10 {fill:#d0d6d9;stroke:#d0d6d9;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st11 {fill:#d0d6d9;stroke:#d0d6d9;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st12 {fill:#004280;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st13 {fill:#00b050;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st14 {fill:#ff0000;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st15 {fill:#00b050;stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st16 {fill:#c00000;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st17 {fill:#000000;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st18 {fill:#7f6d00;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st19 {fill:#ff0000;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st20 {fill:#7e8d96;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st21 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st22 {fill:#000000;font-family:Arial;font-size:0.998566em}
+ .st23 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st24 {stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st25 {fill:#ffffff;font-family:Arial;font-size:0.998566em}
+ .st26 {fill:#ff6600;stroke:#ff6600;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.00749637}
+ .st27 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st28 {fill:#ff0000;stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st29 {fill:#ffffff;font-family:Arial;font-size:1.49785em}
+ .st30 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(304.703,-329.32)">
+ <title>Sheet.3</title>
+ <path d="M0 379.8 C-0 375.37 0.6 371.78 1.35 371.78 L205.05 371.78 C205.78 371.78 206.38 368.18 206.38 363.75 C206.38
+ 368.18 206.98 371.78 207.73 371.78 L411.43 371.78 C412.15 371.78 412.75 375.37 412.75 379.8" class="st1"/>
+ </g>
+ <g id="shape4-4" v:mID="4" v:groupContext="shape" transform="translate(219.943,-329.32)">
+ <title>Sheet.4</title>
+ <path d="M0 379.8 C0 375.64 0.57 372.25 1.26 372.25 L29.77 372.25 C30.48 372.25 31.03 368.88 31.03 364.71 C31.03 368.88
+ 31.6 372.25 32.29 372.25 L60.81 372.25 C61.51 372.25 62.07 375.64 62.07 379.8" class="st1"/>
+ </g>
+ <g id="shape5-7" v:mID="5" v:groupContext="shape" transform="translate(241.175,-343.9)">
+ <title>Sheet.5</title>
+ <desc>Bins</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="12.7158" cy="373.835" width="25.44" height="11.9384"/>
+ <path d="M25.43 367.87 L0 367.87 L0 379.8 L25.43 379.8 L25.43 367.87" class="st2"/>
+ <text x="3.04" y="376.82" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Bins</text> </g>
+ <g id="shape6-11" v:mID="6" v:groupContext="shape" transform="translate(496.212,-344.504)">
+ <title>Sheet.6</title>
+ <desc>Groups</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="20.3447" cy="373.835" width="40.69" height="11.9384"/>
+ <path d="M40.69 367.87 L0 367.87 L0 379.8 L40.69 379.8 L40.69 367.87" class="st2"/>
+ <text x="4.04" y="376.82" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Groups</text> </g>
+ <g id="shape7-15" v:mID="7" v:groupContext="shape" transform="translate(131.823,-260.299)">
+ <title>Sheet.7</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape8-17" v:mID="8" v:groupContext="shape" transform="translate(131.823,-260.299)">
+ <title>Sheet.8</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape9-20" v:mID="9" v:groupContext="shape" transform="translate(134.706,-310.738)">
+ <title>Sheet.9</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape10-24" v:mID="10" v:groupContext="shape" transform="translate(122.218,-329.32)">
+ <title>Sheet.10</title>
+ <path d="M0 379.8 C-0 375.64 0.57 372.25 1.26 372.25 L29.77 372.25 C30.47 372.25 31.03 368.88 31.03 364.71 C31.03 368.88
+ 31.6 372.25 32.29 372.25 L60.81 372.25 C61.51 372.25 62.07 375.64 62.07 379.8" class="st1"/>
+ </g>
+ <g id="shape11-27" v:mID="11" v:groupContext="shape" transform="translate(137.598,-343.9)">
+ <title>Sheet.11</title>
+ <desc>Chunks</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="20.9813" cy="373.835" width="41.97" height="11.9384"/>
+ <path d="M41.96 367.87 L0 367.87 L0 379.8 L41.96 379.8 L41.96 367.87" class="st2"/>
+ <text x="4.12" y="376.82" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Chunks</text> </g>
+ <g id="shape12-31" v:mID="12" v:groupContext="shape" transform="translate(131.823,-195.232)">
+ <title>Sheet.12</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape13-33" v:mID="13" v:groupContext="shape" transform="translate(131.823,-195.232)">
+ <title>Sheet.13</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape14-36" v:mID="14" v:groupContext="shape" transform="translate(134.706,-245.682)">
+ <title>Sheet.14</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape15-40" v:mID="15" v:groupContext="shape" transform="translate(131.823,-130.525)">
+ <title>Sheet.15</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st7"/>
+ </g>
+ <g id="shape16-42" v:mID="16" v:groupContext="shape" transform="translate(131.823,-130.525)">
+ <title>Sheet.16</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape17-45" v:mID="17" v:groupContext="shape" transform="translate(134.706,-180.952)">
+ <title>Sheet.17</title>
+ <desc>…</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.30974" cy="373.835" width="14.62" height="11.9384"/>
+ <path d="M14.62 367.87 L0 367.87 L0 379.8 L14.62 379.8 L14.62 367.87" class="st2"/>
+ <text x="2.34" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>…</text> </g>
+ <g id="shape18-49" v:mID="18" v:groupContext="shape" transform="translate(131.823,-65.4584)">
+ <title>Sheet.18</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape19-51" v:mID="19" v:groupContext="shape" transform="translate(131.823,-65.4584)">
+ <title>Sheet.19</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape20-54" v:mID="20" v:groupContext="shape" transform="translate(130.403,-115.896)">
+ <title>Sheet.20</title>
+ <desc>variable</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="24.7986" cy="373.835" width="49.6" height="11.9384"/>
+ <path d="M49.6 367.87 L0 367.87 L0 379.8 L49.6 379.8 L49.6 367.87" class="st2"/>
+ <text x="6" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>variable </text> </g>
+ <g id="shape21-58" v:mID="21" v:groupContext="shape" transform="translate(130.403,-103.913)">
+ <title>Sheet.21</title>
+ <desc># of</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="13.347" cy="373.835" width="26.7" height="11.9384"/>
+ <path d="M26.69 367.87 L0 367.87 L0 379.8 L26.69 379.8 L26.69 367.87" class="st2"/>
+ <text x="4.51" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/># of </text> </g>
+ <g id="shape22-62" v:mID="22" v:groupContext="shape" transform="translate(130.403,-91.93)">
+ <title>Sheet.22</title>
+ <desc>chunks</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.6122" cy="373.835" width="43.23" height="11.9384"/>
+ <path d="M43.22 367.87 L0 367.87 L0 379.8 L43.22 379.8 L43.22 367.87" class="st2"/>
+ <text x="4.2" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>chunks</text> </g>
+ <g id="shape23-66" v:mID="23" v:groupContext="shape" transform="translate(130.403,-79.9472)">
+ <title>Sheet.23</title>
+ <desc>(power</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.9251" cy="373.835" width="43.86" height="11.9384"/>
+ <path d="M43.85 367.87 L0 367.87 L0 379.8 L43.85 379.8 L43.85 367.87" class="st2"/>
+ <text x="5.62" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(power </text> </g>
+ <g id="shape24-70" v:mID="24" v:groupContext="shape" transform="translate(130.403,-67.9643)">
+ <title>Sheet.24</title>
+ <desc>of 2)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="13.6626" cy="373.835" width="27.33" height="11.9384"/>
+ <path d="M27.33 367.87 L0 367.87 L0 379.8 L27.33 379.8 L27.33 367.87" class="st2"/>
+ <text x="3.17" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>of 2)</text> </g>
+ <g id="shape25-74" v:mID="25" v:groupContext="shape" transform="translate(172.289,-260.838)">
+ <title>Sheet.25</title>
+ <path d="M1.43 379.8 L3.29 375.51 L1.85 374.9 L0 379.19 L1.43 379.8 L1.43 379.8 ZM3.9 374.08 L5.76 369.79 L4.32 369.18
+ L2.47 373.47 L3.9 374.08 L3.9 374.08 ZM6.37 368.36 L8.22 364.07 L6.79 363.45 L4.94 367.75 L6.37 368.36 L6.37
+ 368.36 ZM8.84 362.64 L10.69 358.35 L9.26 357.73 L7.41 362.02 L8.84 362.64 L8.84 362.64 ZM11.31 356.92 L13.16
+ 352.62 L11.73 352 L9.87 356.3 L11.31 356.92 L11.31 356.92 ZM13.78 351.19 L15.63 346.9 L14.2 346.28 L12.34
+ 350.57 L13.78 351.19 L13.78 351.19 ZM16.25 345.47 L18.1 341.17 L16.67 340.56 L14.81 344.85 L16.25 345.47
+ L16.25 345.47 ZM18.71 339.74 L20.57 335.45 L19.13 334.84 L17.28 339.13 L18.71 339.74 L18.71 339.74 ZM21.18
+ 334.02 L23.04 329.73 L21.6 329.12 L19.75 333.41 L21.18 334.02 L21.18 334.02 ZM23.65 328.3 L25.5 324.01 L24.07
+ 323.39 L22.22 327.68 L23.65 328.3 L23.65 328.3 ZM26.12 322.58 L27.97 318.28 L26.54 317.67 L24.69 321.96
+ L26.12 322.58 L26.12 322.58 ZM28.59 316.85 L29.44 314.87 L28.01 314.25 L27.16 316.24 L28.59 316.85 L28.59
+ 316.85 Z" class="st8"/>
+ </g>
+ <g id="shape26-76" v:mID="26" v:groupContext="shape" transform="translate(172.476,-20.463)">
+ <title>Sheet.26</title>
+ <path d="M1.55 203.84 L2.28 208.45 L0.74 208.7 L0 204.09 L1.55 203.84 L1.55 203.84 ZM2.52 209.99 L3.27 214.61 L1.73 214.86
+ L0.99 210.23 L2.52 209.99 L2.52 209.99 ZM3.51 216.15 L4.25 220.76 L2.7 221 L1.97 216.39 L3.51 216.15 L3.51
+ 216.15 ZM4.49 222.3 L5.24 226.92 L3.69 227.16 L2.96 222.54 L4.49 222.3 L4.49 222.3 ZM5.48 228.45 L6.21 233.07
+ L4.67 233.31 L3.93 228.7 L5.48 228.45 L5.48 228.45 ZM6.47 234.6 L7.2 239.22 L5.66 239.47 L4.92 234.86 L6.47
+ 234.6 L6.47 234.6 ZM7.44 240.76 L8.18 245.37 L6.65 245.63 L5.9 241 L7.44 240.76 L7.44 240.76 ZM8.43 246.91
+ L9.17 251.53 L7.62 251.77 L6.89 247.15 L8.43 246.91 L8.43 246.91 ZM9.41 253.07 L10.14 257.68 L8.61 257.92
+ L7.88 253.31 L9.41 253.07 L9.41 253.07 ZM10.4 259.21 L11.14 263.84 L9.59 264.08 L8.85 259.47 L10.4 259.21
+ L10.4 259.21 ZM11.38 265.37 L12.13 269.98 L10.58 270.24 L9.84 265.62 L11.38 265.37 L11.38 265.37 ZM12.37
+ 271.52 L13.1 276.14 L11.56 276.39 L10.82 271.76 L12.37 271.52 L12.37 271.52 ZM13.34 277.68 L14.09 282.29
+ L12.55 282.53 L11.81 277.92 L13.34 277.68 L13.34 277.68 ZM14.33 283.84 L15.07 288.45 L13.52 288.69 L12.79
+ 284.08 L14.33 283.84 L14.33 283.84 ZM15.32 289.99 L16.06 294.61 L14.51 294.85 L13.78 290.23 L15.32 289.99
+ L15.32 289.99 ZM16.3 296.13 L17.03 300.75 L15.5 301 L14.75 296.39 L16.3 296.13 L16.3 296.13 ZM17.29 302.29
+ L18.02 306.9 L16.48 307.16 L15.74 302.53 L17.29 302.29 L17.29 302.29 ZM18.26 308.45 L19 313.06 L17.47 313.3
+ L16.73 308.69 L18.26 308.45 L18.26 308.45 ZM19.25 314.6 L19.99 319.22 L18.44 319.46 L17.71 314.84 L19.25
+ 314.6 L19.25 314.6 ZM20.23 320.76 L20.96 325.37 L19.43 325.61 L18.7 321 L20.23 320.76 L20.23 320.76 ZM21.22
+ 326.9 L21.96 331.51 L20.41 331.77 L19.67 327.15 L21.22 326.9 L21.22 326.9 ZM22.2 333.06 L22.95 337.67 L21.4
+ 337.92 L20.66 333.31 L22.2 333.06 L22.2 333.06 ZM23.19 339.21 L23.92 343.83 L22.38 344.07 L21.64 339.45
+ L23.19 339.21 L23.19 339.21 ZM24.18 345.37 L24.91 349.98 L23.37 350.22 L22.63 345.61 L24.18 345.37 L24.18
+ 345.37 ZM25.15 351.52 L25.89 356.14 L24.36 356.38 L23.61 351.76 L25.15 351.52 L25.15 351.52 ZM26.14 357.67
+ L26.88 362.28 L25.33 362.53 L24.6 357.92 L26.14 357.67 L26.14 357.67 ZM27.12 363.82 L27.85 368.44 L26.32
+ 368.69 L25.59 364.08 L27.12 363.82 L27.12 363.82 ZM28.11 369.98 L28.84 374.59 L27.3 374.83 L26.56 370.22
+ L28.11 369.98 L28.11 369.98 ZM29.08 376.13 L29.64 379.55 L28.09 379.8 L27.55 376.37 L29.08 376.13 L29.08
+ 376.13 Z" class="st9"/>
+ </g>
+ <g id="shape27-78" v:mID="27" v:groupContext="shape" transform="translate(276.159,-233.368)">
+ <title>Sheet.27</title>
+ <path d="M0.45 294.85 L354.04 376.06 L353.59 378.04 L0 296.85 L0.45 294.85 L0.45 294.85 ZM353.5 373.84 L358.79 378.19
+ L352.12 379.8 L353.5 373.84 L353.5 373.84 Z" class="st10"/>
+ </g>
+ <g id="shape28-80" v:mID="28" v:groupContext="shape" transform="translate(275.859,-178.726)">
+ <title>Sheet.28</title>
+ <path d="M1.05 240.32 L231.44 376.33 L230.39 378.1 L0 242.09 L1.05 240.32 L1.05 240.32 ZM231.59 374.05 L235.31 379.8
+ L228.47 379.32 L231.59 374.05 L231.59 374.05 Z" class="st10"/>
+ </g>
+ <g id="shape29-82" v:mID="29" v:groupContext="shape" transform="translate(275.379,-87.6866)">
+ <title>Sheet.29</title>
+ <path d="M2 149.94 L50.05 374.61 L48.05 375.04 L0 150.38 L2 149.94 L2 149.94 ZM51.83 373.18 L50.12 379.8 L45.85 374.47
+ L51.83 373.18 L51.83 373.18 Z" class="st11"/>
+ </g>
+ <g id="shape30-84" v:mID="30" v:groupContext="shape" transform="translate(276.279,-177.108)">
+ <title>Sheet.30</title>
+ <path d="M0.21 353.74 L229.55 375.85 L229.34 377.89 L0 355.75 L0.21 353.74 L0.21 353.74 ZM228.71 373.72 L234.53 377.35
+ L228.14 379.8 L228.71 373.72 L228.71 373.72 Z" class="st10"/>
+ </g>
+ <g id="shape31-86" v:mID="31" v:groupContext="shape" transform="translate(275.919,-213.926)">
+ <title>Sheet.31</title>
+ <path d="M0.45 308.72 L312.65 376.06 L312.2 378.04 L0 310.72 L0.45 308.72 L0.45 308.72 ZM312.08 373.84 L317.43 378.13
+ L310.79 379.8 L312.08 373.84 L312.08 373.84 Z" class="st10"/>
+ </g>
+ <g id="shape32-88" v:mID="32" v:groupContext="shape" transform="translate(275.439,-143.377)">
+ <title>Sheet.32</title>
+ <path d="M1.4 238.41 L150.34 375.59 L148.96 377.09 L0 239.9 L1.4 238.41 L1.4 238.41 ZM150.98 373.41 L153.4 379.8 L146.83
+ 377.9 L150.98 373.41 L150.98 373.41 Z" class="st11"/>
+ </g>
+ <g id="shape33-90" v:mID="33" v:groupContext="shape" transform="translate(275.274,-108.821)">
+ <title>Sheet.33</title>
+ <path d="M1.73 236.53 L90.79 374.97 L89.08 376.07 L0 237.63 L1.73 236.53 L1.73 236.53 ZM91.96 373 L92.7 379.8 L86.82
+ 376.31 L91.96 373 L91.96 373 Z" class="st11"/>
+ </g>
+ <g id="shape34-92" v:mID="34" v:groupContext="shape" transform="translate(275.364,-124.069)">
+ <title>Sheet.34</title>
+ <path d="M1.55 251.66 L108.22 375.28 L106.67 376.61 L0 253 L1.55 251.66 L1.55 251.66 ZM109.1 373.18 L110.78 379.8 L104.46
+ 377.17 L109.1 373.18 L109.1 373.18 Z" class="st11"/>
+ </g>
+ <g id="shape35-94" v:mID="35" v:groupContext="shape" transform="translate(275.154,-87.7165)">
+ <title>Sheet.35</title>
+ <path d="M1.97 215.68 L49.85 374.64 L47.9 375.22 L0 216.27 L1.97 215.68 L1.97 215.68 ZM51.52 373.08 L50.35 379.8 L45.65
+ 374.83 L51.52 373.08 L51.52 373.08 Z" class="st11"/>
+ </g>
+ <g id="shape36-96" v:mID="36" v:groupContext="shape" transform="translate(276.009,-143.736)">
+ <title>Sheet.36</title>
+ <path d="M0.74 320.41 L147.92 376.36 L147.2 378.26 L0 322.32 L0.74 320.41 L0.74 320.41 ZM147.7 374.08 L152.34 379.11
+ L145.52 379.8 L147.7 374.08 L147.7 374.08 Z" class="st11"/>
+ </g>
+ <g id="shape37-98" v:mID="37" v:groupContext="shape" transform="translate(275.649,-108.821)">
+ <title>Sheet.37</title>
+ <path d="M1.46 285.74 L89.46 375.45 L88 376.87 L0 287.16 L1.46 285.74 L1.46 285.74 ZM90.21 373.29 L92.29 379.8 L85.82
+ 377.57 L90.21 373.29 L90.21 373.29 Z" class="st11"/>
+ </g>
+ <g id="shape38-100" v:mID="38" v:groupContext="shape" transform="translate(275.934,-108.686)">
+ <title>Sheet.38</title>
+ <path d="M0.89 335.24 L87.85 376.57 L86.97 378.41 L0 337.09 L0.89 335.24 L0.89 335.24 ZM87.81 374.29 L92.01 379.67 L85.16
+ 379.8 L87.81 374.29 L87.81 374.29 Z" class="st11"/>
+ </g>
+ <g id="shape39-102" v:mID="39" v:groupContext="shape" transform="translate(275.574,-89.454)">
+ <title>Sheet.39</title>
+ <path d="M1.61 316.29 L48.49 375.18 L46.88 376.45 L0 317.57 L1.61 316.29 L1.61 316.29 ZM49.45 373.11 L50.86 379.8 L44.65
+ 376.91 L49.45 373.11 L49.45 373.11 Z" class="st11"/>
+ </g>
+ <g id="shape40-104" v:mID="40" v:groupContext="shape" transform="translate(276.324,-141.744)">
+ <title>Sheet.40</title>
+ <path d="M0.11 368.21 L146.74 375.79 L146.62 377.83 L0 370.23 L0.11 368.21 L0.11 368.21 ZM145.82 373.71 L151.78 377.08
+ L145.51 379.8 L145.82 373.71 L145.82 373.71 Z" class="st11"/>
+ </g>
+ <g id="shape41-106" v:mID="41" v:groupContext="shape" transform="translate(230.508,-309.069)">
+ <title>Sheet.41</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st4"/>
+ </g>
+ <g id="shape42-108" v:mID="42" v:groupContext="shape" transform="translate(230.508,-309.069)">
+ <title>Sheet.42</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape43-111" v:mID="43" v:groupContext="shape" transform="translate(233.39,-309.868)">
+ <title>Sheet.43</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape44-115" v:mID="44" v:groupContext="shape" transform="translate(263.764,-309.869)">
+ <title>Sheet.44</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape45-119" v:mID="45" v:groupContext="shape" transform="translate(230.508,-292.413)">
+ <title>Sheet.45</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape46-121" v:mID="46" v:groupContext="shape" transform="translate(230.508,-292.413)">
+ <title>Sheet.46</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape47-124" v:mID="47" v:groupContext="shape" transform="translate(233.39,-293.221)">
+ <title>Sheet.47</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape48-128" v:mID="48" v:groupContext="shape" transform="translate(230.508,-275.757)">
+ <title>Sheet.48</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st4"/>
+ </g>
+ <g id="shape49-130" v:mID="49" v:groupContext="shape" transform="translate(230.508,-275.757)">
+ <title>Sheet.49</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape50-133" v:mID="50" v:groupContext="shape" transform="translate(233.39,-276.574)">
+ <title>Sheet.50</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape51-137" v:mID="51" v:groupContext="shape" transform="translate(252.478,-276.574)">
+ <title>Sheet.51</title>
+ <desc>3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>3</text> </g>
+ <g id="shape52-141" v:mID="52" v:groupContext="shape" transform="translate(258.001,-276.574)">
+ <title>Sheet.52</title>
+ <desc>+1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="8.11122" cy="373.835" width="16.23" height="11.9384"/>
+ <path d="M16.22 367.87 L0 367.87 L0 379.8 L16.22 379.8 L16.22 367.87" class="st2"/>
+ <text x="2.44" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>+1</text> </g>
+ <g id="shape53-145" v:mID="53" v:groupContext="shape" transform="translate(230.508,-259.7)">
+ <title>Sheet.53</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape54-147" v:mID="54" v:groupContext="shape" transform="translate(230.508,-259.7)">
+ <title>Sheet.54</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape55-150" v:mID="55" v:groupContext="shape" transform="translate(233.39,-260.497)">
+ <title>Sheet.55</title>
+ <desc>3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>3</text> </g>
+ <g id="shape56-154" v:mID="56" v:groupContext="shape" transform="translate(230.508,-243.164)">
+ <title>Sheet.56</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st4"/>
+ </g>
+ <g id="shape57-156" v:mID="57" v:groupContext="shape" transform="translate(230.508,-243.164)">
+ <title>Sheet.57</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape58-159" v:mID="58" v:groupContext="shape" transform="translate(233.39,-244.053)">
+ <title>Sheet.58</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape59-163" v:mID="59" v:groupContext="shape" transform="translate(263.764,-244.053)">
+ <title>Sheet.59</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape60-167" v:mID="60" v:groupContext="shape" transform="translate(230.508,-227.107)">
+ <title>Sheet.60</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape61-169" v:mID="61" v:groupContext="shape" transform="translate(230.508,-227.107)">
+ <title>Sheet.61</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape62-172" v:mID="62" v:groupContext="shape" transform="translate(233.39,-227.976)">
+ <title>Sheet.62</title>
+ <desc>5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>5</text> </g>
+ <g id="shape63-176" v:mID="63" v:groupContext="shape" transform="translate(230.508,-210.211)">
+ <title>Sheet.63</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape64-178" v:mID="64" v:groupContext="shape" transform="translate(230.508,-210.211)">
+ <title>Sheet.64</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape65-181" v:mID="65" v:groupContext="shape" transform="translate(233.39,-211.085)">
+ <title>Sheet.65</title>
+ <desc>6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>6</text> </g>
+ <g id="shape66-185" v:mID="66" v:groupContext="shape" transform="translate(230.508,-193.794)">
+ <title>Sheet.66</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st4"/>
+ </g>
+ <g id="shape67-187" v:mID="67" v:groupContext="shape" transform="translate(230.508,-193.794)">
+ <title>Sheet.67</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape68-190" v:mID="68" v:groupContext="shape" transform="translate(233.39,-194.681)">
+ <title>Sheet.68</title>
+ <desc>7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>7</text> </g>
+ <g id="shape69-194" v:mID="69" v:groupContext="shape" transform="translate(263.764,-194.681)">
+ <title>Sheet.69</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape70-198" v:mID="70" v:groupContext="shape" transform="translate(230.508,-177.258)">
+ <title>Sheet.70</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape71-200" v:mID="71" v:groupContext="shape" transform="translate(230.508,-177.258)">
+ <title>Sheet.71</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape72-203" v:mID="72" v:groupContext="shape" transform="translate(233.39,-178.117)">
+ <title>Sheet.72</title>
+ <desc>8</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>8</text> </g>
+ <g id="shape73-207" v:mID="73" v:groupContext="shape" transform="translate(230.508,-160.602)">
+ <title>Sheet.73</title>
+ <path d="M0 363.15 L0 379.8 L41.18 379.8 L41.18 363.15 L0 363.15 L0 363.15 Z" class="st7"/>
+ </g>
+ <g id="shape74-209" v:mID="74" v:groupContext="shape" transform="translate(230.508,-160.602)">
+ <title>Sheet.74</title>
+ <path d="M0 363.15 L41.18 363.15 L41.18 379.8 L0 379.8 L0 363.15" class="st5"/>
+ </g>
+ <g id="shape75-212" v:mID="75" v:groupContext="shape" transform="translate(233.39,-161.505)">
+ <title>Sheet.75</title>
+ <desc>9</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>9</text> </g>
+ <g id="shape76-216" v:mID="76" v:groupContext="shape" transform="translate(230.508,-143.946)">
+ <title>Sheet.76</title>
+ <path d="M0 363.15 L0 379.8 L41.18 379.8 L41.18 363.15 L0 363.15 L0 363.15 Z" class="st4"/>
+ </g>
+ <g id="shape77-218" v:mID="77" v:groupContext="shape" transform="translate(230.508,-143.946)">
+ <title>Sheet.77</title>
+ <path d="M0 363.15 L41.18 363.15 L41.18 379.8 L0 379.8 L0 363.15" class="st5"/>
+ </g>
+ <g id="shape78-221" v:mID="78" v:groupContext="shape" transform="translate(233.39,-144.841)">
+ <title>Sheet.78</title>
+ <desc>10</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>10</text> </g>
+ <g id="shape79-225" v:mID="79" v:groupContext="shape" transform="translate(263.764,-144.841)">
+ <title>Sheet.79</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape80-229" v:mID="80" v:groupContext="shape" transform="translate(230.508,-127.529)">
+ <title>Sheet.80</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape81-231" v:mID="81" v:groupContext="shape" transform="translate(230.508,-127.529)">
+ <title>Sheet.81</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape82-234" v:mID="82" v:groupContext="shape" transform="translate(233.39,-128.329)">
+ <title>Sheet.82</title>
+ <desc>11</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>11</text> </g>
+ <g id="shape83-238" v:mID="83" v:groupContext="shape" transform="translate(230.508,-110.754)">
+ <title>Sheet.83</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape84-240" v:mID="84" v:groupContext="shape" transform="translate(230.508,-110.754)">
+ <title>Sheet.84</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape85-243" v:mID="85" v:groupContext="shape" transform="translate(233.39,-111.64)">
+ <title>Sheet.85</title>
+ <desc>12</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>12</text> </g>
+ <g id="shape86-247" v:mID="86" v:groupContext="shape" transform="translate(230.508,-94.9362)">
+ <title>Sheet.86</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape87-249" v:mID="87" v:groupContext="shape" transform="translate(230.508,-94.9362)">
+ <title>Sheet.87</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape88-252" v:mID="88" v:groupContext="shape" transform="translate(233.39,-95.7375)">
+ <title>Sheet.88</title>
+ <desc>…</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.30974" cy="373.835" width="14.62" height="11.9384"/>
+ <path d="M14.62 367.87 L0 367.87 L0 379.8 L14.62 379.8 L14.62 367.87" class="st2"/>
+ <text x="2.34" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>…</text> </g>
+ <g id="shape89-256" v:mID="89" v:groupContext="shape" transform="translate(230.508,-78.999)">
+ <title>Sheet.89</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape90-258" v:mID="90" v:groupContext="shape" transform="translate(230.508,-78.999)">
+ <title>Sheet.90</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape91-261" v:mID="91" v:groupContext="shape" transform="translate(233.39,-79.8525)">
+ <title>Sheet.91</title>
+ <desc>255</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1326" cy="373.835" width="22.27" height="11.9384"/>
+ <path d="M22.27 367.87 L0 367.87 L0 379.8 L22.27 379.8 L22.27 367.87" class="st2"/>
+ <text x="2.84" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>255</text> </g>
+ <g id="shape92-265" v:mID="92" v:groupContext="shape" transform="translate(276.219,-250.503)">
+ <title>Sheet.92</title>
+ <path d="M0.33 311.98 L396.81 375.94 L396.48 377.95 L0 313.99 L0.33 311.98 L0.33 311.98 ZM396.12 373.75 L401.68 377.74
+ L395.16 379.8 L396.12 373.75 L396.12 373.75 Z" class="st15"/>
+ </g>
+ <g id="shape93-267" v:mID="93" v:groupContext="shape" transform="translate(275.859,-178.426)">
+ <title>Sheet.93</title>
+ <path d="M0.57 305.72 L230.93 376.21 L230.33 378.16 L0 307.67 L0.57 305.72 L0.57 305.72 ZM230.57 373.96 L235.52 378.67
+ L228.77 379.8 L230.57 373.96 L230.57 373.96 Z" class="st15"/>
+ </g>
+ <g id="shape94-269" v:mID="94" v:groupContext="shape" transform="translate(276.279,-151.285)">
+ <title>Sheet.94</title>
+ <path d="M0.21 379.8 L230.12 353.17 L229.88 351.14 L0 377.8 L0.21 379.8 L0.21 379.8 ZM229.34 355.3 L235.07 351.55 L228.65
+ 349.25 L229.34 355.3 L229.34 355.3 Z" class="st15"/>
+ </g>
+ <g id="shape95-271" v:mID="95" v:groupContext="shape" transform="translate(276.009,-232.679)">
+ <title>Sheet.95</title>
+ <path d="M0.27 327.47 L354.22 375.91 L353.95 377.92 L0 329.48 L0.27 327.47 L0.27 327.47 ZM353.5 373.75 L359.15 377.62
+ L352.66 379.8 L353.5 373.75 L353.5 373.75 Z" class="st10"/>
+ </g>
+ <g id="shape96-273" v:mID="96" v:groupContext="shape" transform="translate(276.279,-201.134)">
+ <title>Sheet.96</title>
+ <path d="M0.21 379.8 L353.86 348.14 L353.68 346.1 L0 377.77 L0.21 379.8 L0.21 379.8 ZM353.05 350.24 L358.88 346.64 L352.48
+ 344.16 L353.05 350.24 L353.05 350.24 Z" class="st15"/>
+ </g>
+ <g id="shape97-275" v:mID="97" v:groupContext="shape" transform="translate(346.482,-41.2531)">
+ <title>Sheet.97</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st7"/>
+ </g>
+ <g id="shape98-277" v:mID="98" v:groupContext="shape" transform="translate(346.482,-41.2531)">
+ <title>Sheet.98</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape99-280" v:mID="99" v:groupContext="shape" transform="translate(349.371,-91.6514)">
+ <title>Sheet.99</title>
+ <desc>…</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.30974" cy="373.835" width="14.62" height="11.9384"/>
+ <path d="M14.62 367.87 L0 367.87 L0 379.8 L14.62 379.8 L14.62 367.87" class="st2"/>
+ <text x="2.34" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>…</text> </g>
+ <g id="shape100-284" v:mID="100" v:groupContext="shape" transform="translate(470.019,-94.337)">
+ <title>Sheet.100</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st7"/>
+ </g>
+ <g id="shape101-286" v:mID="101" v:groupContext="shape" transform="translate(470.019,-94.337)">
+ <title>Sheet.101</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape102-289" v:mID="102" v:groupContext="shape" transform="translate(472.925,-144.778)">
+ <title>Sheet.102</title>
+ <desc>5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>5</text> </g>
+ <g id="shape103-293" v:mID="103" v:groupContext="shape" transform="translate(511.558,-113.749)">
+ <title>Sheet.103</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape104-295" v:mID="104" v:groupContext="shape" transform="translate(511.558,-113.749)">
+ <title>Sheet.104</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape105-298" v:mID="105" v:groupContext="shape" transform="translate(514.441,-164.138)">
+ <title>Sheet.105</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape106-302" v:mID="106" v:groupContext="shape" transform="translate(542.148,-164.138)">
+ <title>Sheet.106</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape107-306" v:mID="107" v:groupContext="shape" transform="translate(542.148,-152.155)">
+ <title>Sheet.107</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape108-310" v:mID="108" v:groupContext="shape" transform="translate(536.626,-140.172)">
+ <title>Sheet.108</title>
+ <desc>10</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>10</text> </g>
+ <g id="shape109-314" v:mID="109" v:groupContext="shape" transform="translate(514.201,-114.441)">
+ <title>Sheet.109</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape110-318" v:mID="110" v:groupContext="shape" transform="translate(519.723,-114.441)">
+ <title>Sheet.110</title>
+ <desc>+4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="8.11122" cy="373.835" width="16.23" height="11.9384"/>
+ <path d="M16.22 367.87 L0 367.87 L0 379.8 L16.22 379.8 L16.22 367.87" class="st2"/>
+ <text x="2.44" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>+4</text> </g>
+ <g id="shape111-322" v:mID="111" v:groupContext="shape" transform="translate(552.257,-130.525)">
+ <title>Sheet.111</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st7"/>
+ </g>
+ <g id="shape112-324" v:mID="112" v:groupContext="shape" transform="translate(552.257,-130.525)">
+ <title>Sheet.112</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape113-327" v:mID="113" v:groupContext="shape" transform="translate(555.203,-180.952)">
+ <title>Sheet.113</title>
+ <desc>3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>3</text> </g>
+ <g id="shape114-331" v:mID="114" v:groupContext="shape" transform="translate(634.615,-169.11)">
+ <title>Sheet.114</title>
+ <path d="M0 313.9 L0 379.8 L41.18 379.8 L41.18 313.9 L0 313.9 L0 313.9 Z" class="st4"/>
+ </g>
+ <g id="shape115-333" v:mID="115" v:groupContext="shape" transform="translate(634.615,-169.11)">
+ <title>Sheet.115</title>
+ <path d="M0 313.9 L41.18 313.9 L41.18 379.8 L0 379.8 L0 313.9" class="st5"/>
+ </g>
+ <g id="shape116-336" v:mID="116" v:groupContext="shape" transform="translate(637.526,-219.595)">
+ <title>Sheet.116</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape117-340" v:mID="117" v:groupContext="shape" transform="translate(665.234,-219.595)">
+ <title>Sheet.117</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape118-344" v:mID="118" v:groupContext="shape" transform="translate(665.2,-225.489)">
+ <title>Sheet.118</title>
+ <path d="M0 379.32 L0 379.8 L5.52 379.8 L5.52 379.32 L0 379.32 L0 379.32 Z" class="st19"/>
+ </g>
+ <g id="shape119-346" v:mID="119" v:groupContext="shape" transform="translate(665.234,-207.612)">
+ <title>Sheet.119</title>
+ <desc>7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>7</text> </g>
+ <g id="shape120-350" v:mID="120" v:groupContext="shape" transform="translate(637.286,-169.898)">
+ <title>Sheet.120</title>
+ <desc>5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>5</text> </g>
+ <g id="shape121-354" v:mID="121" v:groupContext="shape" transform="translate(642.809,-169.898)">
+ <title>Sheet.121</title>
+ <desc>-</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="3.49545" cy="373.835" width="7" height="11.9384"/>
+ <path d="M6.99 367.87 L0 367.87 L0 379.8 L6.99 379.8 L6.99 367.87" class="st2"/>
+ <text x="1.84" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>-</text> </g>
+ <g id="shape122-358" v:mID="122" v:groupContext="shape" transform="translate(646.17,-169.898)">
+ <title>Sheet.122</title>
+ <desc>3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>3</text> </g>
+ <g id="shape123-362" v:mID="123" v:groupContext="shape" transform="translate(676.275,-186.725)">
+ <title>Sheet.123</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape124-364" v:mID="124" v:groupContext="shape" transform="translate(676.275,-186.725)">
+ <title>Sheet.124</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape125-367" v:mID="125" v:groupContext="shape" transform="translate(679.141,-237.17)">
+ <title>Sheet.125</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape126-371" v:mID="126" v:groupContext="shape" transform="translate(706.849,-237.17)">
+ <title>Sheet.126</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape127-375" v:mID="127" v:groupContext="shape" transform="translate(678.901,-187.474)">
+ <title>Sheet.127</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape128-379" v:mID="128" v:groupContext="shape" transform="translate(304.943,-21.841)">
+ <title>Sheet.128</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape129-381" v:mID="129" v:groupContext="shape" transform="translate(304.943,-21.841)">
+ <title>Sheet.129</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape130-384" v:mID="130" v:groupContext="shape" transform="translate(307.855,-72.2917)">
+ <title>Sheet.130</title>
+ <desc>64</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>64</text> </g>
+ <g id="shape131-388" v:mID="131" v:groupContext="shape" transform="translate(330.041,-72.2917)">
+ <title>Sheet.131</title>
+ <desc>96</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>96</text> </g>
+ <g id="shape132-392" v:mID="132" v:groupContext="shape" transform="translate(307.616,-22.5952)">
+ <title>Sheet.132</title>
+ <desc>7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>7</text> </g>
+ <g id="shape133-396" v:mID="133" v:groupContext="shape" transform="translate(428.72,-77.4413)">
+ <title>Sheet.133</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape134-398" v:mID="134" v:groupContext="shape" transform="translate(428.72,-77.4413)">
+ <title>Sheet.134</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape135-401" v:mID="135" v:groupContext="shape" transform="translate(431.648,-127.825)">
+ <title>Sheet.135</title>
+ <desc>6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>6</text> </g>
+ <g id="shape136-405" v:mID="136" v:groupContext="shape" transform="translate(453.834,-127.825)">
+ <title>Sheet.136</title>
+ <desc>98</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>98</text> </g>
+ <g id="shape137-409" v:mID="137" v:groupContext="shape" transform="translate(431.409,-78.1289)">
+ <title>Sheet.137</title>
+ <desc>5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>5</text> </g>
+ <g id="shape138-413" v:mID="138" v:groupContext="shape" transform="translate(593.796,-149.818)">
+ <title>Sheet.138</title>
+ <path d="M0 313.9 L0 379.8 L41.18 379.8 L41.18 313.9 L0 313.9 L0 313.9 Z" class="st4"/>
+ </g>
+ <g id="shape139-415" v:mID="139" v:groupContext="shape" transform="translate(593.796,-149.818)">
+ <title>Sheet.139</title>
+ <path d="M0 313.9 L41.18 313.9 L41.18 379.8 L0 379.8 L0 313.9" class="st5"/>
+ </g>
+ <g id="shape140-418" v:mID="140" v:groupContext="shape" transform="translate(596.718,-200.312)">
+ <title>Sheet.140</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape141-422" v:mID="141" v:groupContext="shape" transform="translate(618.904,-200.312)">
+ <title>Sheet.141</title>
+ <desc>99</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>99</text> </g>
+ <g id="shape142-426" v:mID="142" v:groupContext="shape" transform="translate(596.478,-150.615)">
+ <title>Sheet.142</title>
+ <desc>9</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>9</text> </g>
+ <g id="shape143-430" v:mID="143" v:groupContext="shape" transform="translate(387.181,-58.0291)">
+ <title>Sheet.143</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape144-432" v:mID="144" v:groupContext="shape" transform="translate(387.181,-58.0291)">
+ <title>Sheet.144</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape145-435" v:mID="145" v:groupContext="shape" transform="translate(390.133,-108.466)">
+ <title>Sheet.145</title>
+ <desc>7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>7</text> </g>
+ <g id="shape146-439" v:mID="146" v:groupContext="shape" transform="translate(412.318,-108.466)">
+ <title>Sheet.146</title>
+ <desc>97</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>97</text> </g>
+ <g id="shape147-443" v:mID="147" v:groupContext="shape" transform="translate(389.893,-58.7692)">
+ <title>Sheet.147</title>
+ <desc>6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>6</text> </g>
+ <g id="shape148-447" v:mID="148" v:groupContext="shape" transform="translate(31.8163,-277.674)">
+ <title>Sheet.148</title>
+ <path d="M0 362.83 C0 360.95 1.52 359.43 3.41 359.43 L68.51 359.43 C70.4 359.43 71.91 360.95 71.91 362.83 L71.91 376.41
+ C71.91 378.28 70.4 379.8 68.51 379.8 L3.41 379.8 C1.52 379.8 0 378.28 0 376.41 L0 362.83 Z" class="st4"/>
+ </g>
+ <g id="shape149-449" v:mID="149" v:groupContext="shape" transform="translate(31.8163,-277.674)">
+ <title>Sheet.149</title>
+ <path d="M0 362.83 C0 360.95 1.52 359.43 3.41 359.43 L68.51 359.43 C70.4 359.43 71.91 360.95 71.91 362.83 L71.91 376.41
+ C71.91 378.28 70.4 379.8 68.51 379.8 L3.41 379.8 C1.52 379.8 0 378.28 0 376.41 L0 362.83 Z" class="st21"/>
+ </g>
+ <g id="shape150-451" v:mID="150" v:groupContext="shape" transform="translate(36,-278.851)">
+ <title>Sheet.150</title>
+ <desc>Insert key</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="35.613" cy="372.612" width="71.23" height="14.3829"/>
+ <path d="M71.23 365.42 L0 365.42 L0 379.8 L71.23 379.8 L71.23 365.42" class="st2"/>
+ <text x="9.64" y="376.21" class="st22" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Insert key </text> </g>
+ <g id="shape151-455" v:mID="151" v:groupContext="shape" transform="translate(47.7236,-257.004)">
+ <title>Sheet.151</title>
+ <path d="M0 369.44 L9.81 369.44 L9.81 359.07 L29.44 359.07 L29.44 369.44 L39.26 369.44 L19.63 379.8 L0 369.44 Z"
+ class="st23"/>
+ </g>
+ <g id="shape152-457" v:mID="152" v:groupContext="shape" transform="translate(31.8163,-236.094)">
+ <title>Sheet.152</title>
+ <path d="M0 362.73 C0 360.85 1.54 359.31 3.42 359.31 L68.49 359.31 C70.38 359.31 71.91 360.85 71.91 362.73 L71.91 376.39
+ C71.91 378.28 70.38 379.8 68.49 379.8 L3.42 379.8 C1.54 379.8 0 378.28 0 376.39 L0 362.73 Z" class="st4"/>
+ </g>
+ <g id="shape153-459" v:mID="153" v:groupContext="shape" transform="translate(31.8163,-236.094)">
+ <title>Sheet.153</title>
+ <path d="M0 362.73 C0 360.85 1.54 359.31 3.42 359.31 L68.49 359.31 C70.38 359.31 71.91 360.85 71.91 362.73 L71.91 376.39
+ C71.91 378.28 70.38 379.8 68.49 379.8 L3.42 379.8 C1.54 379.8 0 378.28 0 376.39 L0 362.73 Z" class="st21"/>
+ </g>
+ <g id="shape154-461" v:mID="154" v:groupContext="shape" transform="translate(54.6845,-237.332)">
+ <title>Sheet.154</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.8573" cy="372.612" width="33.72" height="14.3829"/>
+ <path d="M33.71 365.42 L0 365.42 L0 379.8 L33.71 379.8 L33.71 365.42" class="st2"/>
+ <text x="3.86" y="376.21" class="st22" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape155-465" v:mID="155" v:groupContext="shape" transform="translate(23.0522,-195.232)">
+ <title>Sheet.155</title>
+ <path d="M0 363.53 C0 361.73 1.46 360.27 3.26 360.27 L87.15 360.27 C88.95 360.27 90.4 361.73 90.4 363.53 L90.4 376.55
+ C90.4 378.35 88.95 379.8 87.15 379.8 L3.26 379.8 C1.46 379.8 0 378.35 0 376.55 L0 363.53 Z" class="st4"/>
+ </g>
+ <g id="shape156-467" v:mID="156" v:groupContext="shape" transform="translate(23.0522,-195.232)">
+ <title>Sheet.156</title>
+ <path d="M0 363.53 C0 361.73 1.46 360.27 3.26 360.27 L87.15 360.27 C88.95 360.27 90.4 361.73 90.4 363.53 L90.4 376.55
+ C90.4 378.35 88.95 379.8 87.15 379.8 L3.26 379.8 C1.46 379.8 0 378.35 0 376.55 L0 363.53 Z" class="st21"/>
+ </g>
+ <g id="shape157-469" v:mID="157" v:groupContext="shape" transform="translate(27,-196.017)">
+ <title>Sheet.157</title>
+ <desc>0x0102ABCD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.6644" cy="372.612" width="87.33" height="14.3829"/>
+ <path d="M87.33 365.42 L0 365.42 L0 379.8 L87.33 379.8 L87.33 365.42" class="st2"/>
+ <text x="7.36" y="376.21" class="st22" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102ABCD</text> </g>
+ <g id="shape158-473" v:mID="158" v:groupContext="shape" transform="translate(47.7236,-214.824)">
+ <title>Sheet.158</title>
+ <path d="M0 369.5 L9.81 369.5 L9.81 359.19 L29.44 359.19 L29.44 369.5 L39.26 369.5 L19.63 379.8 L0 369.5 Z"
+ class="st23"/>
+ </g>
+ <g id="shape159-475" v:mID="159" v:groupContext="shape" transform="translate(49.8539,-181.212)">
+ <title>Sheet.159</title>
+ <path d="M11.89 368.42 C11.89 371.57 11.47 374.11 10.94 374.11 L6.9 374.11 C6.37 374.11 5.94 376.67 5.94 379.8 C5.94
+ 376.67 5.52 374.11 5 374.11 L0.95 374.11 C0.43 374.11 0 371.57 0 368.42" class="st24"/>
+ </g>
+ <g id="shape160-478" v:mID="160" v:groupContext="shape" transform="translate(64.2606,-180.973)">
+ <title>Sheet.160</title>
+ <path d="M9.54 368.54 C9.54 371.66 9.21 374.17 8.79 374.17 L5.53 374.17 C5.11 374.17 4.77 376.7 4.77 379.8 C4.77 376.7
+ 4.43 374.17 4.02 374.17 L0.76 374.17 C0.34 374.17 0 371.66 0 368.54" class="st24"/>
+ </g>
+ <g id="shape161-481" v:mID="161" v:groupContext="shape" transform="translate(18.19,-60.9649)">
+ <title>Sheet.161</title>
+ <path d="M0 354.74 C0 351.97 2.25 349.73 5.03 349.73 L10.77 349.73 L30.27 267.14 L26.92 349.73 L59.58 349.73 C62.35 349.73
+ 64.59 351.97 64.59 354.74 L64.59 354.74 L64.59 362.26 L64.59 374.8 C64.59 377.57 62.35 379.8 59.58 379.8
+ L26.92 379.8 L10.77 379.8 L10.77 379.8 L5.03 379.8 C2.25 379.8 0 377.57 0 374.8 L0 362.26 L0 354.74 L0 354.74
+ Z" class="st23"/>
+ </g>
+ <g id="shape162-483" v:mID="162" v:groupContext="shape" transform="translate(28.141,-66.9569)">
+ <title>Sheet.162</title>
+ <desc>chunk id</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="27.5794" cy="372.612" width="55.16" height="14.3829"/>
+ <path d="M55.16 365.42 L0 365.42 L0 379.8 L55.16 379.8 L55.16 365.42" class="st2"/>
+ <text x="5.26" y="376.21" class="st25" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>chunk id</text> </g>
+ <g id="shape163-487" v:mID="163" v:groupContext="shape" transform="translate(50.8451,-112.132)">
+ <title>Sheet.163</title>
+ <path d="M0 354.64 C0 351.87 2.27 349.61 5.04 349.61 L10.74 349.61 L16.27 313.66 L26.86 349.61 L59.43 349.61 C62.22 349.61
+ 64.47 351.87 64.47 354.64 L64.47 354.64 L64.47 362.19 L64.47 374.77 C64.47 377.56 62.22 379.8 59.43 379.8
+ L26.86 379.8 L10.74 379.8 L10.74 379.8 L5.04 379.8 C2.27 379.8 0 377.56 0 374.77 L0 362.19 L0 354.64 L0
+ 354.64 Z" class="st23"/>
+ </g>
+ <g id="shape164-489" v:mID="164" v:groupContext="shape" transform="translate(68.8168,-118.181)">
+ <title>Sheet.164</title>
+ <desc>bin id</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="18.3881" cy="372.612" width="36.78" height="14.3829"/>
+ <path d="M36.78 365.42 L0 365.42 L0 379.8 L36.78 379.8 L36.78 365.42" class="st2"/>
+ <text x="4.06" y="376.21" class="st25" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bin id</text> </g>
+ <g id="shape165-493" v:mID="165" v:groupContext="shape" transform="translate(113.454,-225.085)">
+ <title>Sheet.165</title>
+ <path d="M0.01 375.68 L13.23 375.73 L13.22 377.77 L0 377.72 L0.01 375.68 L0.01 375.68 ZM12.22 373.69 L18.33 376.76 L12.2
+ 379.8 L12.22 373.69 L12.22 373.69 Z" class="st26"/>
+ </g>
+ <g id="shape166-495" v:mID="166" v:groupContext="shape" transform="translate(200.975,-280.969)">
+ <title>Sheet.166</title>
+ <path d="M0 375.73 L20.11 375.73 L20.11 377.77 L0 377.77 L0 375.73 L0 375.73 ZM19.09 373.69 L25.21 376.75 L19.09 379.8
+ L19.09 373.69 L19.09 373.69 Z" class="st26"/>
+ </g>
+ <g id="shape167-497" v:mID="167" v:groupContext="shape" transform="translate(275.739,-179.745)">
+ <title>Sheet.167</title>
+ <path d="M0.81 274.59 L231.38 376.48 L230.54 378.37 L0 276.48 L0.81 274.59 L0.81 274.59 ZM231.26 374.2 L235.64 379.47
+ L228.8 379.8 L231.26 374.2 L231.26 374.2 Z" class="st27"/>
+ </g>
+ <g id="shape168-499" v:mID="168" v:groupContext="shape" transform="translate(521.823,-96.8834)">
+ <title>Sheet.168</title>
+ <path d="M127.17 309.02 L127.17 378.79 C127.17 379.35 126.72 379.8 126.15 379.8 L3.06 379.8 C2.52 379.8 2.04 379.35 2.04
+ 378.79 L2.04 369.59 L4.08 369.59 L4.08 378.79 L3.06 377.77 L126.15 377.77 L125.13 378.79 L125.13 309.02
+ L127.17 309.02 ZM0 370.61 L3.06 364.5 L6.12 370.61 L0 370.61 Z" class="st28"/>
+ </g>
+ <g id="shape169-501" v:mID="169" v:groupContext="shape" transform="translate(478.603,-39.7553)">
+ <title>Sheet.169</title>
+ <path d="M0 347.57 C0 344.01 2.91 341.1 6.48 341.1 L237.86 341.1 C241.43 341.1 244.31 344.01 244.31 347.57 L244.31 373.36
+ C244.31 376.93 241.43 379.8 237.86 379.8 L6.48 379.8 C2.91 379.8 0 376.93 0 373.36 L0 347.57 Z"
+ class="st23"/>
+ </g>
+ <g id="shape170-503" v:mID="170" v:groupContext="shape" transform="translate(487.717,-45.5378)">
+ <title>Sheet.170</title>
+ <desc>Move bin from group 1 to 4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="126.387" cy="369.018" width="252.78" height="21.5726"/>
+ <path d="M252.77 358.23 L0 358.23 L0 379.8 L252.77 379.8 L252.77 358.23" class="st2"/>
+ <text x="18.98" y="374.41" class="st29" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Move bin from group 1 to 4</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i2.svg b/doc/guides/prog_guide/img/efd_i2.svg
new file mode 100644
index 0000000..a5f43f9
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i2.svg
@@ -0,0 +1,280 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i2.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="2.85156in" height="2.98777in"
+ viewBox="0 0 205.313 215.12" xml:space="preserve" color-interpolation-filters="sRGB" class="st18">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#deebf6;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st5 {fill:#ff0000;stroke:#c7c8c8;stroke-width:0.25}
+ .st6 {fill:none;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st7 {fill:none;stroke:#0070c0;stroke-width:1.5}
+ .st8 {marker-end:url(#mrkr5-91);stroke:#0070c0;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st9 {fill:#0070c0;fill-opacity:1;stroke:#0070c0;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st10 {fill:none;stroke:none;stroke-width:0.25}
+ .st11 {fill:#ff0000;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st12 {font-size:1em}
+ .st13 {marker-end:url(#mrkr5-101);stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st14 {fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st15 {fill:#5b9bd5;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st16 {marker-end:url(#mrkr5-110);stroke:#41719c;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st17 {fill:#41719c;fill-opacity:1;stroke:#41719c;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st18 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-91" class="st9" v:arrowType="5" v:arrowSize="2" v:setback="4.45" refX="-4.45" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ <marker id="mrkr5-101" class="st14" v:arrowType="5" v:arrowSize="2" v:setback="6.16" refX="-6.16" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ <marker id="mrkr5-110" class="st17" v:arrowType="5" v:arrowSize="2" v:setback="6.16" refX="-6.16" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(24.4044,-42.7174)">
+ <title>Circle</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow2-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 138.62 A76.5 76.5 0 0 1 153 138.62 A76.5 76.5 0 1 1 0 138.62 Z" class="st2"/>
+ </g>
+ <path d="M0 138.62 A76.5 76.5 0 0 1 153 138.62 A76.5 76.5 0 1 1 0 138.62 Z" class="st3"/>
+ </g>
+ <g id="shape3-6" v:mID="3" v:groupContext="shape" transform="translate(24.4044,-144.53)">
+ <title>Circle.3</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow3-7" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape4-11" v:mID="4" v:groupContext="shape" transform="translate(21.0294,-102.342)">
+ <title>Circle.4</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow4-12" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape5-16" v:mID="5" v:groupContext="shape" transform="translate(69.4044,-183.342)">
+ <title>Circle.5</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow5-17" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape6-21" v:mID="6" v:groupContext="shape" transform="translate(117.217,-183.342)">
+ <title>Circle.6</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow6-22" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st5"/>
+ </g>
+ <g id="shape7-26" v:mID="7" v:groupContext="shape" transform="translate(171.217,-104.03)">
+ <title>Circle.7</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow7-27" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st5"/>
+ </g>
+ <g id="shape8-31" v:mID="8" v:groupContext="shape" transform="translate(109.904,-38.2174)">
+ <title>Circle.8</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow8-32" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st5"/>
+ </g>
+ <g id="shape9-36" v:mID="9" v:groupContext="shape" transform="translate(21.0294,-124.842)">
+ <title>Circle.9</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow9-37" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st5"/>
+ </g>
+ <g id="shape10-41" v:mID="10" v:groupContext="shape" transform="translate(147.029,-168.717)">
+ <title>Circle.10</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow10-42" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape11-46" v:mID="11" v:groupContext="shape" transform="translate(138.029,-48.3424)">
+ <title>Circle.11</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow11-47" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape12-51" v:mID="12" v:groupContext="shape" transform="translate(160.529,-74.2174)">
+ <title>Circle.12</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow12-52" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape13-56" v:mID="13" v:groupContext="shape" transform="translate(40.7169,-57.3424)">
+ <title>Circle.13</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow13-57" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape14-61" v:mID="14" v:groupContext="shape" transform="translate(42.4044,-168.717)">
+ <title>Circle.14</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow14-62" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape15-66" v:mID="15" v:groupContext="shape" transform="translate(66.0294,-42.7174)">
+ <title>Circle.15</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow15-67" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape16-71" v:mID="16" v:groupContext="shape" transform="translate(25.5294,-79.8424)">
+ <title>Circle.16</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow16-72" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape17-76" v:mID="17" v:groupContext="shape" transform="translate(165.029,-143.405)">
+ <title>Circle.17</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow17-77" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape18-81" v:mID="18" v:groupContext="shape" transform="translate(276.618,4.50201) rotate(45)">
+ <title>Ellipse</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow18-82" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,1.63935,1.1506)" class="st1">
+ <path d="M0 187.01 A14.7383 28.1086 0 1 1 29.48 187.01 A14.7383 28.1086 0 1 1 0 187.01 Z" class="st6"/>
+ </g>
+ <path d="M0 187.01 A14.7383 28.1086 0 1 1 29.48 187.01 A14.7383 28.1086 0 1 1 0 187.01 Z" class="st7"/>
+ </g>
+ <g id="shape19-86" v:mID="19" v:groupContext="shape" transform="translate(251.273,355.436) rotate(156.038)">
+ <title>Sheet.19</title>
+ <path d="M-0 215.12 A73.4538 31.2572 85.43 0 1 40.92 208.96 L41.1 209.27" class="st8"/>
+ </g>
+ <g id="shape20-92" v:mID="20" v:groupContext="shape" transform="translate(62.705,-78.7174)">
+ <title>Sheet.20</title>
+ <desc>Target Hashed Value</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="42.6994" cy="203.87" width="85.4" height="22.5"/>
+ <rect x="0" y="192.62" width="85.3987" height="22.5" class="st10"/>
+ <text x="6.73" y="200.27" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target Hashed <tspan
+ x="28.48" dy="1.2em" class="st12">Value</tspan></text> </g>
+ <g id="shape21-96" v:mID="21" v:groupContext="shape" transform="translate(314.101,88.728) rotate(75.9638)">
+ <title>Sheet.21</title>
+ <path d="M0 215.12 L16.92 215.12" class="st13"/>
+ </g>
+ <g id="shape23-102" v:mID="23" v:groupContext="shape" transform="translate(60.4044,-138.342)">
+ <title>Sheet.23</title>
+ <desc>Keys</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="24.75" cy="203.87" width="49.5" height="22.5"/>
+ <rect x="0" y="192.62" width="49.5" height="22.5" class="st10"/>
+ <text x="13.21" y="207.47" class="st15" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Keys</text> </g>
+ <g id="shape24-105" v:mID="24" v:groupContext="shape" transform="translate(-125.293,114.034) rotate(-104.574)">
+ <title>Sheet.24</title>
+ <path d="M0 215.12 L22.9 215.12" class="st16"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i3.svg b/doc/guides/prog_guide/img/efd_i3.svg
new file mode 100644
index 0000000..ae22903
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i3.svg
@@ -0,0 +1,634 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i3.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="6.56036in" height="5.44284in"
+ viewBox="0 0 472.346 391.884" xml:space="preserve" color-interpolation-filters="sRGB" class="st22">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false"/>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st5 {marker-end:url(#mrkr5-24);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st6 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st7 {fill:none;stroke:#2e75b5;stroke-width:1}
+ .st8 {fill:#5b9bd5;font-family:Calibri;font-size:1.00001em}
+ .st9 {font-size:1em}
+ .st10 {fill:none;stroke:none;stroke-width:1}
+ .st11 {fill:#feffff;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st12 {fill:#5b9bd5;fill-opacity:0.25;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.25}
+ .st13 {fill:#4f87bb;stroke:#40709c;stroke-width:0.75}
+ .st14 {fill:#feffff;font-family:Calibri;font-size:0.75em}
+ .st15 {fill:none;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st16 {fill:none;stroke:#2e75b5;stroke-width:2.25}
+ .st17 {stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st18 {fill:#305497;stroke:#2e75b5;stroke-width:1}
+ .st19 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:none}
+ .st20 {fill:#92d050;fill-opacity:0.3;stroke:none;stroke-width:0.25}
+ .st21 {fill:#feffff;font-family:Calibri;font-size:1.16666em}
+ .st22 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-24" class="st6" v:arrowType="5" v:arrowSize="2" v:setback="6.16" refX="-6.16" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <v:layer v:name="Connector" v:index="0"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(111.25,-354.482)">
+ <title>Rectangle</title>
+ <desc>Packet Header</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="42.75" cy="382.884" width="85.5" height="18"/>
+ <g id="shadow2-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="85.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="85.5" height="18" class="st3"/>
+ <text x="13.24" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Packet Header</text> </g>
+ <g id="shape3-7" v:mID="3" v:groupContext="shape" transform="translate(192.25,-354.482)">
+ <title>Rectangle.3</title>
+ <desc>Payload</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="382.884" width="108" height="18"/>
+ <g id="shadow3-8" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="108" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="108" height="18" class="st3"/>
+ <text x="37.95" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Payload</text> </g>
+ <g id="shape4-13" v:mID="4" v:groupContext="shape" transform="translate(136,-311.232)">
+ <title>Rectangle.4</title>
+ <desc>Flow Key</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="382.884" width="54" height="18"/>
+ <g id="shadow4-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="54" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="54" height="18" class="st3"/>
+ <text x="8.87" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Flow Key</text> </g>
+ <g id="shape5-19" v:mID="5" v:groupContext="shape" transform="translate(465.501,-160.057) rotate(59.7436)">
+ <title>Sheet.5</title>
+ <path d="M0 391.88 L25.1 391.88" class="st5"/>
+ </g>
+ <g id="shape8-25" v:mID="8" v:groupContext="shape" transform="translate(219.25,-320.169)">
+ <title>Sheet.8</title>
+ <desc>Fields of the packet are used to form a flow Key</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="377.822" width="135" height="28.125"/>
+ <rect x="0" y="363.759" width="135" height="28.125" class="st7"/>
+ <text x="10.7" y="374.22" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Fields of the packet are <tspan
+ x="9.67" dy="1.2em" class="st9">used to form a flow Key</tspan></text> </g>
+ <g id="group13-29" transform="translate(120.25,-266.897)" v:mID="13" v:groupContext="group">
+ <title>Sheet.13</title>
+ <g id="shape11-30" v:mID="11" v:groupContext="shape" transform="translate(85.5,751.143) rotate(180)">
+ <title>Trapezoid</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow11-31" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,-0.345598,-1.97279)" class="st1">
+ <path d="M0 391.88 L85.5 391.88 L60.19 359.26 L25.31 359.26 L0 391.88 Z" class="st2"/>
+ </g>
+ <path d="M0 391.88 L85.5 391.88 L60.19 359.26 L25.31 359.26 L0 391.88 Z" class="st3"/>
+ </g>
+ <g id="shape12-35" v:mID="12" v:groupContext="shape" transform="translate(13.5,-6.525)">
+ <title>Sheet.12</title>
+ <desc>H(..)</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="381.689" width="54" height="20.3906"/>
+ <rect x="0" y="371.494" width="54" height="20.3906" class="st10"/>
+ <text x="16.27" y="385.29" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H(..)</text> </g>
+ </g>
+ <g id="shape14-38" v:mID="14" v:groupContext="shape" transform="translate(-229.872,96.3648) rotate(-90.0429)">
+ <title>Simple Arrow</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ArrowType" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <g id="shadow14-39" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,-1.97305,0.344122)" class="st1">
+ <path d="M0 391.88 L10.18 387.38 L10.18 389.63 L16.71 389.63 L16.71 391.88 L16.71 394.13 L10.18 394.13 L10.18 396.38
+ L0 391.88 Z" class="st12"/>
+ </g>
+ <path d="M0 391.88 L10.18 387.38 L10.18 389.63 L16.71 389.63 L16.71 391.88 L16.71 394.13 L10.18 394.13 L10.18 396.38
+ L0 391.88 Z" class="st13"/>
+ </g>
+ <g id="shape15-43" v:mID="15" v:groupContext="shape" transform="translate(212.5,-271.46)">
+ <title>Sheet.15</title>
+ <desc>Hash function is used to create a flow table index</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="377.822" width="135" height="28.125"/>
+ <rect x="0" y="363.759" width="135" height="28.125" class="st7"/>
+ <text x="9.05" y="374.22" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Hash function is used to <tspan
+ x="7.39" dy="1.2em" class="st9">create a flow table index</tspan></text> </g>
+ <g id="shape58-47" v:mID="58" v:groupContext="shape" transform="translate(199,-221.397)">
+ <title>Rectangle.58</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow58-48" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape59-53" v:mID="59" v:groupContext="shape" transform="translate(232.75,-221.397)">
+ <title>Rectangle.59</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow59-54" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape60-59" v:mID="60" v:groupContext="shape" transform="translate(280,-221.397)">
+ <title>Rectangle.60</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow60-60" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape61-65" v:mID="61" v:groupContext="shape" transform="translate(313.75,-221.397)">
+ <title>Rectangle.61</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow61-66" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape62-71" v:mID="62" v:groupContext="shape" transform="translate(361,-221.397)">
+ <title>Rectangle.62</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow62-72" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape63-76" v:mID="63" v:groupContext="shape" transform="translate(394.75,-221.397)">
+ <title>Rectangle.63</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow63-77" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape64-81" v:mID="64" v:groupContext="shape" transform="translate(199,-198.897)">
+ <title>Rectangle.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow64-82" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape65-86" v:mID="65" v:groupContext="shape" transform="translate(232.75,-198.897)">
+ <title>Rectangle.65</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow65-87" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape66-91" v:mID="66" v:groupContext="shape" transform="translate(280,-198.897)">
+ <title>Rectangle.66</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow66-92" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape67-96" v:mID="67" v:groupContext="shape" transform="translate(313.75,-198.897)">
+ <title>Rectangle.67</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow67-97" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape68-101" v:mID="68" v:groupContext="shape" transform="translate(361,-198.897)">
+ <title>Rectangle.68</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow68-102" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape69-106" v:mID="69" v:groupContext="shape" transform="translate(394.75,-198.897)">
+ <title>Rectangle.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow69-107" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape70-111" v:mID="70" v:groupContext="shape" transform="translate(199,-162.897)">
+ <title>Rectangle.70</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow70-112" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape71-117" v:mID="71" v:groupContext="shape" transform="translate(232.75,-162.897)">
+ <title>Rectangle.71</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow71-118" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape72-123" v:mID="72" v:groupContext="shape" transform="translate(280,-162.897)">
+ <title>Rectangle.72</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow72-124" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape73-129" v:mID="73" v:groupContext="shape" transform="translate(313.75,-162.897)">
+ <title>Rectangle.73</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow73-130" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape74-135" v:mID="74" v:groupContext="shape" transform="translate(361,-162.897)">
+ <title>Rectangle.74</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow74-136" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape75-141" v:mID="75" v:groupContext="shape" transform="translate(394.75,-162.897)">
+ <title>Rectangle.75</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow75-142" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape76-147" v:mID="76" v:groupContext="shape" transform="translate(199,-126.397)">
+ <title>Rectangle.76</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow76-148" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape77-152" v:mID="77" v:groupContext="shape" transform="translate(232.75,-126.397)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow77-153" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape78-157" v:mID="78" v:groupContext="shape" transform="translate(280,-126.397)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow78-158" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape79-162" v:mID="79" v:groupContext="shape" transform="translate(313.75,-126.397)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow79-163" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape80-167" v:mID="80" v:groupContext="shape" transform="translate(361,-126.397)">
+ <title>Rectangle.80</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow80-168" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="385.58" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape81-173" v:mID="81" v:groupContext="shape" transform="translate(394.75,-126.397)">
+ <title>Rectangle.81</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow81-174" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="385.58" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape82-179" v:mID="82" v:groupContext="shape" transform="translate(196.75,-117.397)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow82-180" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="256.384" width="245.25" height="135.5" class="st15"/>
+ </g>
+ <rect x="0" y="256.384" width="245.25" height="135.5" class="st16"/>
+ </g>
+ <g id="shape83-184" v:mID="83" v:groupContext="shape" transform="translate(554.884,123.862) rotate(90)">
+ <title>Sheet.83</title>
+ <path d="M0 391.88 L99 391.88" class="st17"/>
+ </g>
+ <g id="shape84-187" v:mID="84" v:groupContext="shape" transform="translate(208,-248.397)">
+ <title>Sheet.84</title>
+ <desc>Load Balancing Flow Table</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="91.75" cy="386.259" width="183.5" height="11.25"/>
+ <rect x="0" y="380.634" width="183.5" height="11.25" class="st18"/>
+ <text x="26.14" y="389.86" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Load Balancing Flow Table</text> </g>
+ <g id="shape85-190" v:mID="85" v:groupContext="shape" transform="translate(190,-157.835)">
+ <title>Rectangle.85</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow85-191" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="363.759" width="261" height="28.125" class="st19"/>
+ </g>
+ <rect x="0" y="363.759" width="261" height="28.125" class="st20"/>
+ </g>
+ <g id="shape86-195" v:mID="86" v:groupContext="shape" transform="translate(163,-169.022)">
+ <title>Sheet.86</title>
+ <path d="M0 391.88 L18.76 391.88" class="st5"/>
+ </g>
+ <g id="shape87-200" v:mID="87" v:groupContext="shape" transform="translate(19,-198.107)">
+ <title>Sheet.87</title>
+ <desc>Hash value used to index Flow table</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="377.822" width="135" height="28.125"/>
+ <rect x="0" y="363.759" width="135" height="28.125" class="st7"/>
+ <text x="6.79" y="374.22" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Hash value used to index <tspan
+ x="42.16" dy="1.2em" class="st9">Flow table</tspan></text> </g>
+ <g id="shape88-204" v:mID="88" v:groupContext="shape" transform="translate(551.381,21.2928) rotate(87.9001)">
+ <title>Sheet.88</title>
+ <path d="M0 391.88 L20.86 391.88" class="st5"/>
+ </g>
+ <g id="shape89-209" v:mID="89" v:groupContext="shape" transform="translate(494.785,297.309) rotate(131.987)">
+ <title>Sheet.89</title>
+ <path d="M0 391.88 L30.84 391.88" class="st5"/>
+ </g>
+ <g id="shape90-214" v:mID="90" v:groupContext="shape" transform="translate(228.25,-92.5847)">
+ <title>Rectangle.90</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow90-215" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape91-220" v:mID="91" v:groupContext="shape" transform="translate(340.75,-92.5847)">
+ <title>Rectangle.91</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow91-221" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="group96-226" transform="translate(253,-51.4597)" v:mID="96" v:groupContext="group">
+ <title>Sheet.96</title>
+ <g id="shape97-227" v:mID="97" v:groupContext="shape" transform="translate(85.5,751.143) rotate(180)">
+ <title>Trapezoid</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow97-228" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,-0.345598,-1.97279)" class="st1">
+ <path d="M0 391.88 L85.5 391.88 L60.19 359.26 L25.31 359.26 L0 391.88 Z" class="st2"/>
+ </g>
+ <path d="M0 391.88 L85.5 391.88 L60.19 359.26 L25.31 359.26 L0 391.88 Z" class="st3"/>
+ </g>
+ <g id="shape98-232" v:mID="98" v:groupContext="shape" transform="translate(13.5,-6.525)">
+ <title>Sheet.98</title>
+ <desc>Match</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="381.689" width="54" height="20.3906"/>
+ <rect x="0" y="371.494" width="54" height="20.3906" class="st10"/>
+ <text x="10.98" y="385.29" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Match</text> </g>
+ </g>
+ <g id="shape99-235" v:mID="99" v:groupContext="shape" transform="translate(532.137,0.00916548) rotate(54.6508)">
+ <title>Sheet.99</title>
+ <path d="M0 391.88 L93.23 391.88" class="st5"/>
+ </g>
+ <g id="shape100-240" v:mID="100" v:groupContext="shape" transform="translate(683.134,224.487) rotate(90)">
+ <title>Sheet.100</title>
+ <path d="M0 391.88 L77.15 391.88" class="st5"/>
+ </g>
+ <g id="shape101-245" v:mID="101" v:groupContext="shape" transform="translate(692.213,476.024) rotate(129.078)">
+ <title>Sheet.101</title>
+ <path d="M0 391.88 L95.37 391.88" class="st5"/>
+ </g>
+ <g id="shape102-250" v:mID="102" v:groupContext="shape" transform="translate(293.5,-97.0847)">
+ <title>Rectangle.102</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow102-251" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape103-256" v:mID="103" v:groupContext="shape" transform="translate(169.75,-55.9597)">
+ <title>Rectangle.103</title>
+ <desc>Flow Key</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="382.884" width="54" height="18"/>
+ <g id="shadow103-257" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="54" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="54" height="18" class="st3"/>
+ <text x="8.87" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Flow Key</text> </g>
+ <g id="shape104-262" v:mID="104" v:groupContext="shape" transform="translate(226,-64.9597)">
+ <title>Sheet.104</title>
+ <path d="M0 391.88 L34.34 391.88" class="st5"/>
+ </g>
+ <g id="shape105-267" v:mID="105" v:groupContext="shape" transform="translate(54,-82.4597)">
+ <title>Sheet.105</title>
+ <desc>Retrieved keys are matched with input key</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="377.822" width="135" height="28.125"/>
+ <rect x="0" y="363.759" width="135" height="28.125" class="st7"/>
+ <text x="22.51" y="374.22" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Retrieved keys are <tspan
+ x="9.83" dy="1.2em" class="st9">matched with input key</tspan></text> </g>
+ <g id="shape106-271" v:mID="106" v:groupContext="shape" transform="translate(271,-23.9597)">
+ <title>Rectangle.106</title>
+ <desc>Action</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="382.884" width="54" height="18"/>
+ <g id="shadow106-272" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="54" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="54" height="18" class="st3"/>
+ <text x="8.67" y="387.08" class="st21" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action</text> </g>
+ <g id="shape111-277" v:mID="111" v:groupContext="shape" transform="translate(-94.8716,350.902) rotate(-90.0429)">
+ <title>Simple Arrow.111</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ArrowType" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <g id="shadow111-278" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,-1.97305,0.344122)" class="st1">
+ <path d="M0 391.88 L10.18 387.38 L10.18 389.63 L16.71 389.63 L16.71 391.88 L16.71 394.13 L10.18 394.13 L10.18 396.38
+ L0 391.88 Z" class="st12"/>
+ </g>
+ <path d="M0 391.88 L10.18 387.38 L10.18 389.63 L16.71 389.63 L16.71 391.88 L16.71 394.13 L10.18 394.13 L10.18 396.38
+ L0 391.88 Z" class="st13"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i4.svg b/doc/guides/prog_guide/img/efd_i4.svg
new file mode 100644
index 0000000..5be5ccd
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i4.svg
@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i4.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="2.78993in" height="1.78151in"
+ viewBox="0 0 200.875 128.269" xml:space="preserve" color-interpolation-filters="sRGB" class="st19">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:none;stroke:none;stroke-width:0.25}
+ .st2 {fill:#5b9bd5;font-family:Calibri;font-size:0.75em}
+ .st3 {font-size:1em}
+ .st4 {fill:#5b9bd5;font-family:Calibri;font-size:0.75em;font-weight:bold}
+ .st5 {fill:#deebf6;stroke:none;stroke-width:0.25}
+ .st6 {stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st7 {stroke:#5b9bd5;stroke-dasharray:0.75,1.5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st8 {fill:#ff0000;font-size:1em}
+ .st9 {baseline-shift:-28.8834%;font-size:0.577667em}
+ .st10 {fill:#ff0000;font-family:Calibri;font-size:0.75em}
+ .st11 {fill:#5b9bd5;font-size:1em}
+ .st12 {visibility:visible}
+ .st13 {fill:#5b9bd5;fill-opacity:0.25;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.25}
+ .st14 {fill:url(#grad0-73);stroke:#40709c;stroke-width:0.75}
+ .st15 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st16 {fill:#00fefe;font-size:1em}
+ .st17 {fill:#00b050}
+ .st18 {stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st19 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Patterns_And_Gradients">
+ <linearGradient id="grad0-73" x1="0" y1="0" x2="1" y2="0" gradientTransform="rotate(250 0.5 0.5)">
+ <stop offset="0" stop-color="#4f87bb" stop-opacity="1"/>
+ <stop offset="0.48" stop-color="#4f87bb" stop-opacity="1"/>
+ <stop offset="0.82" stop-color="#5b9bd5" stop-opacity="1"/>
+ </linearGradient>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(18.25,-59.3478)">
+ <title>Sheet.2</title>
+ <desc>Key 1 Key 2 ... Key 28</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="18" cy="121.519" width="36" height="13.5"/>
+ <rect x="0" y="114.769" width="36" height="13.5" class="st1"/>
+ <text x="8.09" y="108.02" class="st2" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1<v:newlineChar/><tspan
+ x="8.09" dy="1.2em" class="st3">Key </tspan>2<v:newlineChar/><tspan x="14.59" dy="1.2em" class="st3">...<v:newlineChar/></tspan><tspan
+ x="5.81" dy="1.2em" class="st3">Key </tspan>28</text> </g>
+ <g id="shape9-7" v:mID="9" v:groupContext="shape" transform="translate(52,-91.9728)">
+ <title>Sheet.9</title>
+ <desc>Target Value</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="17.4375" cy="122.644" width="34.88" height="11.25"/>
+ <rect x="0" y="117.019" width="34.875" height="11.25" class="st1"/>
+ <text x="5.43" y="119.94" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target <tspan x="6.77"
+ dy="1.2em" class="st3">Value</tspan></text> </g>
+ <g id="shape11-11" v:mID="11" v:groupContext="shape" transform="translate(52,-42.4728)">
+ <title>Sheet.11</title>
+ <desc>0 1 0</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="17.4375" cy="105.769" width="34.88" height="45"/>
+ <rect x="0" y="83.2689" width="34.875" height="45" class="st5"/>
+ <text x="15.16" y="92.27" class="st2" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0<v:newlineChar/><tspan
+ x="15.16" dy="1.2em" class="st3">1<v:newlineChar/><v:newlineChar/></tspan><tspan x="15.16" dy="2.4em"
+ class="st3">0</tspan></text> </g>
+ <g id="shape8-16" v:mID="8" v:groupContext="shape" transform="translate(180.269,21.6711) rotate(90)">
+ <title>Sheet.8</title>
+ <path d="M0 128.27 L69.75 128.27" class="st6"/>
+ </g>
+ <g id="shape10-19" v:mID="10" v:groupContext="shape" transform="translate(215.144,21.6711) rotate(90)">
+ <title>Sheet.10</title>
+ <path d="M0 128.27 L69.75 128.27" class="st6"/>
+ </g>
+ <g id="shape4-22" v:mID="4" v:groupContext="shape" transform="translate(22.75,-77.3478)">
+ <title>Sheet.4</title>
+ <path d="M0 128.27 L157.5 128.27" class="st7"/>
+ </g>
+ <g id="shape5-25" v:mID="5" v:groupContext="shape" transform="translate(23.875,-66.0978)">
+ <title>Sheet.5</title>
+ <path d="M0 128.27 L158.62 128.27" class="st7"/>
+ </g>
+ <g id="shape6-28" v:mID="6" v:groupContext="shape" transform="translate(22.75,-54.8478)">
+ <title>Sheet.6</title>
+ <path d="M0 128.27 L159.75 128.27" class="st7"/>
+ </g>
+ <g id="shape7-31" v:mID="7" v:groupContext="shape" transform="translate(22.75,-87.4728)">
+ <title>Sheet.7</title>
+ <path d="M0 128.27 L155.25 128.27" class="st6"/>
+ </g>
+ <g id="shape12-34" v:mID="12" v:groupContext="shape" transform="translate(91.9375,-42.4728)">
+ <title>Sheet.12</title>
+ <desc>0 0 0</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="8.4375" cy="105.769" width="16.88" height="45"/>
+ <rect x="0" y="83.2689" width="16.875" height="45" class="st1"/>
+ <text x="6.16" y="92.27" class="st2" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0<v:newlineChar/><tspan
+ x="6.16" dy="1.2em" class="st8">0<v:newlineChar/><v:newlineChar/></tspan><tspan x="6.16" dy="2.4em"
+ class="st3">0</tspan></text> </g>
+ <g id="shape26-39" v:mID="26" v:groupContext="shape" transform="translate(86.875,-88.5978)">
+ <title>Sheet.26</title>
+ <desc>H1(x)</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="14.0625" cy="122.644" width="28.13" height="11.25"/>
+ <rect x="0" y="117.019" width="28.125" height="11.25" class="st1"/>
+ <text x="5.03" y="125.34" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st9" v:baseFontSize="8">1</tspan><tspan dy="0.164em" class="st3">(</tspan>x)</text> </g>
+ <g id="shape27-44" v:mID="27" v:groupContext="shape" transform="translate(115,-42.4728)">
+ <title>Sheet.27</title>
+ <desc>1 1 0</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="8.4375" cy="105.769" width="16.88" height="45"/>
+ <rect x="0" y="83.2689" width="16.875" height="45" class="st1"/>
+ <text x="6.16" y="92.27" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1<v:newlineChar/><tspan
+ x="6.16" dy="1.2em" class="st11">1<v:newlineChar/><v:newlineChar/></tspan><tspan x="6.16" dy="2.4em"
+ class="st11">0</tspan></text> </g>
+ <g id="shape28-49" v:mID="28" v:groupContext="shape" transform="translate(109.938,-88.5978)">
+ <title>Sheet.28</title>
+ <desc>H2(x)</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="14.0625" cy="122.644" width="28.13" height="11.25"/>
+ <rect x="0" y="117.019" width="28.125" height="11.25" class="st1"/>
+ <text x="5.03" y="125.34" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st9" v:baseFontSize="8">2</tspan><tspan dy="0.164em" class="st3">(</tspan>x)</text> </g>
+ <g id="shape29-54" v:mID="29" v:groupContext="shape" transform="translate(155.5,-42.4728)">
+ <title>Sheet.29</title>
+ <desc>0 1 0</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="8.4375" cy="105.769" width="16.88" height="45"/>
+ <rect x="0" y="83.2689" width="16.875" height="45" class="st1"/>
+ <text x="6.16" y="92.27" class="st2" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0<v:newlineChar/><tspan
+ x="6.16" dy="1.2em" class="st3">1<v:newlineChar/><v:newlineChar/></tspan><tspan x="6.16" dy="2.4em"
+ class="st3">0</tspan></text> </g>
+ <g id="shape30-59" v:mID="30" v:groupContext="shape" transform="translate(150.438,-88.5978)">
+ <title>Sheet.30</title>
+ <desc>Hm(x)</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="14.0625" cy="122.644" width="28.13" height="11.25"/>
+ <rect x="0" y="117.019" width="28.125" height="11.25" class="st1"/>
+ <text x="4.24" y="125.34" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st9" v:baseFontSize="8">m</tspan><tspan dy="0.164em" class="st3">(</tspan>x)</text> </g>
+ <g id="shape31-64" v:mID="31" v:groupContext="shape" transform="translate(130.188,-89.7228)">
+ <title>Sheet.31</title>
+ <desc>…..</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="14.0625" cy="122.644" width="28.13" height="11.25"/>
+ <rect x="0" y="117.019" width="28.125" height="11.25" class="st1"/>
+ <text x="8.46" y="125.34" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>…..</text> </g>
+ <g id="shape32-67" v:mID="32" v:groupContext="shape" transform="translate(34,-23.3478)">
+ <title>Sheet.32</title>
+ <desc>Store m for this group of keys</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="66.375" cy="122.644" width="132.75" height="11.25"/>
+ <g id="shadow32-68" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st12">
+ <rect x="0" y="117.019" width="132.75" height="11.25" class="st13"/>
+ </g>
+ <rect x="0" y="117.019" width="132.75" height="11.25" class="st14"/>
+ <text x="6.32" y="125.64" class="st15" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Store <tspan
+ class="st16">m</tspan> for this group of keys</text> </g>
+ <g id="shape36-76" v:mID="36" v:groupContext="shape" transform="translate(159.381,-100.964)">
+ <title>Sheet.36</title>
+ <path d="M3.45 125.81 L6.87 119.34 L7.99 120.16 L3.87 128.27 L0 124.35 L0.86 123.13 L3.45 125.81 Z" class="st17"/>
+ </g>
+ <g id="group44-79" transform="translate(97.5625,-100.086)" v:mID="44" v:groupContext="group">
+ <title>Sheet.44</title>
+ <g id="shape42-80" v:mID="42" v:groupContext="shape" transform="translate(85.4972,28.6255) rotate(41.8011)">
+ <title>Sheet.42</title>
+ <path d="M0 128.27 L6.04 128.27" class="st18"/>
+ </g>
+ <g id="shape43-83" v:mID="43" v:groupContext="shape" transform="translate(-87.9035,34.8564) rotate(-43.2597)">
+ <title>Sheet.43</title>
+ <path d="M0 128.27 L5.87 128.27" class="st18"/>
+ </g>
+ </g>
+ <g id="group45-86" transform="translate(120.625,-100.086)" v:mID="45" v:groupContext="group">
+ <title>Sheet.45</title>
+ <g id="shape46-87" v:mID="46" v:groupContext="shape" transform="translate(85.4972,28.6255) rotate(41.8011)">
+ <title>Sheet.46</title>
+ <path d="M0 128.27 L6.04 128.27" class="st18"/>
+ </g>
+ <g id="shape47-90" v:mID="47" v:groupContext="shape" transform="translate(-87.9035,34.8564) rotate(-43.2597)">
+ <title>Sheet.47</title>
+ <path d="M0 128.27 L5.87 128.27" class="st18"/>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i5.svg b/doc/guides/prog_guide/img/efd_i5.svg
new file mode 100644
index 0000000..b6540ba
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i5.svg
@@ -0,0 +1,183 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i5.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="8.34375in" height="2.86443in"
+ viewBox="0 0 600.75 206.239" xml:space="preserve" color-interpolation-filters="sRGB" class="st14">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:1.5em}
+ .st5 {fill:#feffff;font-family:Calibri;font-size:1.16666em}
+ .st6 {marker-end:url(#mrkr5-36);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st7 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st8 {stroke:#5b9bd5;stroke-dasharray:1.5,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st9 {fill:none;stroke:none;stroke-width:0.25}
+ .st10 {fill:#5b9bd5;font-family:Calibri;font-size:1.5em;font-weight:bold}
+ .st11 {baseline-shift:-32.4951%;font-size:0.649902em}
+ .st12 {fill:#deebf6;stroke:#0070c0;stroke-width:1}
+ .st13 {fill:#5b9bd5;font-family:Calibri;font-size:1.5em}
+ .st14 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-36" class="st7" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(93.0294,-158.5)">
+ <title>Rectangle</title>
+ <desc>All Keys</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="216" cy="192.739" width="432" height="27"/>
+ <g id="shadow2-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="179.239" width="432" height="27" class="st2"/>
+ </g>
+ <rect x="0" y="179.239" width="432" height="27" class="st3"/>
+ <text x="187.88" y="198.14" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>All Keys</text> </g>
+ <g id="shape3-7" v:mID="3" v:groupContext="shape" transform="translate(21.0294,-77.5)">
+ <title>Rectangle.3</title>
+ <desc>Group 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="188.239" width="108" height="36"/>
+ <g id="shadow3-8" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="170.239" width="108" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="170.239" width="108" height="36" class="st3"/>
+ <text x="30.97" y="192.44" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group 1</text> </g>
+ <g id="shape4-13" v:mID="4" v:groupContext="shape" transform="translate(156.029,-77.5)">
+ <title>Rectangle.4</title>
+ <desc>Group 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="188.239" width="108" height="36"/>
+ <g id="shadow4-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="170.239" width="108" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="170.239" width="108" height="36" class="st3"/>
+ <text x="30.97" y="192.44" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group 2</text> </g>
+ <g id="shape5-19" v:mID="5" v:groupContext="shape" transform="translate(291.029,-77.5)">
+ <title>Rectangle.5</title>
+ <desc>Group 3</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="188.239" width="108" height="36"/>
+ <g id="shadow5-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="170.239" width="108" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="170.239" width="108" height="36" class="st3"/>
+ <text x="30.97" y="192.44" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group 3</text> </g>
+ <g id="shape6-25" v:mID="6" v:groupContext="shape" transform="translate(471.029,-77.5)">
+ <title>Rectangle.6</title>
+ <desc>Group X</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="188.239" width="108" height="36"/>
+ <g id="shadow6-26" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="170.239" width="108" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="170.239" width="108" height="36" class="st3"/>
+ <text x="30.88" y="192.44" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group X</text> </g>
+ <g id="shape7-31" v:mID="7" v:groupContext="shape" transform="translate(359.05,247.819) rotate(165.964)">
+ <title>Sheet.7</title>
+ <path d="M0 206.24 L178.5 206.24" class="st6"/>
+ </g>
+ <g id="shape8-37" v:mID="8" v:groupContext="shape" transform="translate(428.903,215.562) rotate(144.462)">
+ <title>Sheet.8</title>
+ <path d="M0 206.24 L70.39 206.24" class="st6"/>
+ </g>
+ <g id="shape9-42" v:mID="9" v:groupContext="shape" transform="translate(470.075,-81.0976) rotate(51.3402)">
+ <title>Sheet.9</title>
+ <path d="M0 206.24 L50.59 206.24" class="st6"/>
+ </g>
+ <g id="shape10-47" v:mID="10" v:groupContext="shape" transform="translate(364.228,-150.976) rotate(15.5241)">
+ <title>Sheet.10</title>
+ <path d="M0 206.24 L161.1 206.24" class="st6"/>
+ </g>
+ <g id="shape11-52" v:mID="11" v:groupContext="shape" transform="translate(408.029,-95.5)">
+ <title>Sheet.11</title>
+ <path d="M0 206.24 L45 206.24" class="st8"/>
+ </g>
+ <g id="shape12-55" v:mID="12" v:groupContext="shape" transform="translate(48.0294,-50.5)">
+ <title>Sheet.12</title>
+ <desc>H7</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="192.739" width="45" height="27"/>
+ <rect x="0" y="179.239" width="45" height="27" class="st9"/>
+ <text x="13.86" y="198.14" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan
+ dy="-0.284em" class="st11" v:baseFontSize="18">7</tspan></text> </g>
+ <g id="shape13-59" v:mID="13" v:groupContext="shape" transform="translate(192.029,-50.5)">
+ <title>Sheet.13</title>
+ <desc>H267</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="192.739" width="45" height="27"/>
+ <rect x="0" y="179.239" width="45" height="27" class="st9"/>
+ <text x="7.93" y="198.14" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st11" v:baseFontSize="18">267</tspan></text> </g>
+ <g id="shape14-63" v:mID="14" v:groupContext="shape" transform="translate(318.029,-50.5)">
+ <title>Sheet.14</title>
+ <desc>H46</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="192.739" width="45" height="27"/>
+ <rect x="0" y="179.239" width="45" height="27" class="st9"/>
+ <text x="10.89" y="198.14" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan
+ dy="-0.284em" class="st11" v:baseFontSize="18">46</tspan></text> </g>
+ <g id="shape15-67" v:mID="15" v:groupContext="shape" transform="translate(502.529,-50.5)">
+ <title>Sheet.15</title>
+ <desc>H132</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="192.739" width="45" height="27"/>
+ <rect x="0" y="179.239" width="45" height="27" class="st9"/>
+ <text x="7.93" y="198.14" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st11" v:baseFontSize="18">132</tspan></text> </g>
+ <g id="shape16-71" v:mID="16" v:groupContext="shape" transform="translate(111.029,-19)">
+ <title>Sheet.16</title>
+ <desc>Store hash function index for each group of keys</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="189" cy="192.739" width="378" height="27"/>
+ <rect x="0" y="179.239" width="378" height="27" class="st12"/>
+ <text x="12.27" y="198.14" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Store hash function index for each group of keys</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i6.svg b/doc/guides/prog_guide/img/efd_i6.svg
new file mode 100644
index 0000000..9aee30b
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i6.svg
@@ -0,0 +1,1254 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i6.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="8.2496in" height="5.89673in"
+ viewBox="0 0 593.971 424.565" xml:space="preserve" color-interpolation-filters="sRGB" class="st27">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st5 {fill:#feffff;font-family:Calibri;font-size:0.75em}
+ .st6 {fill:none;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st7 {fill:none;stroke:#2e75b5;stroke-width:2.25}
+ .st8 {fill:#305497;stroke:#2e75b5;stroke-width:1}
+ .st9 {fill:#feffff;font-family:Calibri;font-size:0.833336em;font-weight:bold}
+ .st10 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2)}
+ .st11 {fill:#5b9bd5}
+ .st12 {stroke:#c7c8c8;stroke-width:0.25}
+ .st13 {fill:#acccea;stroke:#c7c8c8;stroke-width:0.25}
+ .st14 {fill:#feffff;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st15 {fill:#ed7d31;stroke:#c7c8c8;stroke-width:0.25}
+ .st16 {fill:#deebf6;stroke:#c7c8c8;stroke-width:0.25}
+ .st17 {marker-end:url(#mrkr5-212);stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st18 {fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st19 {fill:none;stroke:#2e75b5;stroke-width:1}
+ .st20 {fill:#5b9bd5;font-family:Calibri;font-size:1.00001em}
+ .st21 {fill:none;stroke:none;stroke-width:0.25}
+ .st22 {font-size:1em}
+ .st23 {fill:#ffffff}
+ .st24 {stroke:#5b9bd5;stroke-dasharray:1.5,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st25 {marker-end:url(#mrkr5-444);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st26 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st27 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-212" class="st18" v:arrowType="5" v:arrowSize="2" v:setback="5.8" refX="-5.8" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ <marker id="mrkr5-444" class="st26" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(319.501,-335.688)">
+ <title>Rectangle.58</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow3-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape4-7" v:mID="4" v:groupContext="shape" transform="translate(353.251,-335.688)">
+ <title>Rectangle.59</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow4-8" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape5-13" v:mID="5" v:groupContext="shape" transform="translate(400.501,-335.688)">
+ <title>Rectangle.60</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow5-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape6-19" v:mID="6" v:groupContext="shape" transform="translate(434.251,-335.688)">
+ <title>Rectangle.61</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow6-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape7-25" v:mID="7" v:groupContext="shape" transform="translate(481.501,-335.688)">
+ <title>Rectangle.62</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow7-26" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape8-30" v:mID="8" v:groupContext="shape" transform="translate(515.251,-335.688)">
+ <title>Rectangle.63</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow8-31" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape9-35" v:mID="9" v:groupContext="shape" transform="translate(319.501,-313.188)">
+ <title>Rectangle.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow9-36" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape10-40" v:mID="10" v:groupContext="shape" transform="translate(353.251,-313.188)">
+ <title>Rectangle.65</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow10-41" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape11-45" v:mID="11" v:groupContext="shape" transform="translate(400.501,-313.188)">
+ <title>Rectangle.66</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow11-46" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape12-50" v:mID="12" v:groupContext="shape" transform="translate(434.251,-313.188)">
+ <title>Rectangle.67</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow12-51" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape13-55" v:mID="13" v:groupContext="shape" transform="translate(481.501,-313.188)">
+ <title>Rectangle.68</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow13-56" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape14-60" v:mID="14" v:groupContext="shape" transform="translate(515.251,-313.188)">
+ <title>Rectangle.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow14-61" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape15-65" v:mID="15" v:groupContext="shape" transform="translate(319.501,-277.188)">
+ <title>Rectangle.70</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow15-66" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape16-71" v:mID="16" v:groupContext="shape" transform="translate(353.251,-277.188)">
+ <title>Rectangle.71</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow16-72" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape17-77" v:mID="17" v:groupContext="shape" transform="translate(400.501,-277.188)">
+ <title>Rectangle.72</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow17-78" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape18-83" v:mID="18" v:groupContext="shape" transform="translate(434.251,-277.188)">
+ <title>Rectangle.73</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow18-84" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape19-89" v:mID="19" v:groupContext="shape" transform="translate(481.501,-277.188)">
+ <title>Rectangle.74</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow19-90" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape20-95" v:mID="20" v:groupContext="shape" transform="translate(515.251,-277.188)">
+ <title>Rectangle.75</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow20-96" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape21-101" v:mID="21" v:groupContext="shape" transform="translate(319.501,-240.687)">
+ <title>Rectangle.76</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow21-102" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape22-106" v:mID="22" v:groupContext="shape" transform="translate(353.251,-240.687)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow22-107" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape23-111" v:mID="23" v:groupContext="shape" transform="translate(400.501,-240.687)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow23-112" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape24-116" v:mID="24" v:groupContext="shape" transform="translate(434.251,-240.687)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow24-117" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape25-121" v:mID="25" v:groupContext="shape" transform="translate(481.501,-240.687)">
+ <title>Rectangle.80</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow25-122" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape26-127" v:mID="26" v:groupContext="shape" transform="translate(515.251,-240.687)">
+ <title>Rectangle.81</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow26-128" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape27-133" v:mID="27" v:groupContext="shape" transform="translate(317.251,-231.687)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow27-134" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st6"/>
+ </g>
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st7"/>
+ </g>
+ <g id="shape28-138" v:mID="28" v:groupContext="shape" transform="translate(328.501,-362.688)">
+ <title>Sheet.28</title>
+ <desc>Local Table for N Specific Flows Serviced at Node 1</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="110.423" cy="418.94" width="220.85" height="11.25"/>
+ <rect x="0" y="413.315" width="220.846" height="11.25" class="st8"/>
+ <text x="5.77" y="421.94" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Local Table for N Specific Flows Serviced at Node 1</text> </g>
+ <g id="group34-141" transform="translate(66.0294,-165.569)" v:mID="34" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Device)"/>
+ <v:cp v:nameU="SubShapeType" v:lbl="SubShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Load balancer)"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Load balancer</title>
+ <g id="shape35-142" v:mID="35" v:groupContext="shape" transform="translate(0,-7.33146)">
+ <title>Sheet.35</title>
+ <g id="shadow35-143" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23 Z" class="st10"/>
+ <path d="M0 377.86 L72 377.86" class="st6"/>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23" class="st6"/>
+ </g>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23 Z" class="st11"/>
+ <path d="M0 377.86 L72 377.86" class="st12"/>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23" class="st12"/>
+ </g>
+ <g id="shape36-152" v:mID="36" v:groupContext="shape" transform="translate(8.03054,-12.9324)">
+ <title>Sheet.36</title>
+ <g id="shadow36-153" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M45.34 421.81 L41.2 422.66 L44.12 424.56 L49.68 423.16 L48.75 417.51 L45.8 415.59 L46.69 419.68 L36.97
+ 413.34 L35.6 415.45 L45.34 421.81 ZM50.83 405.36 L39.2 405.36 L39.2 407.88 L50.8 407.88 L47.82 410.83
+ L51.34 410.83 L55.21 406.61 L51.32 402.39 L47.83 402.39 L50.83 405.36 ZM46.49 392.01 L36.75 398.37
+ L38.13 400.48 L47.84 394.14 L46.96 398.23 L49.91 396.31 L50.84 390.66 L45.28 389.26 L42.36 391.16
+ L46.49 392.01 ZM27.71 397.16 C22.66 397.16 18.58 401.25 18.58 406.29 C18.58 411.33 22.66 415.42
+ 27.71 415.42 C32.75 415.42 36.84 411.33 36.84 406.29 C36.84 401.25 32.75 397.16 27.71 397.16 ZM27.71
+ 400.04 C31.15 400.04 33.96 402.84 33.96 406.29 C33.96 409.74 31.15 412.54 27.71 412.54 C24.26 412.54
+ 21.46 409.74 21.46 406.29 C21.46 402.84 24.26 400.04 27.71 400.04 ZM11.64 405.04 L0 405.04 L0 407.56
+ L11.6 407.56 L8.62 410.51 L12.14 410.51 L16.01 406.29 L12.12 402.07 L8.64 402.07 L11.64 405.04 Z"
+ class="st2"/>
+ </g>
+ <path d="M45.34 421.81 L41.2 422.66 L44.12 424.56 L49.68 423.16 L48.75 417.51 L45.8 415.59 L46.69 419.68 L36.97 413.34
+ L35.6 415.45 L45.34 421.81 ZM50.83 405.36 L39.2 405.36 L39.2 407.88 L50.8 407.88 L47.82 410.83 L51.34
+ 410.83 L55.21 406.61 L51.32 402.39 L47.83 402.39 L50.83 405.36 ZM46.49 392.01 L36.75 398.37 L38.13 400.48
+ L47.84 394.14 L46.96 398.23 L49.91 396.31 L50.84 390.66 L45.28 389.26 L42.36 391.16 L46.49 392.01 ZM27.71
+ 397.16 C22.66 397.16 18.58 401.25 18.58 406.29 C18.58 411.33 22.66 415.42 27.71 415.42 C32.75 415.42
+ 36.84 411.33 36.84 406.29 C36.84 401.25 32.75 397.16 27.71 397.16 ZM27.71 400.04 C31.15 400.04 33.96
+ 402.84 33.96 406.29 C33.96 409.74 31.15 412.54 27.71 412.54 C24.26 412.54 21.46 409.74 21.46 406.29
+ C21.46 402.84 24.26 400.04 27.71 400.04 ZM11.64 405.04 L0 405.04 L0 407.56 L11.6 407.56 L8.62 410.51
+ L12.14 410.51 L16.01 406.29 L12.12 402.07 L8.64 402.07 L11.64 405.04 Z" class="st13"/>
+ </g>
+ </g>
+ <g id="shape37-157" v:mID="37" v:groupContext="shape" transform="translate(21.0294,-45.4375)">
+ <title>Rectangle</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow37-158" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="336.433" width="135" height="88.1315" class="st2"/>
+ </g>
+ <rect x="0" y="336.433" width="135" height="88.1315" class="st3"/>
+ </g>
+ <g id="shape38-162" v:mID="38" v:groupContext="shape" transform="translate(34.693,-126.438)">
+ <title>Sheet.38</title>
+ <desc>EFD Table</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="49.3364" cy="415.565" width="98.68" height="18"/>
+ <rect x="0" y="406.565" width="98.6728" height="18" class="st8"/>
+ <text x="24.87" y="419.17" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>EFD Table</text> </g>
+ <g id="shape39-165" v:mID="39" v:groupContext="shape" transform="translate(30.0294,-99.4375)">
+ <title>Rectangle.39</title>
+ <desc>Group_id</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="26.9182" cy="415.565" width="53.84" height="18"/>
+ <g id="shadow39-166" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st15"/>
+ <text x="7.87" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group_id</text> </g>
+ <g id="shape40-171" v:mID="40" v:groupContext="shape" transform="translate(93.193,-99.4375)">
+ <title>Rectangle.40</title>
+ <desc>Hash index</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="26.9182" cy="415.565" width="53.84" height="18"/>
+ <g id="shadow40-172" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st15"/>
+ <text x="4.64" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Hash index</text> </g>
+ <g id="shape41-177" v:mID="41" v:groupContext="shape" transform="translate(30.193,-82.4275)">
+ <title>Rectangle.41</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow41-178" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape42-182" v:mID="42" v:groupContext="shape" transform="translate(30.193,-66.8125)">
+ <title>Rectangle.42</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow42-183" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape43-187" v:mID="43" v:groupContext="shape" transform="translate(30.1112,-52.1875)">
+ <title>Rectangle.43</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow43-188" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape44-192" v:mID="44" v:groupContext="shape" transform="translate(93.0294,-81.4375)">
+ <title>Rectangle.44</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow44-193" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape45-197" v:mID="45" v:groupContext="shape" transform="translate(93.193,-66.8125)">
+ <title>Rectangle.45</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow45-198" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape46-202" v:mID="46" v:groupContext="shape" transform="translate(93.193,-52.1875)">
+ <title>Rectangle.46</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow46-203" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape47-207" v:mID="47" v:groupContext="shape" transform="translate(374.924,544.022) rotate(135)">
+ <title>Sheet.47</title>
+ <path d="M-0 417.75 A40.674 18.0151 -156.2 0 0 40.24 422.15 L40.49 421.89" class="st17"/>
+ </g>
+ <g id="shape48-213" v:mID="48" v:groupContext="shape" transform="translate(21.0294,-19)">
+ <title>Sheet.48</title>
+ <desc>Supports X*N Flows</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="415.565" width="135" height="18"/>
+ <rect x="0" y="406.565" width="135" height="18" class="st19"/>
+ <text x="19.05" y="419.17" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Supports X*N Flows</text> </g>
+ <g id="shape49-216" v:mID="49" v:groupContext="shape" transform="translate(48.0294,-229.938)">
+ <title>Sheet.49</title>
+ <desc>Frontend Server or Load Balancer</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="400.94" width="108" height="47.25"/>
+ <rect x="0" y="377.315" width="108" height="47.25" class="st21"/>
+ <text x="14.56" y="397.34" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Frontend Server<v:newlineChar/><tspan
+ x="13.16" dy="1.2em" class="st22">or Load Balancer </tspan> </text> </g>
+ <g id="group51-220" transform="translate(223.876,-310.938)" v:mID="51" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server</title>
+ <g id="shape52-221" v:mID="52" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.52</title>
+ <g id="shadow52-222" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape53-226" v:mID="53" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.53</title>
+ <g id="shadow53-227" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape54-231" v:mID="54" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.54</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow54-232" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape59-239" v:mID="59" v:groupContext="shape" transform="translate(277.876,-373.938)">
+ <title>Sheet.59</title>
+ <path d="M-0 424.56 A111.108 53.2538 42.31 0 1 93.83 421.21 L94.14 421.41" class="st17"/>
+ </g>
+ <g id="shape60-244" v:mID="60" v:groupContext="shape" transform="translate(205.876,-283.938)">
+ <title>Sheet.60</title>
+ <desc>Backend Server 1</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.93" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server 1</text> </g>
+ <g id="group61-247" transform="translate(223.876,-207.438)" v:mID="61" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server.61</title>
+ <g id="shape62-248" v:mID="62" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.62</title>
+ <g id="shadow62-249" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape63-253" v:mID="63" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.63</title>
+ <g id="shadow63-254" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape64-258" v:mID="64" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow64-259" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape65-266" v:mID="65" v:groupContext="shape" transform="translate(205.876,-180.437)">
+ <title>Sheet.65</title>
+ <desc>Backend Server 2</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.93" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server 2</text> </g>
+ <g id="group66-269" transform="translate(219.029,-58.9375)" v:mID="66" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server.66</title>
+ <g id="shape67-270" v:mID="67" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.67</title>
+ <g id="shadow67-271" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape68-275" v:mID="68" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.68</title>
+ <g id="shadow68-276" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape69-280" v:mID="69" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow69-281" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape70-288" v:mID="70" v:groupContext="shape" transform="translate(201.029,-26.056)">
+ <title>Sheet.70</title>
+ <desc>Backend Server X</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.86" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server X</text> </g>
+ <g id="shape71-291" v:mID="71" v:groupContext="shape" transform="translate(684.44,239.627) rotate(90)">
+ <title>Sheet.71</title>
+ <path d="M0 424.56 L45 424.56" class="st24"/>
+ </g>
+ <g id="shape72-294" v:mID="72" v:groupContext="shape" transform="translate(6.85967,-22.443) rotate(-38.1076)">
+ <title>Sheet.72</title>
+ <path d="M-0 424.56 A96.1331 44.4001 55.03 0 1 68.24 420.56 L68.51 420.79" class="st17"/>
+ </g>
+ <g id="shape73-299" v:mID="73" v:groupContext="shape" transform="translate(328.501,-135.937)">
+ <title>Rectangle.73</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow73-300" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape74-305" v:mID="74" v:groupContext="shape" transform="translate(362.251,-135.937)">
+ <title>Rectangle.74</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow74-306" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape75-311" v:mID="75" v:groupContext="shape" transform="translate(409.501,-135.937)">
+ <title>Rectangle.75</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow75-312" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape76-317" v:mID="76" v:groupContext="shape" transform="translate(443.251,-135.937)">
+ <title>Rectangle.76</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow76-318" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape77-323" v:mID="77" v:groupContext="shape" transform="translate(490.501,-135.937)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow77-324" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape78-328" v:mID="78" v:groupContext="shape" transform="translate(524.251,-135.937)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow78-329" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape79-333" v:mID="79" v:groupContext="shape" transform="translate(328.501,-113.437)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow79-334" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape80-338" v:mID="80" v:groupContext="shape" transform="translate(362.251,-113.437)">
+ <title>Rectangle.80</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow80-339" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape81-343" v:mID="81" v:groupContext="shape" transform="translate(409.501,-113.437)">
+ <title>Rectangle.81</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow81-344" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape82-348" v:mID="82" v:groupContext="shape" transform="translate(443.251,-113.437)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow82-349" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape83-353" v:mID="83" v:groupContext="shape" transform="translate(490.501,-113.437)">
+ <title>Rectangle.83</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow83-354" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape84-358" v:mID="84" v:groupContext="shape" transform="translate(524.251,-113.437)">
+ <title>Rectangle.84</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow84-359" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape85-363" v:mID="85" v:groupContext="shape" transform="translate(328.501,-77.4375)">
+ <title>Rectangle.85</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow85-364" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape86-369" v:mID="86" v:groupContext="shape" transform="translate(362.251,-77.4375)">
+ <title>Rectangle.86</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow86-370" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape87-375" v:mID="87" v:groupContext="shape" transform="translate(409.501,-77.4375)">
+ <title>Rectangle.87</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow87-376" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape88-381" v:mID="88" v:groupContext="shape" transform="translate(443.251,-77.4375)">
+ <title>Rectangle.88</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow88-382" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape89-387" v:mID="89" v:groupContext="shape" transform="translate(490.501,-77.4375)">
+ <title>Rectangle.89</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow89-388" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape90-393" v:mID="90" v:groupContext="shape" transform="translate(524.251,-77.4375)">
+ <title>Rectangle.90</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow90-394" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape91-399" v:mID="91" v:groupContext="shape" transform="translate(328.501,-40.9375)">
+ <title>Rectangle.91</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow91-400" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape92-404" v:mID="92" v:groupContext="shape" transform="translate(362.251,-40.9375)">
+ <title>Rectangle.92</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow92-405" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape93-409" v:mID="93" v:groupContext="shape" transform="translate(409.501,-40.9375)">
+ <title>Rectangle.93</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow93-410" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape94-414" v:mID="94" v:groupContext="shape" transform="translate(443.251,-40.9375)">
+ <title>Rectangle.94</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow94-415" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape95-419" v:mID="95" v:groupContext="shape" transform="translate(490.501,-40.9375)">
+ <title>Rectangle.95</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow95-420" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape96-425" v:mID="96" v:groupContext="shape" transform="translate(524.251,-40.9375)">
+ <title>Rectangle.96</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow96-426" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape97-431" v:mID="97" v:groupContext="shape" transform="translate(326.251,-31.9375)">
+ <title>Rectangle.97</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow97-432" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st6"/>
+ </g>
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st7"/>
+ </g>
+ <g id="shape98-436" v:mID="98" v:groupContext="shape" transform="translate(337.501,-162.938)">
+ <title>Sheet.98</title>
+ <desc>Local Table for N Specific Flows Serviced at Node X</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="110.423" cy="418.94" width="220.85" height="11.25"/>
+ <rect x="0" y="413.315" width="220.846" height="11.25" class="st8"/>
+ <text x="5.55" y="421.94" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Local Table for N Specific Flows Serviced at Node X</text> </g>
+ <g id="shape99-439" v:mID="99" v:groupContext="shape" transform="translate(-204.342,-29.4449) rotate(-53.7462)">
+ <title>Sheet.99</title>
+ <path d="M0 424.56 L160.37 424.56" class="st25"/>
+ </g>
+ <g id="shape100-445" v:mID="100" v:groupContext="shape" transform="translate(-37.6568,-164.882) rotate(-24.444)">
+ <title>Sheet.100</title>
+ <path d="M0 424.56 L101.71 424.56" class="st25"/>
+ </g>
+ <g id="shape101-450" v:mID="101" v:groupContext="shape" transform="translate(464.049,-50.8578) rotate(50.099)">
+ <title>Sheet.101</title>
+ <path d="M0 424.56 L139.8 424.56" class="st25"/>
+ </g>
+ <g id="shape102-455" v:mID="102" v:groupContext="shape" transform="translate(372.376,-207.438)">
+ <title>Sheet.102</title>
+ <desc>Supports N Flows</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="415.565" width="135" height="18"/>
+ <rect x="0" y="406.565" width="135" height="18" class="st19"/>
+ <text x="25.15" y="419.17" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Supports N Flows</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i7.svg b/doc/guides/prog_guide/img/efd_i7.svg
new file mode 100644
index 0000000..98f8000
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i7.svg
@@ -0,0 +1,790 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i8.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="10.6168in" height="4.81965in"
+ viewBox="0 0 764.409 347.015" xml:space="preserve" color-interpolation-filters="sRGB" class="st27">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st4 {fill:#000000;font-family:Intel Clear;font-size:0.998566em}
+ .st5 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st6 {stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st7 {stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st8 {stroke:#ca8f02;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st9 {stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st10 {fill:#c00000;font-family:Intel Clear;font-size:0.828804em;font-weight:bold}
+ .st11 {fill:#7f6d00;font-family:Intel Clear;font-size:0.828804em;font-weight:bold}
+ .st12 {fill:#00b050;stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st13 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st14 {fill:#00b050;stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st15 {fill:#ca8f02;stroke:#ca8f02;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st16 {fill:#004280;font-family:Intel Clear;font-size:0.828804em}
+ .st17 {fill:#ffffff;font-family:Intel Clear;font-size:0.998566em}
+ .st18 {fill:#ffffff;font-family:Intel Clear;font-size:1.49785em}
+ .st19 {visibility:visible}
+ .st20 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st21 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st22 {fill:#feffff;font-family:Symbol;font-size:1.16666em}
+ .st23 {font-size:1em}
+ .st24 {font-family:Calibri;font-size:1em}
+ .st25 {fill:none;stroke:none;stroke-width:0.25}
+ .st26 {fill:#ffffff;font-family:Calibri;font-size:1.00001em}
+ .st27 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(27.7836,-307.505)">
+ <title>Sheet.3</title>
+ <path d="M0 329.94 C-0 328.06 1.54 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.06 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.54 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape4-3" v:mID="4" v:groupContext="shape" transform="translate(27.7836,-307.505)">
+ <title>Sheet.4</title>
+ <path d="M0 329.94 C-0 328.06 1.54 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.06 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.54 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape5-5" v:mID="5" v:groupContext="shape" transform="translate(50.1544,-309.121)">
+ <title>Sheet.5</title>
+ <desc>Key1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1</text> </g>
+ <g id="shape6-9" v:mID="6" v:groupContext="shape" transform="translate(43.6909,-286.954)">
+ <title>Sheet.6</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape7-11" v:mID="7" v:groupContext="shape" transform="translate(27.7836,-266.044)">
+ <title>Sheet.7</title>
+ <path d="M0 330.04 C0 328.16 1.52 326.64 3.41 326.64 L68.51 326.64 C70.4 326.64 71.91 328.16 71.91 330.04 L71.91 343.62
+ C71.91 345.49 70.4 347.02 68.51 347.02 L3.41 347.02 C1.52 347.02 0 345.49 0 343.62 L0 330.04 Z"
+ class="st1"/>
+ </g>
+ <g id="shape8-13" v:mID="8" v:groupContext="shape" transform="translate(27.7836,-266.044)">
+ <title>Sheet.8</title>
+ <path d="M0 330.04 C0 328.16 1.52 326.64 3.41 326.64 L68.51 326.64 C70.4 326.64 71.91 328.16 71.91 330.04 L71.91 343.62
+ C71.91 345.49 70.4 347.02 68.51 347.02 L3.41 347.02 C1.52 347.02 0 345.49 0 343.62 L0 330.04 Z"
+ class="st2"/>
+ </g>
+ <g id="shape9-15" v:mID="9" v:groupContext="shape" transform="translate(50.7572,-267.602)">
+ <title>Sheet.9</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape10-19" v:mID="10" v:groupContext="shape" transform="translate(19.0195,-225.183)">
+ <title>Sheet.10</title>
+ <path d="M0 330.74 C0 328.94 1.46 327.48 3.26 327.48 L87.15 327.48 C88.95 327.48 90.4 328.94 90.4 330.74 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.26 347.02 C1.46 347.02 0 345.56 0 343.76 L0 330.74 Z"
+ class="st1"/>
+ </g>
+ <g id="shape11-21" v:mID="11" v:groupContext="shape" transform="translate(19.0195,-225.183)">
+ <title>Sheet.11</title>
+ <path d="M0 330.74 C0 328.94 1.46 327.48 3.26 327.48 L87.15 327.48 C88.95 327.48 90.4 328.94 90.4 330.74 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.26 347.02 C1.46 347.02 0 345.56 0 343.76 L0 330.74 Z"
+ class="st2"/>
+ </g>
+ <g id="shape12-23" v:mID="12" v:groupContext="shape" transform="translate(28.0373,-226.287)">
+ <title>Sheet.12</title>
+ <desc>0x0102ABCD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.3615" cy="339.824" width="86.73" height="14.3829"/>
+ <path d="M86.72 332.63 L0 332.63 L0 347.02 L86.72 347.02 L86.72 332.63" class="st3"/>
+ <text x="7.12" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102ABCD</text> </g>
+ <g id="shape13-27" v:mID="13" v:groupContext="shape" transform="translate(43.6909,-244.775)">
+ <title>Sheet.13</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape14-29" v:mID="14" v:groupContext="shape" transform="translate(40.7496,-210.444)">
+ <title>Sheet.14</title>
+ <path d="M26.29 334.91 C26.29 338.26 25.84 340.96 25.29 340.96 L14.16 340.96 C13.6 340.96 13.15 343.67 13.15 347.02 C13.15
+ 343.67 12.7 340.96 12.14 340.96 L1.01 340.96 C0.46 340.96 0 338.26 0 334.91" class="st6"/>
+ </g>
+ <g id="shape15-32" v:mID="15" v:groupContext="shape" transform="translate(125.629,-307.625)">
+ <title>Sheet.15</title>
+ <path d="M0 330.04 C0 328.16 1.52 326.64 3.41 326.64 L68.63 326.64 C70.51 326.64 72.03 328.16 72.03 330.04 L72.03 343.62
+ C72.03 345.49 70.51 347.02 68.63 347.02 L3.41 347.02 C1.52 347.02 0 345.49 0 343.62 L0 330.04 Z"
+ class="st1"/>
+ </g>
+ <g id="shape16-34" v:mID="16" v:groupContext="shape" transform="translate(125.629,-307.625)">
+ <title>Sheet.16</title>
+ <path d="M0 330.04 C0 328.16 1.52 326.64 3.41 326.64 L68.63 326.64 C70.51 326.64 72.03 328.16 72.03 330.04 L72.03 343.62
+ C72.03 345.49 70.51 347.02 68.63 347.02 L3.41 347.02 C1.52 347.02 0 345.49 0 343.62 L0 330.04 Z"
+ class="st2"/>
+ </g>
+ <g id="shape17-36" v:mID="17" v:groupContext="shape" transform="translate(148.034,-309.155)">
+ <title>Sheet.17</title>
+ <desc>Key2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key2</text> </g>
+ <g id="shape18-40" v:mID="18" v:groupContext="shape" transform="translate(141.536,-286.954)">
+ <title>Sheet.18</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape19-42" v:mID="19" v:groupContext="shape" transform="translate(125.629,-266.044)">
+ <title>Sheet.19</title>
+ <path d="M0 329.94 C0 328.06 1.54 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.06 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.54 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st1"/>
+ </g>
+ <g id="shape20-44" v:mID="20" v:groupContext="shape" transform="translate(125.629,-266.044)">
+ <title>Sheet.20</title>
+ <path d="M0 329.94 C0 328.06 1.54 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.06 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.54 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st2"/>
+ </g>
+ <g id="shape21-46" v:mID="21" v:groupContext="shape" transform="translate(148.636,-267.636)">
+ <title>Sheet.21</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape22-50" v:mID="22" v:groupContext="shape" transform="translate(116.865,-225.183)">
+ <title>Sheet.22</title>
+ <path d="M0 330.74 C0 328.94 1.46 327.48 3.26 327.48 L87.15 327.48 C88.95 327.48 90.4 328.94 90.4 330.74 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.26 347.02 C1.46 347.02 0 345.56 0 343.76 L0 330.74 Z"
+ class="st1"/>
+ </g>
+ <g id="shape23-52" v:mID="23" v:groupContext="shape" transform="translate(116.865,-225.183)">
+ <title>Sheet.23</title>
+ <path d="M0 330.74 C0 328.94 1.46 327.48 3.26 327.48 L87.15 327.48 C88.95 327.48 90.4 328.94 90.4 330.74 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.26 347.02 C1.46 347.02 0 345.56 0 343.76 L0 330.74 Z"
+ class="st2"/>
+ </g>
+ <g id="shape24-54" v:mID="24" v:groupContext="shape" transform="translate(125.917,-226.322)">
+ <title>Sheet.24</title>
+ <desc>0x0103CDAB</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.3615" cy="339.824" width="86.73" height="14.3829"/>
+ <path d="M86.72 332.63 L0 332.63 L0 347.02 L86.72 347.02 L86.72 332.63" class="st3"/>
+ <text x="7.12" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0103CDAB</text> </g>
+ <g id="shape25-58" v:mID="25" v:groupContext="shape" transform="translate(141.536,-244.775)">
+ <title>Sheet.25</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape26-60" v:mID="26" v:groupContext="shape" transform="translate(138.595,-210.444)">
+ <title>Sheet.26</title>
+ <path d="M26.29 334.91 C26.29 338.26 25.84 340.96 25.29 340.96 L14.16 340.96 C13.6 340.96 13.15 343.67 13.15 347.02 C13.15
+ 343.67 12.7 340.96 12.14 340.96 L1.01 340.96 C0.46 340.96 0 338.26 0 334.91" class="st7"/>
+ </g>
+ <g id="shape27-63" v:mID="27" v:groupContext="shape" transform="translate(221.793,-307.625)">
+ <title>Sheet.27</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.64 326.64 C70.52 326.64 72.03 328.17 72.03 330.04 L72.03 343.63
+ C72.03 345.5 70.52 347.02 68.64 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st1"/>
+ </g>
+ <g id="shape28-65" v:mID="28" v:groupContext="shape" transform="translate(221.793,-307.625)">
+ <title>Sheet.28</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.64 326.64 C70.52 326.64 72.03 328.17 72.03 330.04 L72.03 343.63
+ C72.03 345.5 70.52 347.02 68.64 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st2"/>
+ </g>
+ <g id="shape29-67" v:mID="29" v:groupContext="shape" transform="translate(244.237,-309.155)">
+ <title>Sheet.29</title>
+ <desc>Key3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3</text> </g>
+ <g id="shape30-71" v:mID="30" v:groupContext="shape" transform="translate(237.701,-286.954)">
+ <title>Sheet.30</title>
+ <path d="M0 336.65 L9.84 336.65 L9.84 326.28 L29.53 326.28 L29.53 336.65 L39.38 336.65 L19.69 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape31-73" v:mID="31" v:groupContext="shape" transform="translate(221.793,-266.044)">
+ <title>Sheet.31</title>
+ <path d="M0 329.94 C-0 328.07 1.55 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.07 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st1"/>
+ </g>
+ <g id="shape32-75" v:mID="32" v:groupContext="shape" transform="translate(221.793,-266.044)">
+ <title>Sheet.32</title>
+ <path d="M0 329.94 C-0 328.07 1.55 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.07 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st2"/>
+ </g>
+ <g id="shape33-77" v:mID="33" v:groupContext="shape" transform="translate(244.84,-267.636)">
+ <title>Sheet.33</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape34-81" v:mID="34" v:groupContext="shape" transform="translate(213.029,-225.183)">
+ <title>Sheet.34</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.27 327.48 C89.07 327.48 90.52 328.95 90.52 330.75 L90.52 343.76
+ C90.52 345.56 89.07 347.02 87.27 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape35-83" v:mID="35" v:groupContext="shape" transform="translate(213.029,-225.183)">
+ <title>Sheet.35</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.27 327.48 C89.07 327.48 90.52 328.95 90.52 330.75 L90.52 343.76
+ C90.52 345.56 89.07 347.02 87.27 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape36-85" v:mID="36" v:groupContext="shape" transform="translate(222.002,-226.322)">
+ <title>Sheet.36</title>
+ <desc>0x0102BAAD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.4787" cy="339.824" width="86.96" height="14.3829"/>
+ <path d="M86.96 332.63 L0 332.63 L0 347.02 L86.96 347.02 L86.96 332.63" class="st3"/>
+ <text x="7.13" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102BAAD</text> </g>
+ <g id="shape37-89" v:mID="37" v:groupContext="shape" transform="translate(237.701,-244.775)">
+ <title>Sheet.37</title>
+ <path d="M0 336.71 L9.84 336.71 L9.84 326.4 L29.53 326.4 L29.53 336.71 L39.38 336.71 L19.69 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape38-91" v:mID="38" v:groupContext="shape" transform="translate(234.759,-210.444)">
+ <title>Sheet.38</title>
+ <path d="M26.41 334.91 C26.41 338.26 25.96 340.96 25.41 340.96 L14.22 340.96 C13.66 340.96 13.21 343.67 13.21 347.02
+ C13.21 343.67 12.76 340.96 12.2 340.96 L1.01 340.96 C0.46 340.96 0 338.26 0 334.91" class="st6"/>
+ </g>
+ <g id="shape39-94" v:mID="39" v:groupContext="shape" transform="translate(319.759,-307.625)">
+ <title>Sheet.39</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.52 326.64 C70.4 326.64 71.91 328.17 71.91 330.04 L71.91 343.63
+ C71.91 345.5 70.4 347.02 68.52 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st1"/>
+ </g>
+ <g id="shape40-96" v:mID="40" v:groupContext="shape" transform="translate(319.759,-307.625)">
+ <title>Sheet.40</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.52 326.64 C70.4 326.64 71.91 328.17 71.91 330.04 L71.91 343.63
+ C71.91 345.5 70.4 347.02 68.52 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st2"/>
+ </g>
+ <g id="shape41-98" v:mID="41" v:groupContext="shape" transform="translate(342.125,-309.155)">
+ <title>Sheet.41</title>
+ <desc>Key4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4</text> </g>
+ <g id="shape42-102" v:mID="42" v:groupContext="shape" transform="translate(335.666,-286.954)">
+ <title>Sheet.42</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape43-104" v:mID="43" v:groupContext="shape" transform="translate(319.759,-266.044)">
+ <title>Sheet.43</title>
+ <path d="M0 329.94 C0 328.07 1.55 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.07 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape44-106" v:mID="44" v:groupContext="shape" transform="translate(319.759,-266.044)">
+ <title>Sheet.44</title>
+ <path d="M0 329.94 C0 328.07 1.55 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.07 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape45-108" v:mID="45" v:groupContext="shape" transform="translate(342.728,-267.636)">
+ <title>Sheet.45</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape46-112" v:mID="46" v:groupContext="shape" transform="translate(310.995,-225.183)">
+ <title>Sheet.46</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.15 327.48 C88.95 327.48 90.4 328.95 90.4 330.75 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape47-114" v:mID="47" v:groupContext="shape" transform="translate(310.995,-225.183)">
+ <title>Sheet.47</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.15 327.48 C88.95 327.48 90.4 328.95 90.4 330.75 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape48-116" v:mID="48" v:groupContext="shape" transform="translate(321.689,-226.322)">
+ <title>Sheet.48</title>
+ <desc>0x0104BEEF</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="41.4183" cy="339.824" width="82.84" height="14.3829"/>
+ <path d="M82.84 332.63 L0 332.63 L0 347.02 L82.84 347.02 L82.84 332.63" class="st3"/>
+ <text x="6.87" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0104BEEF</text> </g>
+ <g id="shape49-120" v:mID="49" v:groupContext="shape" transform="translate(335.666,-244.775)">
+ <title>Sheet.49</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape50-122" v:mID="50" v:groupContext="shape" transform="translate(332.725,-210.444)">
+ <title>Sheet.50</title>
+ <path d="M26.29 334.91 C26.29 338.27 25.84 340.96 25.29 340.96 L14.17 340.96 C13.61 340.96 13.15 343.67 13.15 347.02
+ C13.15 343.67 12.7 340.96 12.14 340.96 L1.02 340.96 C0.47 340.96 0 338.27 0 334.91" class="st6"/>
+ </g>
+ <g id="shape51-125" v:mID="51" v:groupContext="shape" transform="translate(416.884,-307.625)">
+ <title>Sheet.51</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.52 326.64 C70.4 326.64 71.91 328.17 71.91 330.04 L71.91 343.63
+ C71.91 345.5 70.4 347.02 68.52 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st1"/>
+ </g>
+ <g id="shape52-127" v:mID="52" v:groupContext="shape" transform="translate(416.884,-307.625)">
+ <title>Sheet.52</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.52 326.64 C70.4 326.64 71.91 328.17 71.91 330.04 L71.91 343.63
+ C71.91 345.5 70.4 347.02 68.52 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st2"/>
+ </g>
+ <g id="shape53-129" v:mID="53" v:groupContext="shape" transform="translate(439.255,-309.155)">
+ <title>Sheet.53</title>
+ <desc>Key5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key5</text> </g>
+ <g id="shape54-133" v:mID="54" v:groupContext="shape" transform="translate(432.791,-286.954)">
+ <title>Sheet.54</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape55-135" v:mID="55" v:groupContext="shape" transform="translate(416.884,-266.044)">
+ <title>Sheet.55</title>
+ <path d="M0 329.94 C0 328.07 1.55 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.07 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape56-137" v:mID="56" v:groupContext="shape" transform="translate(416.884,-266.044)">
+ <title>Sheet.56</title>
+ <path d="M0 329.94 C0 328.07 1.55 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.07 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape57-139" v:mID="57" v:groupContext="shape" transform="translate(439.858,-267.636)">
+ <title>Sheet.57</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape58-143" v:mID="58" v:groupContext="shape" transform="translate(408.12,-225.183)">
+ <title>Sheet.58</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.15 327.48 C88.95 327.48 90.4 328.95 90.4 330.75 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape59-145" v:mID="59" v:groupContext="shape" transform="translate(408.12,-225.183)">
+ <title>Sheet.59</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.15 327.48 C88.95 327.48 90.4 328.95 90.4 330.75 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape60-147" v:mID="60" v:groupContext="shape" transform="translate(416.778,-226.322)">
+ <title>Sheet.60</title>
+ <desc>0x0103DABD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.7817" cy="339.824" width="87.57" height="14.3829"/>
+ <path d="M87.56 332.63 L0 332.63 L0 347.02 L87.56 347.02 L87.56 332.63" class="st3"/>
+ <text x="7.17" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0103DABD</text> </g>
+ <g id="shape61-151" v:mID="61" v:groupContext="shape" transform="translate(432.791,-244.775)">
+ <title>Sheet.61</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape62-153" v:mID="62" v:groupContext="shape" transform="translate(429.85,-210.444)">
+ <title>Sheet.62</title>
+ <path d="M26.29 334.91 C26.29 338.27 25.84 340.96 25.29 340.96 L14.17 340.96 C13.61 340.96 13.15 343.67 13.15 347.02
+ C13.15 343.67 12.7 340.96 12.14 340.96 L1.02 340.96 C0.47 340.96 0 338.27 0 334.91" class="st7"/>
+ </g>
+ <g id="shape63-156" v:mID="63" v:groupContext="shape" transform="translate(514.489,-307.625)">
+ <title>Sheet.63</title>
+ <path d="M0 330.06 C-0 328.17 1.53 326.64 3.42 326.64 L68.64 326.64 C70.53 326.64 72.03 328.17 72.03 330.06 L72.03 343.63
+ C72.03 345.52 70.53 347.02 68.64 347.02 L3.42 347.02 C1.53 347.02 0 345.52 0 343.63 L0 330.06 Z"
+ class="st1"/>
+ </g>
+ <g id="shape64-158" v:mID="64" v:groupContext="shape" transform="translate(514.489,-307.625)">
+ <title>Sheet.64</title>
+ <path d="M0 330.06 C-0 328.17 1.53 326.64 3.42 326.64 L68.64 326.64 C70.53 326.64 72.03 328.17 72.03 330.06 L72.03 343.63
+ C72.03 345.52 70.53 347.02 68.64 347.02 L3.42 347.02 C1.53 347.02 0 345.52 0 343.63 L0 330.06 Z"
+ class="st2"/>
+ </g>
+ <g id="shape65-160" v:mID="65" v:groupContext="shape" transform="translate(536.883,-309.19)">
+ <title>Sheet.65</title>
+ <desc>Key6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key6</text> </g>
+ <g id="shape66-164" v:mID="66" v:groupContext="shape" transform="translate(530.396,-287.074)">
+ <title>Sheet.66</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape67-166" v:mID="67" v:groupContext="shape" transform="translate(514.489,-266.044)">
+ <title>Sheet.67</title>
+ <path d="M0 329.94 C0 328.08 1.56 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.08 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.56 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st1"/>
+ </g>
+ <g id="shape68-168" v:mID="68" v:groupContext="shape" transform="translate(514.489,-266.044)">
+ <title>Sheet.68</title>
+ <path d="M0 329.94 C0 328.08 1.56 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.08 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.56 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st2"/>
+ </g>
+ <g id="shape69-170" v:mID="69" v:groupContext="shape" transform="translate(537.486,-267.671)">
+ <title>Sheet.69</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape70-174" v:mID="70" v:groupContext="shape" transform="translate(505.725,-225.183)">
+ <title>Sheet.70</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.16 327.48 C88.96 327.48 90.4 328.95 90.4 330.75 L90.4 343.78
+ C90.4 345.58 88.96 347.02 87.16 347.02 L3.27 347.02 C1.47 347.02 0 345.58 0 343.78 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape71-176" v:mID="71" v:groupContext="shape" transform="translate(505.725,-225.183)">
+ <title>Sheet.71</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.16 327.48 C88.96 327.48 90.4 328.95 90.4 330.75 L90.4 343.78
+ C90.4 345.58 88.96 347.02 87.16 347.02 L3.27 347.02 C1.47 347.02 0 345.58 0 343.78 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape72-178" v:mID="72" v:groupContext="shape" transform="translate(514.766,-226.356)">
+ <title>Sheet.72</title>
+ <desc>0x0102ADCB</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.3615" cy="339.824" width="86.73" height="14.3829"/>
+ <path d="M86.72 332.63 L0 332.63 L0 347.02 L86.72 347.02 L86.72 332.63" class="st3"/>
+ <text x="7.12" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102ADCB</text> </g>
+ <g id="shape73-182" v:mID="73" v:groupContext="shape" transform="translate(530.396,-244.775)">
+ <title>Sheet.73</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape74-184" v:mID="74" v:groupContext="shape" transform="translate(527.455,-210.564)">
+ <title>Sheet.74</title>
+ <path d="M26.29 335.03 C26.29 338.36 25.87 341.02 25.3 341.02 L14.17 341.02 C13.6 341.02 13.15 343.72 13.15 347.02 C13.15
+ 343.72 12.73 341.02 12.16 341.02 L1.02 341.02 C0.45 341.02 0 338.36 0 335.03" class="st6"/>
+ </g>
+ <g id="shape75-187" v:mID="75" v:groupContext="shape" transform="translate(610.653,-307.505)">
+ <title>Sheet.75</title>
+ <path d="M0 329.94 C0 328.08 1.56 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.08 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.56 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st1"/>
+ </g>
+ <g id="shape76-189" v:mID="76" v:groupContext="shape" transform="translate(610.653,-307.505)">
+ <title>Sheet.76</title>
+ <path d="M0 329.94 C0 328.08 1.56 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.08 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.56 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st2"/>
+ </g>
+ <g id="shape77-191" v:mID="77" v:groupContext="shape" transform="translate(633.086,-309.121)">
+ <title>Sheet.77</title>
+ <desc>Key7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7</text> </g>
+ <g id="shape78-195" v:mID="78" v:groupContext="shape" transform="translate(626.561,-286.954)">
+ <title>Sheet.78</title>
+ <path d="M0 336.65 L9.84 336.65 L9.84 326.28 L29.53 326.28 L29.53 336.65 L39.38 336.65 L19.69 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape79-197" v:mID="79" v:groupContext="shape" transform="translate(610.653,-266.044)">
+ <title>Sheet.79</title>
+ <path d="M0 330.06 C-0 328.17 1.53 326.64 3.42 326.64 L68.64 326.64 C70.53 326.64 72.03 328.17 72.03 330.06 L72.03 343.63
+ C72.03 345.52 70.53 347.02 68.64 347.02 L3.42 347.02 C1.53 347.02 0 345.52 0 343.63 L0 330.06 Z"
+ class="st1"/>
+ </g>
+ <g id="shape80-199" v:mID="80" v:groupContext="shape" transform="translate(610.653,-266.044)">
+ <title>Sheet.80</title>
+ <path d="M0 330.06 C-0 328.17 1.53 326.64 3.42 326.64 L68.64 326.64 C70.53 326.64 72.03 328.17 72.03 330.06 L72.03 343.63
+ C72.03 345.52 70.53 347.02 68.64 347.02 L3.42 347.02 C1.53 347.02 0 345.52 0 343.63 L0 330.06 Z"
+ class="st2"/>
+ </g>
+ <g id="shape81-201" v:mID="81" v:groupContext="shape" transform="translate(633.689,-267.602)">
+ <title>Sheet.81</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape82-205" v:mID="82" v:groupContext="shape" transform="translate(601.889,-225.183)">
+ <title>Sheet.82</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.28 327.48 C89.08 327.48 90.52 328.95 90.52 330.75 L90.52 343.78
+ C90.52 345.58 89.08 347.02 87.28 347.02 L3.27 347.02 C1.47 347.02 0 345.58 0 343.78 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape83-207" v:mID="83" v:groupContext="shape" transform="translate(601.889,-225.183)">
+ <title>Sheet.83</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.28 327.48 C89.08 327.48 90.52 328.95 90.52 330.75 L90.52 343.78
+ C90.52 345.58 89.08 347.02 87.28 347.02 L3.27 347.02 C1.47 347.02 0 345.58 0 343.78 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape84-209" v:mID="84" v:groupContext="shape" transform="translate(610.969,-226.287)">
+ <title>Sheet.84</title>
+ <desc>0x0104DBCA</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.3615" cy="339.824" width="86.73" height="14.3829"/>
+ <path d="M86.72 332.63 L0 332.63 L0 347.02 L86.72 347.02 L86.72 332.63" class="st3"/>
+ <text x="7.12" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0104DBCA</text> </g>
+ <g id="shape85-213" v:mID="85" v:groupContext="shape" transform="translate(626.561,-244.775)">
+ <title>Sheet.85</title>
+ <path d="M0 336.71 L9.84 336.71 L9.84 326.4 L29.53 326.4 L29.53 336.71 L39.38 336.71 L19.69 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape86-215" v:mID="86" v:groupContext="shape" transform="translate(623.619,-210.444)">
+ <title>Sheet.86</title>
+ <path d="M26.41 334.91 C26.41 338.27 25.96 340.96 25.42 340.96 L14.23 340.96 C13.69 340.96 13.21 343.69 13.21 347.02
+ C13.21 343.69 12.76 340.96 12.22 340.96 L1.02 340.96 C0.48 340.96 0 338.27 0 334.91" class="st8"/>
+ </g>
+ <g id="shape87-218" v:mID="87" v:groupContext="shape" transform="translate(242.323,-81.6288)">
+ <title>Sheet.87</title>
+ <path d="M0 281.23 L0 347.02 L41.18 347.02 L41.18 281.23 L0 281.23 L0 281.23 Z" class="st1"/>
+ </g>
+ <g id="shape88-220" v:mID="88" v:groupContext="shape" transform="translate(247.009,-81.6288)">
+ <title>Sheet.88</title>
+ <path d="M0 281.23 L41.18 281.23 L41.18 347.02 L0 347.02 L0 281.23" class="st9"/>
+ </g>
+ <g id="shape89-223" v:mID="89" v:groupContext="shape" transform="translate(245.254,-132.398)">
+ <title>Sheet.89</title>
+ <desc>0x0102</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.3211" cy="341.046" width="42.65" height="11.9384"/>
+ <path d="M42.64 335.08 L0 335.08 L0 347.02 L42.64 347.02 L42.64 335.08" class="st3"/>
+ <text x="4" y="344.03" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102</text> </g>
+ <g id="shape90-227" v:mID="90" v:groupContext="shape" transform="translate(245.015,-82.7016)">
+ <title>Sheet.90</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.79425" cy="341.046" width="9.59" height="11.9384"/>
+ <path d="M9.59 335.08 L0 335.08 L0 347.02 L9.59 347.02 L9.59 335.08" class="st3"/>
+ <text x="1.84" y="344.03" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape91-231" v:mID="91" v:groupContext="shape" transform="translate(336.326,-81.6288)">
+ <title>Sheet.91</title>
+ <path d="M0 281.23 L0 347.02 L41.18 347.02 L41.18 281.23 L0 281.23 L0 281.23 Z" class="st1"/>
+ </g>
+ <g id="shape92-233" v:mID="92" v:groupContext="shape" transform="translate(339.598,-81.6288)">
+ <title>Sheet.92</title>
+ <path d="M0 281.23 L41.18 281.23 L41.18 347.02 L0 347.02 L0 281.23" class="st9"/>
+ </g>
+ <g id="shape93-236" v:mID="93" v:groupContext="shape" transform="translate(339.264,-132.398)">
+ <title>Sheet.93</title>
+ <desc>0x0103</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.3211" cy="341.046" width="42.65" height="11.9384"/>
+ <path d="M42.64 335.08 L0 335.08 L0 347.02 L42.64 347.02 L42.64 335.08" class="st3"/>
+ <text x="4" y="344.03" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0103</text> </g>
+ <g id="shape94-240" v:mID="94" v:groupContext="shape" transform="translate(339.024,-82.7016)">
+ <title>Sheet.94</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.79425" cy="341.046" width="9.59" height="11.9384"/>
+ <path d="M9.59 335.08 L0 335.08 L0 347.02 L9.59 347.02 L9.59 335.08" class="st3"/>
+ <text x="1.84" y="344.03" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape95-244" v:mID="95" v:groupContext="shape" transform="translate(438.598,-81.5089)">
+ <title>Sheet.95</title>
+ <path d="M0 281.23 L0 347.02 L41.18 347.02 L41.18 281.23 L0 281.23 L0 281.23 Z" class="st1"/>
+ </g>
+ <g id="shape96-246" v:mID="96" v:groupContext="shape" transform="translate(438.598,-81.5089)">
+ <title>Sheet.96</title>
+ <path d="M0 281.23 L41.18 281.23 L41.18 347.02 L0 347.02 L0 281.23" class="st9"/>
+ </g>
+ <g id="shape97-249" v:mID="97" v:groupContext="shape" transform="translate(437.81,-132.27)">
+ <title>Sheet.97</title>
+ <desc>0x0104</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.3211" cy="341.046" width="42.65" height="11.9384"/>
+ <path d="M42.64 335.08 L0 335.08 L0 347.02 L42.64 347.02 L42.64 335.08" class="st3"/>
+ <text x="4" y="344.03" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0104</text> </g>
+ <g id="shape98-253" v:mID="98" v:groupContext="shape" transform="translate(437.57,-82.5735)">
+ <title>Sheet.98</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.79425" cy="341.046" width="9.59" height="11.9384"/>
+ <path d="M9.59 335.08 L0 335.08 L0 347.02 L9.59 347.02 L9.59 335.08" class="st3"/>
+ <text x="1.84" y="344.03" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape99-257" v:mID="99" v:groupContext="shape" transform="translate(53.5505,-147.924)">
+ <title>Sheet.99</title>
+ <path d="M0.59 283.52 L206.27 343.39 L205.7 345.34 L0 285.48 L0.59 283.52 L0.59 283.52 ZM205.85 341.14 L210.88 345.79
+ L204.14 347.02 L205.85 341.14 L205.85 341.14 Z" class="st12"/>
+ </g>
+ <g id="shape100-259" v:mID="100" v:groupContext="shape" transform="translate(151.516,-147.924)">
+ <title>Sheet.100</title>
+ <path d="M0.59 283.52 L202.41 343.41 L201.83 345.35 L0 285.48 L0.59 283.52 L0.59 283.52 ZM202.01 341.16 L207.01 345.83
+ L200.27 347.02 L202.01 341.16 L202.01 341.16 Z" class="st13"/>
+ </g>
+ <g id="shape101-261" v:mID="101" v:groupContext="shape" transform="translate(246.975,-147.37)">
+ <title>Sheet.101</title>
+ <path d="M2 283.72 L15.77 341.83 L13.79 342.3 L0 284.18 L2 283.72 L2 283.72 ZM17.53 340.36 L15.97 347.02 L11.57 341.77
+ L17.53 340.36 L17.53 340.36 Z" class="st12"/>
+ </g>
+ <g id="shape102-263" v:mID="102" v:groupContext="shape" transform="translate(262.972,-147.37)">
+ <title>Sheet.102</title>
+ <path d="M82.31 283.13 L3.45 343.12 L4.68 344.74 L83.54 284.76 L82.31 283.13 L82.31 283.13 ZM3.02 340.89 L0 347.02 L6.74
+ 345.74 L3.02 340.89 L3.02 340.89 Z" class="st12"/>
+ </g>
+ <g id="shape103-265" v:mID="103" v:groupContext="shape" transform="translate(358.537,-149.107)">
+ <title>Sheet.103</title>
+ <path d="M83.92 284.85 L3.53 343.2 L4.73 344.84 L85.12 286.5 L83.92 284.85 L83.92 284.85 ZM3.15 340.95 L0 347.02 L6.75
+ 345.89 L3.15 340.95 L3.15 340.95 Z" class="st13"/>
+ </g>
+ <g id="shape104-267" v:mID="104" v:groupContext="shape" transform="translate(264.413,-147.534)">
+ <title>Sheet.104</title>
+ <path d="M275.95 283 L4.77 343.27 L5.22 345.25 L276.37 285 L275.95 283 L275.95 283 ZM5.31 341.05 L0 345.37 L6.66 347.02
+ L5.31 341.05 L5.31 341.05 Z" class="st14"/>
+ </g>
+ <g id="shape105-269" v:mID="105" v:groupContext="shape" transform="translate(456.982,-148.103)">
+ <title>Sheet.105</title>
+ <path d="M179.48 283.72 L4.5 343.48 L5.16 345.43 L180.14 285.66 L179.48 283.72 L179.48 283.72 ZM4.8 341.23 L0 346.12
+ L6.81 347.02 L4.8 341.23 L4.8 341.23 Z" class="st15"/>
+ </g>
+ <g id="shape106-271" v:mID="106" v:groupContext="shape" transform="translate(335.628,-18)">
+ <title>Sheet.106</title>
+ <path d="M0 309.64 C0 305.52 2.99 302.16 6.65 302.16 L14.2 302.16 L8.01 284.85 L35.48 302.16 L78.47 302.16 C82.15 302.16
+ 85.12 305.52 85.12 309.64 L85.12 309.64 L85.12 320.85 L85.12 339.54 C85.12 343.68 82.15 347.02 78.47 347.02
+ L35.48 347.02 L14.2 347.02 L14.2 347.02 L6.65 347.02 C2.99 347.02 0 343.68 0 339.54 L0 320.85 L0 309.64
+ L0 309.64 Z" class="st5"/>
+ </g>
+ <g id="shape109-273" v:mID="109" v:groupContext="shape" transform="translate(157.564,-62.4234)">
+ <title>Sheet.109</title>
+ <path d="M16.21 347.02 C11.74 347.02 8.1 346.42 8.1 345.67 L8.1 303.49 C8.1 302.75 4.49 302.14 0 302.14 C4.49 302.14
+ 8.1 301.54 8.1 300.79 L8.1 258.61 C8.1 257.88 11.74 257.26 16.21 257.26" class="st7"/>
+ </g>
+ <g id="shape110-276" v:mID="110" v:groupContext="shape" transform="translate(113.844,-100.157)">
+ <title>Sheet.110</title>
+ <desc>Groups</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="20.2175" cy="341.046" width="40.44" height="11.9384"/>
+ <path d="M40.44 335.08 L0 335.08 L0 347.02 L40.44 347.02 L40.44 335.08" class="st3"/>
+ <text x="3.85" y="344.03" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Groups</text> </g>
+ <g id="shape111-280" v:mID="111" v:groupContext="shape" transform="translate(196.718,-76.2186)">
+ <title>Sheet.111</title>
+ <path d="M0 331.97 C0 330.32 2.27 328.96 5.04 328.96 L37.61 328.96 L60.43 284.85 L53.72 328.96 L59.43 328.96 C62.22 328.96
+ 64.47 330.32 64.47 331.97 L64.47 331.97 L64.47 336.48 L64.47 344.01 C64.47 345.67 62.22 347.02 59.43 347.02
+ L53.72 347.02 L37.61 347.02 L37.61 347.02 L5.04 347.02 C2.27 347.02 0 345.67 0 344.01 L0 336.48 L0 331.97
+ L0 331.97 Z" class="st5"/>
+ </g>
+ <g id="shape112-282" v:mID="112" v:groupContext="shape" transform="translate(196.65,-80.2991)">
+ <title>Sheet.112</title>
+ <desc>group id</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="27.7691" cy="339.824" width="55.54" height="14.3829"/>
+ <path d="M55.54 332.63 L0 332.63 L0 347.02 L55.54 347.02 L55.54 332.63" class="st3"/>
+ <text x="5.09" y="343.42" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>group id</text> </g>
+ <g id="shape114-286" v:mID="114" v:groupContext="shape" transform="translate(506.433,-128.007)">
+ <title>Sheet.114</title>
+ <desc>-</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="6.63728" cy="336.229" width="13.28" height="21.5726"/>
+ <path d="M13.27 325.44 L0 325.44 L0 347.02 L13.27 347.02 L13.27 325.44" class="st3"/>
+ <text x="3.06" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>-</text> </g>
+ <g id="shape115-290" v:mID="115" v:groupContext="shape" transform="translate(529.004,-128.007)">
+ <title>Sheet.115</title>
+ <desc>Keys separated into</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="97.1729" cy="336.229" width="194.35" height="21.5726"/>
+ <path d="M194.35 325.44 L0 325.44 L0 347.02 L194.35 347.02 L194.35 325.44" class="st3"/>
+ <text x="17.06" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Keys separated into </text> </g>
+ <g id="shape116-294" v:mID="116" v:groupContext="shape" transform="translate(529.004,-106.438)">
+ <title>Sheet.116</title>
+ <desc>groups based on</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="83.1587" cy="336.229" width="166.32" height="21.5726"/>
+ <path d="M166.32 325.44 L0 325.44 L0 347.02 L166.32 347.02 L166.32 325.44" class="st3"/>
+ <text x="15.23" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>groups based on </text> </g>
+ <g id="shape117-298" v:mID="117" v:groupContext="shape" transform="translate(529.004,-84.869)">
+ <title>Sheet.117</title>
+ <desc>some bits from hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="97.731" cy="336.229" width="195.47" height="21.5726"/>
+ <path d="M195.46 325.44 L0 325.44 L0 347.02 L195.46 347.02 L195.46 325.44" class="st3"/>
+ <text x="14.94" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>some bits from hash</text> </g>
+ <g id="shape118-302" v:mID="118" v:groupContext="shape" transform="translate(506.433,-63.2999)">
+ <title>Sheet.118</title>
+ <desc>-</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="6.63728" cy="336.229" width="13.28" height="21.5726"/>
+ <path d="M13.27 325.44 L0 325.44 L0 347.02 L13.27 347.02 L13.27 325.44" class="st3"/>
+ <text x="3.06" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>-</text> </g>
+ <g id="shape119-306" v:mID="119" v:groupContext="shape" transform="translate(529.004,-63.2999)">
+ <title>Sheet.119</title>
+ <desc>Groups contain a</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="84.2539" cy="336.229" width="168.51" height="21.5726"/>
+ <path d="M168.51 325.44 L0 325.44 L0 347.02 L168.51 347.02 L168.51 325.44" class="st3"/>
+ <text x="15.38" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Groups contain a </text> </g>
+ <g id="shape120-310" v:mID="120" v:groupContext="shape" transform="translate(529.004,-41.7308)">
+ <title>Sheet.120</title>
+ <desc>small number of</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="81.4635" cy="336.229" width="162.93" height="21.5726"/>
+ <path d="M162.93 325.44 L0 325.44 L0 347.02 L162.93 347.02 L162.93 325.44" class="st3"/>
+ <text x="15.01" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>small number of </text> </g>
+ <g id="shape121-314" v:mID="121" v:groupContext="shape" transform="translate(529.004,-20.1617)">
+ <title>Sheet.121</title>
+ <desc>keys (<28)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.4481" cy="336.229" width="100.9" height="21.5726"/>
+ <path d="M100.9 325.44 L0 325.44 L0 347.02 L100.9 347.02 L100.9 325.44" class="st3"/>
+ <text x="8.77" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>keys (<28)</text> </g>
+ <g id="shape122-318" v:mID="122" v:groupContext="shape" transform="translate(19.1996,-146.276)">
+ <title>Sheet.122</title>
+ <path d="M0 310.17 C-0 306.1 3.62 302.8 8.07 302.8 L14.46 302.8 L29.68 282.28 L36.14 302.8 L78.65 302.8 C83.11 302.8
+ 86.72 306.1 86.72 310.17 L86.72 310.17 L86.72 321.22 L86.72 339.65 C86.72 343.72 83.11 347.02 78.65 347.02
+ L36.14 347.02 L14.46 347.02 L14.46 347.02 L8.07 347.02 C3.62 347.02 0 343.72 0 339.65 L0 321.22 L0 310.17
+ L0 310.17 Z" class="st5"/>
+ </g>
+ <g id="shape123-320" v:mID="123" v:groupContext="shape" transform="translate(41.9777,-174.053)">
+ <title>Sheet.123</title>
+ <desc>Group</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="22.8289" cy="339.824" width="45.66" height="14.3829"/>
+ <path d="M45.66 332.63 L0 332.63 L0 347.02 L45.66 347.02 L45.66 332.63" class="st3"/>
+ <text x="5.9" y="343.42" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group </text> </g>
+ <g id="shape124-324" v:mID="124" v:groupContext="shape" transform="translate(34.4142,-159.674)">
+ <title>Sheet.124</title>
+ <desc>Identifier</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="31.5173" cy="339.824" width="63.04" height="14.3829"/>
+ <path d="M63.03 332.63 L0 332.63 L0 347.02 L63.03 347.02 L63.03 332.63" class="st3"/>
+ <text x="7.04" y="343.42" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Identifier </text> </g>
+ <g id="shape125-328" v:mID="125" v:groupContext="shape" transform="translate(28.7716,-145.295)">
+ <title>Sheet.125</title>
+ <desc>(simplified)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="36.2165" cy="339.824" width="72.44" height="14.3829"/>
+ <path d="M72.43 332.63 L0 332.63 L0 347.02 L72.43 347.02 L72.43 332.63" class="st3"/>
+ <text x="6.19" y="343.42" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(simplified)</text> </g>
+ <g id="shape127-332" v:mID="127" v:groupContext="shape" transform="translate(517.688,-71.2991)">
+ <title>Sheet.127</title>
+ <desc>Keys separated into groups based on some bits from hash. Grou...</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="112.5" cy="302.139" width="225" height="89.7513"/>
+ <g id="shadow127-333" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st19">
+ <rect x="0" y="257.264" width="225" height="89.7513" class="st20"/>
+ </g>
+ <rect x="0" y="257.264" width="225" height="89.7513" class="st21"/>
+ <text x="4" y="281.09" class="st22" v:langID="1033"><v:paragraph v:indentFirst="-18" v:indentLeft="18" v:bullet="1"/><v:tabList/><tspan
+ class="st23" v:isBullet="true">·</tspan> <tspan class="st24">Keys separated into groups based </tspan><tspan
+ x="22" dy="1.204em" class="st24">on some bits from hash</tspan><tspan class="st24">.<v:newlineChar/></tspan><tspan
+ x="4" dy="1.211em" class="st23" v:isBullet="true">·</tspan> <tspan class="st24">Groups contain a small number of </tspan><tspan
+ x="22" dy="1.204em" class="st24">keys </tspan><tspan class="st24">(</tspan><tspan class="st24"><</tspan><tspan
+ class="st24">28</tspan><tspan class="st24">)</tspan></text> </g>
+ <g id="shape129-349" v:mID="129" v:groupContext="shape" transform="translate(336.326,-26.2991)">
+ <title>Sheet.129</title>
+ <desc>Total # of keys in group so far</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="39.6784" cy="333.515" width="79.36" height="27"/>
+ <rect x="0" y="320.015" width="79.3567" height="27" class="st25"/>
+ <text x="4.5" y="329.92" class="st26" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Total # of keys <tspan
+ x="4.39" dy="1.2em" class="st23">in group so far</tspan></text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i8.svg b/doc/guides/prog_guide/img/efd_i8.svg
new file mode 100644
index 0000000..d0fd463
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i8.svg
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i9.svg Page-2 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="4.98372in" height="2.08442in"
+ viewBox="0 0 358.828 150.078" xml:space="preserve" color-interpolation-filters="sRGB" class="st8">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st4 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st5 {fill:#000000;font-family:Intel Clear;font-size:0.998566em}
+ .st6 {fill:#c00000;font-family:Intel Clear;font-size:0.828804em;font-weight:bold}
+ .st7 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st8 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="4" v:index="2" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-2</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape4-1" v:mID="4" v:groupContext="shape" transform="translate(206.306,-19.0195)">
+ <title>Sheet.4</title>
+ <path d="M0 38.04 L0 150.08 L133.5 150.08 L133.5 38.04 L0 38.04 L0 38.04 Z" class="st1"/>
+ </g>
+ <g id="shape5-3" v:mID="5" v:groupContext="shape" transform="translate(206.306,-19.0195)">
+ <title>Sheet.5</title>
+ <path d="M0 38.04 L133.5 38.04 L133.5 150.08 L0 150.08 L0 38.04" class="st2"/>
+ </g>
+ <g id="shape6-6" v:mID="6" v:groupContext="shape" transform="translate(215.55,-70.7853)">
+ <title>Sheet.6</title>
+ <path d="M0 121.92 C0 118.82 2.54 116.29 5.64 116.29 L110.69 116.29 C113.81 116.29 116.33 118.82 116.33 121.92 L116.33
+ 144.45 C116.33 147.56 113.81 150.08 110.69 150.08 L5.64 150.08 C2.54 150.08 0 147.56 0 144.45 L0 121.92
+ Z" class="st1"/>
+ </g>
+ <g id="shape7-8" v:mID="7" v:groupContext="shape" transform="translate(215.55,-70.7853)">
+ <title>Sheet.7</title>
+ <path d="M0 121.92 C0 118.82 2.54 116.29 5.64 116.29 L110.69 116.29 C113.81 116.29 116.33 118.82 116.33 121.92 L116.33
+ 144.45 C116.33 147.56 113.81 150.08 110.69 150.08 L5.64 150.08 C2.54 150.08 0 147.56 0 144.45 L0 121.92
+ Z" class="st3"/>
+ </g>
+ <g id="shape8-10" v:mID="8" v:groupContext="shape" transform="translate(242.756,-86.1914)">
+ <title>Sheet.8</title>
+ <desc>hash_index</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="36.9951" cy="142.887" width="74" height="14.3829"/>
+ <path d="M73.99 135.7 L0 135.7 L0 150.08 L73.99 150.08 L73.99 135.7" class="st4"/>
+ <text x="6.29" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash_index</text> </g>
+ <g id="shape9-14" v:mID="9" v:groupContext="shape" transform="translate(229.67,-71.812)">
+ <title>Sheet.9</title>
+ <desc>(integer, 16 bits)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="52.0635" cy="142.887" width="104.13" height="14.3829"/>
+ <path d="M104.13 135.7 L0 135.7 L0 150.08 L104.13 150.08 L104.13 135.7" class="st4"/>
+ <text x="8.25" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(integer, 16 bits)</text> </g>
+ <g id="shape10-18" v:mID="10" v:groupContext="shape" transform="translate(215.55,-27.1678)">
+ <title>Sheet.10</title>
+ <path d="M0 121.92 C0 118.82 2.54 116.29 5.64 116.29 L110.69 116.29 C113.81 116.29 116.33 118.82 116.33 121.92 L116.33
+ 144.45 C116.33 147.56 113.81 150.08 110.69 150.08 L5.64 150.08 C2.54 150.08 0 147.56 0 144.45 L0 121.92
+ Z" class="st1"/>
+ </g>
+ <g id="shape11-20" v:mID="11" v:groupContext="shape" transform="translate(215.55,-27.1678)">
+ <title>Sheet.11</title>
+ <path d="M0 121.92 C0 118.82 2.54 116.29 5.64 116.29 L110.69 116.29 C113.81 116.29 116.33 118.82 116.33 121.92 L116.33
+ 144.45 C116.33 147.56 113.81 150.08 110.69 150.08 L5.64 150.08 C2.54 150.08 0 147.56 0 144.45 L0 121.92
+ Z" class="st3"/>
+ </g>
+ <g id="shape12-22" v:mID="12" v:groupContext="shape" transform="translate(237.836,-42.6033)">
+ <title>Sheet.12</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="42.693" cy="142.887" width="85.39" height="14.3829"/>
+ <path d="M85.39 135.7 L0 135.7 L0 150.08 L85.39 150.08 L85.39 135.7" class="st4"/>
+ <text x="7.03" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape13-26" v:mID="13" v:groupContext="shape" transform="translate(251.643,-28.2239)">
+ <title>Sheet.13</title>
+ <desc>(16 bits)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="26.9562" cy="142.887" width="53.92" height="14.3829"/>
+ <path d="M53.91 135.7 L0 135.7 L0 150.08 L53.91 150.08 L53.91 135.7" class="st4"/>
+ <text x="4.98" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(16 bits)</text> </g>
+ <g id="shape14-30" v:mID="14" v:groupContext="shape" transform="translate(213.473,-114.303)">
+ <title>Sheet.14</title>
+ <desc>Group ID: 0x0102</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="47.976" cy="144.109" width="95.96" height="11.9384"/>
+ <path d="M95.95 138.14 L0 138.14 L0 150.08 L95.95 150.08 L95.95 138.14" class="st4"/>
+ <text x="7.47" y="147.09" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group ID: 0x0102</text> </g>
+ <g id="shape15-34" v:mID="15" v:groupContext="shape" transform="translate(19.0195,-99.4242)">
+ <title>Sheet.15</title>
+ <path d="M0 129.31 C0 127.02 1.87 125.15 4.16 125.15 L109.18 125.15 C111.47 125.15 113.33 127.02 113.33 129.31 L113.33
+ 145.93 C113.33 148.22 111.47 150.08 109.18 150.08 L4.16 150.08 C1.87 150.08 0 148.22 0 145.93 L0 129.31
+ Z" class="st1"/>
+ </g>
+ <g id="shape16-36" v:mID="16" v:groupContext="shape" transform="translate(19.0195,-99.4242)">
+ <title>Sheet.16</title>
+ <path d="M0 129.31 C0 127.02 1.87 125.15 4.16 125.15 L109.18 125.15 C111.47 125.15 113.33 127.02 113.33 129.31 L113.33
+ 145.93 C113.33 148.22 111.47 150.08 109.18 150.08 L4.16 150.08 C1.87 150.08 0 148.22 0 145.93 L0 129.31
+ Z" class="st3"/>
+ </g>
+ <g id="shape17-38" v:mID="17" v:groupContext="shape" transform="translate(33.9485,-103.285)">
+ <title>Sheet.17</title>
+ <desc>Key1: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="49.6862" cy="142.887" width="99.38" height="14.3829"/>
+ <path d="M99.37 135.7 L0 135.7 L0 150.08 L99.37 150.08 L99.37 135.7" class="st4"/>
+ <text x="7.94" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1: Value = 0</text> </g>
+ <g id="shape18-42" v:mID="18" v:groupContext="shape" transform="translate(19.0195,-74.6198)">
+ <title>Sheet.18</title>
+ <path d="M0 129.31 C0 127.02 1.87 125.15 4.16 125.15 L109.18 125.15 C111.47 125.15 113.33 127.02 113.33 129.31 L113.33
+ 145.93 C113.33 148.22 111.47 150.08 109.18 150.08 L4.16 150.08 C1.87 150.08 0 148.22 0 145.93 L0 129.31
+ Z" class="st1"/>
+ </g>
+ <g id="shape19-44" v:mID="19" v:groupContext="shape" transform="translate(19.0195,-74.6198)">
+ <title>Sheet.19</title>
+ <path d="M0 129.31 C0 127.02 1.87 125.15 4.16 125.15 L109.18 125.15 C111.47 125.15 113.33 127.02 113.33 129.31 L113.33
+ 145.93 C113.33 148.22 111.47 150.08 109.18 150.08 L4.16 150.08 C1.87 150.08 0 148.22 0 145.93 L0 129.31
+ Z" class="st3"/>
+ </g>
+ <g id="shape20-46" v:mID="20" v:groupContext="shape" transform="translate(33.9485,-78.4626)">
+ <title>Sheet.20</title>
+ <desc>Key3: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="49.6862" cy="142.887" width="99.38" height="14.3829"/>
+ <path d="M99.37 135.7 L0 135.7 L0 150.08 L99.37 150.08 L99.37 135.7" class="st4"/>
+ <text x="7.94" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3: Value = 1</text> </g>
+ <g id="shape21-50" v:mID="21" v:groupContext="shape" transform="translate(19.0195,-49.5757)">
+ <title>Sheet.21</title>
+ <path d="M0 129.21 C0 126.91 1.88 125.03 4.19 125.03 L109.15 125.03 C111.46 125.03 113.33 126.91 113.33 129.21 L113.33
+ 145.91 C113.33 148.21 111.46 150.08 109.15 150.08 L4.19 150.08 C1.88 150.08 0 148.21 0 145.91 L0 129.21
+ Z" class="st1"/>
+ </g>
+ <g id="shape22-52" v:mID="22" v:groupContext="shape" transform="translate(19.0195,-49.5757)">
+ <title>Sheet.22</title>
+ <path d="M0 129.21 C0 126.91 1.88 125.03 4.19 125.03 L109.15 125.03 C111.46 125.03 113.33 126.91 113.33 129.21 L113.33
+ 145.91 C113.33 148.21 111.46 150.08 109.15 150.08 L4.19 150.08 C1.88 150.08 0 148.21 0 145.91 L0 129.21
+ Z" class="st3"/>
+ </g>
+ <g id="shape23-54" v:mID="23" v:groupContext="shape" transform="translate(33.9485,-53.4903)">
+ <title>Sheet.23</title>
+ <desc>Key4: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="49.6862" cy="142.887" width="99.38" height="14.3829"/>
+ <path d="M99.37 135.7 L0 135.7 L0 150.08 L99.37 150.08 L99.37 135.7" class="st4"/>
+ <text x="7.94" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4: Value = 0</text> </g>
+ <g id="shape24-58" v:mID="24" v:groupContext="shape" transform="translate(19.0195,-25.0109)">
+ <title>Sheet.24</title>
+ <path d="M0 129.21 C0 126.91 1.88 125.03 4.19 125.03 L109.15 125.03 C111.46 125.03 113.33 126.91 113.33 129.21 L113.33
+ 145.91 C113.33 148.21 111.46 150.08 109.15 150.08 L4.19 150.08 C1.88 150.08 0 148.21 0 145.91 L0 129.21
+ Z" class="st1"/>
+ </g>
+ <g id="shape25-60" v:mID="25" v:groupContext="shape" transform="translate(19.0195,-25.0109)">
+ <title>Sheet.25</title>
+ <path d="M0 129.21 C0 126.91 1.88 125.03 4.19 125.03 L109.15 125.03 C111.46 125.03 113.33 126.91 113.33 129.21 L113.33
+ 145.91 C113.33 148.21 111.46 150.08 109.15 150.08 L4.19 150.08 C1.88 150.08 0 148.21 0 145.91 L0 129.21
+ Z" class="st3"/>
+ </g>
+ <g id="shape26-62" v:mID="26" v:groupContext="shape" transform="translate(33.9485,-28.927)">
+ <title>Sheet.26</title>
+ <desc>Key7: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="49.6862" cy="142.887" width="99.38" height="14.3829"/>
+ <path d="M99.37 135.7 L0 135.7 L0 150.08 L99.37 150.08 L99.37 135.7" class="st4"/>
+ <text x="7.94" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7: Value = 1</text> </g>
+ <g id="shape27-66" v:mID="27" v:groupContext="shape" transform="translate(141.536,-51.5529)">
+ <title>Sheet.27</title>
+ <path d="M0 115.39 L22.75 115.39 L22.75 103.82 L45.5 126.95 L22.75 150.08 L22.75 138.51 L0 138.51 L0 115.39 Z"
+ class="st7"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i9.svg b/doc/guides/prog_guide/img/efd_i9.svg
new file mode 100644
index 0000000..b2e385d
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i9.svg
@@ -0,0 +1,390 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i10.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="9.8125in" height="3.76365in"
+ viewBox="0 0 706.5 270.983" xml:space="preserve" color-interpolation-filters="sRGB" class="st9">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st4 {fill:#000000;font-family:Arial;font-size:0.998566em}
+ .st5 {fill:#000000;font-family:Arial;font-size:0.918686em}
+ .st6 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st7 {fill:#ffffff;font-family:Arial;font-size:0.998566em}
+ .st8 {fill:#ffffff;font-family:Arial;font-size:1.49785em}
+ .st9 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape68-1" v:mID="68" v:groupContext="shape" transform="translate(196.523,-158.978)">
+ <title>Sheet.68</title>
+ <path d="M0 250.22 C0 247.95 1.89 246.06 4.17 246.06 L317.25 246.06 C319.53 246.06 321.39 247.95 321.39 250.22 L321.39
+ 266.85 C321.39 269.13 319.53 270.98 317.25 270.98 L4.17 270.98 C1.89 270.98 0 269.13 0 266.85 L0 250.22
+ Z" class="st1"/>
+ </g>
+ <g id="shape69-3" v:mID="69" v:groupContext="shape" transform="translate(196.523,-158.978)">
+ <title>Sheet.69</title>
+ <path d="M0 250.22 C0 247.95 1.89 246.06 4.17 246.06 L317.25 246.06 C319.53 246.06 321.39 247.95 321.39 250.22 L321.39
+ 266.85 C321.39 269.13 319.53 270.98 317.25 270.98 L4.17 270.98 C1.89 270.98 0 269.13 0 266.85 L0 250.22
+ Z" class="st2"/>
+ </g>
+ <g id="shape70-5" v:mID="70" v:groupContext="shape" transform="translate(186.139,-162.437)">
+ <title>Sheet.70</title>
+ <desc>(hash(key, seed1) + hash_index *</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="106.671" cy="263.792" width="213.35" height="14.3829"/>
+ <path d="M213.34 256.6 L0 256.6 L0 270.98 L213.34 270.98 L213.34 256.6" class="st3"/>
+ <text x="17.24" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(hash(key, seed1) + hash_index * </text> </g>
+ <g id="shape71-9" v:mID="71" v:groupContext="shape" transform="translate(381.48,-162.845)">
+ <title>Sheet.71</title>
+ <desc>hash(key</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="27.4843" cy="264.367" width="54.97" height="13.2327"/>
+ <path d="M54.97 257.75 L0 257.75 L0 270.98 L54.97 270.98 L54.97 257.75" class="st3"/>
+ <text x="5.12" y="267.67" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash(key</text> </g>
+ <g id="shape72-13" v:mID="72" v:groupContext="shape" transform="translate(424.755,-162.437)">
+ <title>Sheet.72</title>
+ <desc>, seed2)) % 16</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="46.7254" cy="263.792" width="93.46" height="14.3829"/>
+ <path d="M93.45 256.6 L0 256.6 L0 270.98 L93.45 270.98 L93.45 256.6" class="st3"/>
+ <text x="7.76" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>, seed2)) % 16</text> </g>
+ <g id="shape73-17" v:mID="73" v:groupContext="shape" transform="translate(524.094,-148.373)">
+ <title>Sheet.73</title>
+ <path d="M0 236.29 L22.75 236.29 L22.75 224.73 L45.5 247.86 L22.75 270.98 L22.75 259.42 L0 259.42 L0 236.29 Z"
+ class="st6"/>
+ </g>
+ <g id="shape74-19" v:mID="74" v:groupContext="shape" transform="translate(574.148,-217.574)">
+ <title>Sheet.74</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st1"/>
+ </g>
+ <g id="shape75-21" v:mID="75" v:groupContext="shape" transform="translate(574.148,-217.574)">
+ <title>Sheet.75</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st2"/>
+ </g>
+ <g id="shape76-23" v:mID="76" v:groupContext="shape" transform="translate(584.296,-231.499)">
+ <title>Sheet.76</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="40.993" cy="263.792" width="81.99" height="14.3829"/>
+ <path d="M81.99 256.6 L0 256.6 L0 270.98 L81.99 270.98 L81.99 256.6" class="st3"/>
+ <text x="7.01" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape77-27" v:mID="77" v:groupContext="shape" transform="translate(655.369,-231.499)">
+ <title>Sheet.77</title>
+ <desc>bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1076" cy="263.792" width="22.22" height="14.3829"/>
+ <path d="M22.22 256.6 L0 256.6 L0 270.98 L22.22 270.98 L22.22 256.6" class="st3"/>
+ <text x="4.78" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit </text> </g>
+ <g id="shape78-31" v:mID="78" v:groupContext="shape" transform="translate(588.858,-217.12)">
+ <title>Sheet.78</title>
+ <desc>index for key1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="44.8113" cy="263.792" width="89.63" height="14.3829"/>
+ <path d="M89.62 256.6 L0 256.6 L0 270.98 L89.62 270.98 L89.62 256.6" class="st3"/>
+ <text x="7.51" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>index for key1</text> </g>
+ <g id="shape79-35" v:mID="79" v:groupContext="shape" transform="translate(573.548,-178.869)">
+ <title>Sheet.79</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st1"/>
+ </g>
+ <g id="shape80-37" v:mID="80" v:groupContext="shape" transform="translate(573.548,-178.869)">
+ <title>Sheet.80</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st2"/>
+ </g>
+ <g id="shape81-39" v:mID="81" v:groupContext="shape" transform="translate(584.296,-192.768)">
+ <title>Sheet.81</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="40.993" cy="263.792" width="81.99" height="14.3829"/>
+ <path d="M81.99 256.6 L0 256.6 L0 270.98 L81.99 270.98 L81.99 256.6" class="st3"/>
+ <text x="7.01" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape82-43" v:mID="82" v:groupContext="shape" transform="translate(655.369,-192.768)">
+ <title>Sheet.82</title>
+ <desc>bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1076" cy="263.792" width="22.22" height="14.3829"/>
+ <path d="M22.22 256.6 L0 256.6 L0 270.98 L22.22 270.98 L22.22 256.6" class="st3"/>
+ <text x="4.78" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit </text> </g>
+ <g id="shape83-47" v:mID="83" v:groupContext="shape" transform="translate(588.858,-178.388)">
+ <title>Sheet.83</title>
+ <desc>index for key3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="44.8113" cy="263.792" width="89.63" height="14.3829"/>
+ <path d="M89.62 256.6 L0 256.6 L0 270.98 L89.62 270.98 L89.62 256.6" class="st3"/>
+ <text x="7.51" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>index for key3</text> </g>
+ <g id="shape84-51" v:mID="84" v:groupContext="shape" transform="translate(574.148,-139.326)">
+ <title>Sheet.84</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st1"/>
+ </g>
+ <g id="shape85-53" v:mID="85" v:groupContext="shape" transform="translate(574.148,-139.326)">
+ <title>Sheet.85</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st2"/>
+ </g>
+ <g id="shape86-55" v:mID="86" v:groupContext="shape" transform="translate(584.296,-153.227)">
+ <title>Sheet.86</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="40.993" cy="263.792" width="81.99" height="14.3829"/>
+ <path d="M81.99 256.6 L0 256.6 L0 270.98 L81.99 270.98 L81.99 256.6" class="st3"/>
+ <text x="7.01" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape87-59" v:mID="87" v:groupContext="shape" transform="translate(655.369,-153.227)">
+ <title>Sheet.87</title>
+ <desc>bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1076" cy="263.792" width="22.22" height="14.3829"/>
+ <path d="M22.22 256.6 L0 256.6 L0 270.98 L22.22 270.98 L22.22 256.6" class="st3"/>
+ <text x="4.78" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit </text> </g>
+ <g id="shape88-63" v:mID="88" v:groupContext="shape" transform="translate(588.858,-138.848)">
+ <title>Sheet.88</title>
+ <desc>index for key4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="44.8113" cy="263.792" width="89.63" height="14.3829"/>
+ <path d="M89.62 256.6 L0 256.6 L0 270.98 L89.62 270.98 L89.62 256.6" class="st3"/>
+ <text x="7.51" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>index for key4</text> </g>
+ <g id="shape89-67" v:mID="89" v:groupContext="shape" transform="translate(574.148,-100.622)">
+ <title>Sheet.89</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st1"/>
+ </g>
+ <g id="shape90-69" v:mID="90" v:groupContext="shape" transform="translate(574.148,-100.622)">
+ <title>Sheet.90</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st2"/>
+ </g>
+ <g id="shape91-71" v:mID="91" v:groupContext="shape" transform="translate(584.296,-114.496)">
+ <title>Sheet.91</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="40.993" cy="263.792" width="81.99" height="14.3829"/>
+ <path d="M81.99 256.6 L0 256.6 L0 270.98 L81.99 270.98 L81.99 256.6" class="st3"/>
+ <text x="7.01" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape92-75" v:mID="92" v:groupContext="shape" transform="translate(655.369,-114.496)">
+ <title>Sheet.92</title>
+ <desc>bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1076" cy="263.792" width="22.22" height="14.3829"/>
+ <path d="M22.22 256.6 L0 256.6 L0 270.98 L22.22 270.98 L22.22 256.6" class="st3"/>
+ <text x="4.78" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit </text> </g>
+ <g id="shape93-79" v:mID="93" v:groupContext="shape" transform="translate(588.858,-100.117)">
+ <title>Sheet.93</title>
+ <desc>index for key7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="44.8113" cy="263.792" width="89.63" height="14.3829"/>
+ <path d="M89.62 256.6 L0 256.6 L0 270.98 L89.62 270.98 L89.62 256.6" class="st3"/>
+ <text x="7.51" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>index for key7</text> </g>
+ <g id="shape94-83" v:mID="94" v:groupContext="shape" transform="translate(205.227,-191.137)">
+ <title>Sheet.94</title>
+ <path d="M0 217.76 C0 213 3.87 209.14 8.64 209.14 L14.53 209.14 L14.53 209.14 L36.32 209.14 L78.52 209.14 C83.3 209.14
+ 87.16 213 87.16 217.76 L87.16 239.33 L87.16 239.33 L87.16 252.27 L87.16 252.27 C87.16 257.05 83.3 260.9
+ 78.52 260.9 L36.32 260.9 L18.46 270.98 L14.53 260.9 L8.64 260.9 C3.87 260.9 0 257.05 0 252.27 L0 239.33
+ L0 239.33 L0 217.76 Z" class="st6"/>
+ </g>
+ <g id="shape95-85" v:mID="95" v:groupContext="shape" transform="translate(214.98,-225.215)">
+ <title>Sheet.95</title>
+ <desc>CRC32 (32</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="38.2947" cy="263.792" width="76.59" height="14.3829"/>
+ <path d="M76.59 256.6 L0 256.6 L0 270.98 L76.59 270.98 L76.59 256.6" class="st3"/>
+ <text x="8.33" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>CRC32 (32 </text> </g>
+ <g id="shape96-89" v:mID="96" v:groupContext="shape" transform="translate(222.123,-210.835)">
+ <title>Sheet.96</title>
+ <desc>bit output)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="32.5584" cy="263.792" width="65.12" height="14.3829"/>
+ <path d="M65.12 256.6 L0 256.6 L0 270.98 L65.12 270.98 L65.12 256.6" class="st3"/>
+ <text x="5.91" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit output)</text> </g>
+ <g id="shape97-93" v:mID="97" v:groupContext="shape" transform="translate(305.473,-188.366)">
+ <title>Sheet.97</title>
+ <path d="M0 226.84 C0 223.28 2.9 220.39 6.47 220.39 L21.37 220.39 L21.37 220.39 L53.42 220.39 L121.77 220.39 C125.34
+ 220.39 128.22 223.28 128.22 226.84 L128.22 242.97 L128.22 242.97 L128.22 252.65 L128.22 252.65 C128.22 256.21
+ 125.34 259.09 121.77 259.09 L53.42 259.09 L38.73 270.98 L21.37 259.09 L6.47 259.09 C2.9 259.09 0 256.21
+ 0 252.65 L0 242.97 L0 242.97 L0 226.84 Z" class="st6"/>
+ </g>
+ <g id="shape98-95" v:mID="98" v:groupContext="shape" transform="translate(318.48,-217.733)">
+ <title>Sheet.98</title>
+ <desc>Goal: Find a valid</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="57.4478" cy="263.792" width="114.9" height="14.3829"/>
+ <path d="M114.9 256.6 L0 256.6 L0 270.98 L114.9 270.98 L114.9 256.6" class="st3"/>
+ <text x="10.82" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Goal: Find a valid </text> </g>
+ <g id="shape99-99" v:mID="99" v:groupContext="shape" transform="translate(339.077,-203.354)">
+ <title>Sheet.99</title>
+ <desc>hash_index</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="37.1611" cy="263.792" width="74.33" height="14.3829"/>
+ <path d="M74.32 256.6 L0 256.6 L0 270.98 L74.32 270.98 L74.32 256.6" class="st3"/>
+ <text x="6.51" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash_index</text> </g>
+ <g id="shape100-103" v:mID="100" v:groupContext="shape" transform="translate(438.135,-185.939)">
+ <title>Sheet.100</title>
+ <path d="M0 217.36 C0 213.8 2.91 210.89 6.48 210.89 L21.37 210.89 L21.37 210.89 L53.42 210.89 L121.77 210.89 C125.34
+ 210.89 128.22 213.8 128.22 217.36 L128.22 233.48 L128.22 233.48 L128.22 243.15 L128.22 243.15 C128.22 246.72
+ 125.34 249.59 121.77 249.59 L53.42 249.59 L54.75 270.98 L21.37 249.59 L6.48 249.59 C2.91 249.59 0 246.72
+ 0 243.15 L0 233.48 L0 233.48 L0 217.36 Z" class="st6"/>
+ </g>
+ <g id="shape101-105" v:mID="101" v:groupContext="shape" transform="translate(448.763,-224.802)">
+ <title>Sheet.101</title>
+ <desc>Lookup Table has</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.6085" cy="263.792" width="117.22" height="14.3829"/>
+ <path d="M117.22 256.6 L0 256.6 L0 270.98 L117.22 270.98 L117.22 256.6" class="st3"/>
+ <text x="10.98" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Lookup Table has </text> </g>
+ <g id="shape102-109" v:mID="102" v:groupContext="shape" transform="translate(484.549,-210.423)">
+ <title>Sheet.102</title>
+ <desc>16 bits</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="22.2166" cy="263.792" width="44.44" height="14.3829"/>
+ <path d="M44.43 256.6 L0 256.6 L0 270.98 L44.43 270.98 L44.43 256.6" class="st3"/>
+ <text x="4.56" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>16 bits</text> </g>
+ <g id="shape103-113" v:mID="103" v:groupContext="shape" transform="translate(369.583,-90.8555)">
+ <title>Sheet.103</title>
+ <path d="M0 227.76 C0 222.98 3.89 219.1 8.67 219.1 L14.53 219.1 L34.47 205.09 L36.32 219.1 L78.5 219.1 C83.29 219.1 87.16
+ 222.98 87.16 227.76 L87.16 227.76 L87.16 240.73 L87.16 262.34 C87.16 267.12 83.29 270.98 78.5 270.98 L36.32
+ 270.98 L14.53 270.98 L14.53 270.98 L8.67 270.98 C3.89 270.98 0 267.12 0 262.34 L0 240.73 L0 227.76 L0 227.76
+ Z" class="st6"/>
+ </g>
+ <g id="shape104-115" v:mID="104" v:groupContext="shape" transform="translate(383.264,-114.932)">
+ <title>Sheet.104</title>
+ <desc>CRC32 (32</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="38.2947" cy="263.792" width="76.59" height="14.3829"/>
+ <path d="M76.59 256.6 L0 256.6 L0 270.98 L76.59 270.98 L76.59 256.6" class="st3"/>
+ <text x="8.33" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>CRC32 (32 </text> </g>
+ <g id="shape105-119" v:mID="105" v:groupContext="shape" transform="translate(386.505,-100.553)">
+ <title>Sheet.105</title>
+ <desc>bit output)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="32.5584" cy="263.792" width="65.12" height="14.3829"/>
+ <path d="M65.12 256.6 L0 256.6 L0 270.98 L65.12 270.98 L65.12 256.6" class="st3"/>
+ <text x="5.91" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit output)</text> </g>
+ <g id="shape106-123" v:mID="106" v:groupContext="shape" transform="translate(313.397,-18)">
+ <title>Sheet.106</title>
+ <path d="M0 226.35 C0 221.43 4.02 217.42 8.94 217.42 L347.02 217.42 C351.97 217.42 355.96 221.43 355.96 226.35 L355.96
+ 262.06 C355.96 267 351.97 270.98 347.02 270.98 L8.94 270.98 C4.02 270.98 0 267 0 262.06 L0 226.35 Z"
+ class="st6"/>
+ </g>
+ <g id="shape107-125" v:mID="107" v:groupContext="shape" transform="translate(313.98,-41.963)">
+ <title>Sheet.107</title>
+ <desc>Goal is to find a hash_index that produces</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="177.75" cy="260.197" width="355.5" height="21.5726"/>
+ <path d="M355.5 249.41 L0 249.41 L0 270.98 L355.5 270.98 L355.5 249.41" class="st3"/>
+ <text x="9.88" y="265.59" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Goal is to find a hash_index that produces </text> </g>
+ <g id="shape108-129" v:mID="108" v:groupContext="shape" transform="translate(318.48,-20.3939)">
+ <title>Sheet.108</title>
+ <desc>a lookup_table with no contradictions</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="175.5" cy="260.197" width="351" height="21.5726"/>
+ <path d="M351 249.41 L0 249.41 L0 270.98 L351 270.98 L351 249.41" class="st3"/>
+ <text x="28.12" y="265.59" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>a lookup_table with no contradictions</text> </g>
+ <g id="shape109-133" v:mID="109" v:groupContext="shape" transform="translate(18,-196.244)">
+ <title>Sheet.109</title>
+ <path d="M0 250.22 C0 247.92 1.87 246.06 4.16 246.06 L109.18 246.06 C111.47 246.06 113.33 247.92 113.33 250.22 L113.33
+ 266.83 C113.33 269.13 111.47 270.98 109.18 270.98 L4.16 270.98 C1.87 270.98 0 269.13 0 266.83 L0 250.22
+ Z" class="st1"/>
+ </g>
+ <g id="shape110-135" v:mID="110" v:groupContext="shape" transform="translate(29.8201,-196.244)">
+ <title>Sheet.110</title>
+ <path d="M0 250.22 C-0 247.92 1.67 246.06 3.73 246.06 L97.79 246.06 C99.85 246.06 101.51 247.92 101.51 250.22 L101.51
+ 266.83 C101.51 269.13 99.85 270.98 97.79 270.98 L3.73 270.98 C1.67 270.98 0 269.13 0 266.83 L0 250.22 Z"
+ class="st2"/>
+ </g>
+ <g id="shape111-137" v:mID="111" v:groupContext="shape" transform="translate(32.5663,-199.746)">
+ <title>Sheet.111</title>
+ <desc>Key1: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.7562" cy="263.792" width="101.52" height="14.3829"/>
+ <path d="M101.51 256.6 L0 256.6 L0 270.98 L101.51 270.98 L101.51 256.6" class="st3"/>
+ <text x="8.29" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1: Value = 0</text> </g>
+ <g id="shape112-141" v:mID="112" v:groupContext="shape" transform="translate(18,-171.44)">
+ <title>Sheet.112</title>
+ <path d="M0 250.22 C0 247.92 1.87 246.06 4.16 246.06 L109.18 246.06 C111.47 246.06 113.33 247.92 113.33 250.22 L113.33
+ 266.83 C113.33 269.13 111.47 270.98 109.18 270.98 L4.16 270.98 C1.87 270.98 0 269.13 0 266.83 L0 250.22
+ Z" class="st1"/>
+ </g>
+ <g id="shape113-143" v:mID="113" v:groupContext="shape" transform="translate(29.8201,-171.44)">
+ <title>Sheet.113</title>
+ <path d="M0 250.22 C0 247.92 1.67 246.06 3.73 246.06 L97.79 246.06 C99.85 246.06 101.51 247.92 101.51 250.22 L101.51
+ 266.83 C101.51 269.13 99.85 270.98 97.79 270.98 L3.73 270.98 C1.67 270.98 0 269.13 0 266.83 L0 250.22 Z"
+ class="st2"/>
+ </g>
+ <g id="shape114-145" v:mID="114" v:groupContext="shape" transform="translate(32.5663,-174.923)">
+ <title>Sheet.114</title>
+ <desc>Key3: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.7562" cy="263.792" width="101.52" height="14.3829"/>
+ <path d="M101.51 256.6 L0 256.6 L0 270.98 L101.51 270.98 L101.51 256.6" class="st3"/>
+ <text x="8.29" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3: Value = 1</text> </g>
+ <g id="shape115-149" v:mID="115" v:groupContext="shape" transform="translate(18,-146.396)">
+ <title>Sheet.115</title>
+ <path d="M0 250.12 C0 247.81 1.88 245.94 4.19 245.94 L109.15 245.94 C111.46 245.94 113.33 247.81 113.33 250.12 L113.33
+ 266.81 C113.33 269.12 111.46 270.98 109.15 270.98 L4.19 270.98 C1.88 270.98 0 269.12 0 266.81 L0 250.12
+ Z" class="st1"/>
+ </g>
+ <g id="shape116-151" v:mID="116" v:groupContext="shape" transform="translate(29.8201,-146.396)">
+ <title>Sheet.116</title>
+ <path d="M0 250.12 C0 247.81 1.68 245.94 3.75 245.94 L97.77 245.94 C99.84 245.94 101.51 247.81 101.51 250.12 L101.51
+ 266.81 C101.51 269.12 99.84 270.98 97.77 270.98 L3.75 270.98 C1.68 270.98 0 269.12 0 266.81 L0 250.12 Z"
+ class="st2"/>
+ </g>
+ <g id="shape117-153" v:mID="117" v:groupContext="shape" transform="translate(32.5663,-149.951)">
+ <title>Sheet.117</title>
+ <desc>Key4: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.7562" cy="263.792" width="101.52" height="14.3829"/>
+ <path d="M101.51 256.6 L0 256.6 L0 270.98 L101.51 270.98 L101.51 256.6" class="st3"/>
+ <text x="8.29" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4: Value = 0</text> </g>
+ <g id="shape118-157" v:mID="118" v:groupContext="shape" transform="translate(18,-121.831)">
+ <title>Sheet.118</title>
+ <path d="M0 250.12 C0 247.81 1.88 245.94 4.19 245.94 L109.15 245.94 C111.46 245.94 113.33 247.81 113.33 250.12 L113.33
+ 266.81 C113.33 269.12 111.46 270.98 109.15 270.98 L4.19 270.98 C1.88 270.98 0 269.12 0 266.81 L0 250.12
+ Z" class="st1"/>
+ </g>
+ <g id="shape119-159" v:mID="119" v:groupContext="shape" transform="translate(29.8201,-121.831)">
+ <title>Sheet.119</title>
+ <path d="M0 250.12 C0 247.81 1.68 245.94 3.75 245.94 L97.77 245.94 C99.84 245.94 101.51 247.81 101.51 250.12 L101.51
+ 266.81 C101.51 269.12 99.84 270.98 97.77 270.98 L3.75 270.98 C1.68 270.98 0 269.12 0 266.81 L0 250.12 Z"
+ class="st2"/>
+ </g>
+ <g id="shape120-161" v:mID="120" v:groupContext="shape" transform="translate(32.5663,-125.388)">
+ <title>Sheet.120</title>
+ <desc>Key7: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.7562" cy="263.792" width="101.52" height="14.3829"/>
+ <path d="M101.51 256.6 L0 256.6 L0 270.98 L101.51 270.98 L101.51 256.6" class="st3"/>
+ <text x="8.29" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7: Value = 1</text> </g>
+ <g id="shape121-165" v:mID="121" v:groupContext="shape" transform="translate(140.517,-148.373)">
+ <title>Sheet.121</title>
+ <path d="M0 236.29 L22.75 236.29 L22.75 224.73 L45.5 247.86 L22.75 270.98 L22.75 259.42 L0 259.42 L0 236.29 Z"
+ class="st6"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/index.rst b/doc/guides/prog_guide/index.rst
index ed7f770..7f825cb 100644
--- a/doc/guides/prog_guide/index.rst
+++ b/doc/guides/prog_guide/index.rst
@@ -47,6 +47,7 @@ Programmer's Guide
link_bonding_poll_mode_drv_lib
timer_lib
hash_lib
+ efd_lib
lpm_lib
lpm6_lib
packet_distrib_lib
@@ -167,6 +168,28 @@ Programmer's Guide
:numref:`figure_figure39` :ref:`figure_figure39`
+:numref:`figure_efd1` :ref:`figure_efd1`
+
+:numref:`figure_efd2` :ref:`figure_efd2`
+
+:numref:`figure_efd3` :ref:`figure_efd3`
+
+:numref:`figure_efd4` :ref:`figure_efd4`
+
+:numref:`figure_efd5` :ref:`figure_efd5`
+
+:numref:`figure_efd6` :ref:`figure_efd6`
+
+:numref:`figure_efd7` :ref:`figure_efd7`
+
+:numref:`figure_efd8` :ref:`figure_efd8`
+
+:numref:`figure_efd9` :ref:`figure_efd9`
+
+:numref:`figure_efd10` :ref:`figure_efd10`
+
+:numref:`figure_efd11` :ref:`figure_efd11`
+
**Tables**
diff --git a/doc/guides/rel_notes/release_17_02.rst b/doc/guides/rel_notes/release_17_02.rst
index 32ea8d9..b0a032b 100644
--- a/doc/guides/rel_notes/release_17_02.rst
+++ b/doc/guides/rel_notes/release_17_02.rst
@@ -107,6 +107,9 @@ New Features
is much smaller than a hash-based flow table and therefore, it can better fit for
CPU cache, being able to scale to millions of flow keys.
+ See the :ref:`Elastic Flow Distributor Library <Efd_Library>` documentation in
+ the Programmers Guide document, for more information.
+
Resolved Issues
---------------
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v7 6/6] doc: add flow distributor guide
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 0/6] " Pablo de Lara
` (4 preceding siblings ...)
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 5/6] doc: add EFD library section in Programmers guide Pablo de Lara
@ 2017-01-17 22:10 ` Pablo de Lara
2017-01-17 22:18 ` [dpdk-dev] [PATCH v7 0/6] Elastic Flow Distributor De Lara Guarch, Pablo
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 " Pablo de Lara
7 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-17 22:10 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Sameh Gobriel
Signed-off-by: Sameh Gobriel <sameh.gobriel@intel.com>
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 1 +
doc/guides/sample_app_ug/flow_distributor.rst | 494 ++++++++
doc/guides/sample_app_ug/img/flow_distributor.svg | 1254 +++++++++++++++++++++
doc/guides/sample_app_ug/index.rst | 1 +
4 files changed, 1750 insertions(+)
create mode 100644 doc/guides/sample_app_ug/flow_distributor.rst
create mode 100644 doc/guides/sample_app_ug/img/flow_distributor.svg
diff --git a/MAINTAINERS b/MAINTAINERS
index 66e9466..0d3b247 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -535,6 +535,7 @@ F: lib/librte_efd/
F: doc/guides/prog_guide/efd_lib.rst
F: app/test/test_efd*
F: examples/flow_distributor/
+F: doc/guides/sample_app_ug/flow_distributor.rst
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
diff --git a/doc/guides/sample_app_ug/flow_distributor.rst b/doc/guides/sample_app_ug/flow_distributor.rst
new file mode 100644
index 0000000..a12df76
--- /dev/null
+++ b/doc/guides/sample_app_ug/flow_distributor.rst
@@ -0,0 +1,494 @@
+.. BSD LICENSE
+ Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of Intel Corporation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Flow Distributor Sample Application
+===================================
+
+This sample application demonstrates the use of EFD library as a flow-level
+load balancer, for more information about the EFD Library please refer to the
+DPDK programmer's guide.
+
+This sample application is a variant of the
+:ref:`client-server sample application <multi_process_app>`
+where a specific target node is specified for every and each flow
+(not in a round-robin fashion as the original load balancing sample application).
+
+Overview
+--------
+
+The architecture of the EFD flow-based load balancer sample application is
+presented in the following figure.
+
+.. _figure_efd_sample_app_overview:
+
+.. figure:: img/flow_distributor.*
+
+ Using EFD as a Flow-Level Load Balancer
+
+As shown in :numref:`figure_efd_sample_app_overview`,
+the sample application consists of a front-end node (distributor)
+using the EFD library to create a load-balancing table for flows,
+for each flow a target backend worker node is specified. The EFD table does not
+store the flow key (unlike a regular hash table), and hence, it can
+individually load-balance millions of flows (number of targets * maximum number
+of flows fit in a flow table per target) while still fitting in CPU cache.
+
+It should be noted that although they are referred to as nodes, the frontend
+distributor and worker nodes are processes running on the same platform.
+
+Front-end Distributor
+~~~~~~~~~~~~~~~~~~~~~
+
+Upon initializing, the frontend distributor node (process) creates a flow
+distributor table (based on the EFD library) which is populated with flow
+information and its intended target node.
+
+The sample application assigns a specific target node_id (process) for each of
+the IP destination addresses as follows:
+
+.. code-block:: c
+
+ node_id = i % num_nodes; /* Target node id is generated */
+ ip_dst = rte_cpu_to_be_32(i); /* Specific ip destination address is
+ assigned to this target node */
+
+then the pair of <key,target> is inserted into the flow distribution table.
+
+The main loop of the the distributor node receives a burst of packets, then for
+each packet, a flow key (IP destination address) is extracted. The flow
+distributor table is looked up and the target node id is returned. Packets are
+then enqueued to the specified target node id.
+
+It should be noted that flow distributor table is not a membership test table.
+I.e. if the key has already been inserted the target node id will be correct,
+but for new keys the flow distributor table will return a value (which can be
+valid).
+
+Backend Worker Nodes
+~~~~~~~~~~~~~~~~~~~~
+
+Upon initializing, the worker node (process) creates a flow table (a regular
+hash table that stores the key default size 1M flows) which is populated with
+only the flow information that is serviced at this node. This flow key is
+essential to point out new keys that have not been inserted before.
+
+The worker node's main loop is simply receiving packets then doing a hash table
+lookup. If a match occurs then statistics are updated for flows serviced by
+this node. If no match is found in the local hash table then this indicates
+that this is a new flow, which is dropped.
+
+
+Compiling the Application
+-------------------------
+
+The sequence of steps used to build the application is:
+
+#. Export the required environment variables:
+
+ .. code-block:: console
+
+ export RTE_SDK=/path/to/rte_sdk
+ export RTE_TARGET=x86_64-native-linuxapp-gcc
+
+#. Build the application executable file:
+
+ .. code-block:: console
+
+ cd ${RTE_SDK}/examples/flow_distributor/
+ make
+
+ For more details on how to build the DPDK libraries and sample
+ applications,
+ please refer to the *DPDK Getting Started Guide.*
+
+
+Running the Application
+-----------------------
+
+The application has two binaries to be run: the front-end distributor
+and the back-end node.
+
+The frontend distributor (distributor) has the following command line options::
+
+ ./distributor [EAL options] -- -p PORTMASK -n NUM_NODES -f NUM_FLOWS
+
+Where,
+
+* ``-p PORTMASK:`` Hexadecimal bitmask of ports to configure
+* ``-n NUM_NODES:`` Number of back-end nodes that will be used
+* ``-f NUM_FLOWS:`` Number of flows to be added in the EFD table (1 million, by default)
+
+The back-end node (node) has the following command line options::
+
+ ./node [EAL options] -- -n NODE_ID
+
+Where,
+
+* ``-n NODE_ID:`` Node ID, which cannot be equal or higher than NUM_MODES
+
+
+First, the distributor app must be launched, with the number of nodes that will be run.
+Once it has been started, the node instances can be run, with different NODE_ID.
+These instances have to be run as secondary processes, with ``--proc-type=secondary``
+in the EAL options, which will attach to the primary process memory, and therefore,
+they can access the queues created by the primary process to distribute packets.
+
+To successfully run the application, the command line used to start the
+application has to be in sync with the traffic flows configured on the traffic
+generator side.
+
+For examples of application command lines and traffic generator flows, please
+refer to the DPDK Test Report. For more details on how to set up and run the
+sample applications provided with DPDK package, please refer to the
+:ref:`DPDK Getting Started Guide for Linux <linux_gsg>` and
+:ref:`DPDK Getting Started Guide for FreeBSD <freebsd_gsg>`.
+
+
+Explanation
+-----------
+
+As described in previous sections, there are two processes in this example.
+
+The first process, the front-end distributor, creates and populates the EFD table,
+which is used to distribute packets to nodes, which the number of flows
+specified in the command line (1 million, by default).
+
+
+.. code-block:: c
+
+ static void
+ create_flow_distributor_table(void)
+ {
+ uint8_t socket_id = rte_socket_id();
+
+ /* create table */
+ efd_table = rte_efd_create("flow table", num_flows * 2, sizeof(uint32_t),
+ 1 << socket_id, socket_id);
+
+ if (efd_table == NULL)
+ rte_exit(EXIT_FAILURE, "Problem creating the flow table\n");
+ }
+
+ static void
+ populate_flow_distributor_table(void)
+ {
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint8_t socket_id = rte_socket_id();
+ uint64_t node_id;
+
+ /* Add flows in table */
+ for (i = 0; i < num_flows; i++) {
+ node_id = i % num_nodes;
+
+ ip_dst = rte_cpu_to_be_32(i);
+ ret = rte_efd_update(efd_table, socket_id,
+ (void *)&ip_dst, (efd_value_t)node_id);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u in "
+ "flow distributor table\n", i);
+ }
+
+ printf("EFD table: Adding 0x%x keys\n", num_flows);
+ }
+
+After initialization, packets are received from the enabled ports, and the IPv4
+address from the packets is used as a key to look up in the EFD table,
+which tells the node where the packet has to be distributed.
+
+.. code-block:: c
+
+ static void
+ process_packets(uint32_t port_num __rte_unused, struct rte_mbuf *pkts[],
+ uint16_t rx_count, unsigned int socket_id)
+ {
+ uint16_t i;
+ uint8_t node;
+ efd_value_t data[EFD_BURST_MAX];
+ const void *key_ptrs[EFD_BURST_MAX];
+
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[EFD_BURST_MAX];
+
+ for (i = 0; i < rx_count; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(pkts[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = (void *)&ipv4_dst_ip[i];
+ }
+
+ rte_efd_lookup_bulk(efd_table, socket_id, rx_count,
+ (const void **) key_ptrs, data);
+ for (i = 0; i < rx_count; i++) {
+ node = (uint8_t) ((uintptr_t)data[i]);
+
+ if (node >= num_nodes) {
+ /*
+ * Node is out of range, which means that
+ * flow has not been inserted
+ */
+ flow_dist_stats.drop++;
+ rte_pktmbuf_free(pkts[i]);
+ } else {
+ flow_dist_stats.distributed++;
+ enqueue_rx_packet(node, pkts[i]);
+ }
+ }
+
+ for (i = 0; i < num_nodes; i++)
+ flush_rx_queue(i);
+ }
+
+The burst of packets received is enqueued in temporary buffers (per node),
+and enqueued in the shared ring between the distributor and the node.
+After this, a new burst of packets is received and this process is
+repeated infinitely.
+
+.. code-block:: c
+
+ static void
+ flush_rx_queue(uint16_t node)
+ {
+ uint16_t j;
+ struct node *cl;
+
+ if (cl_rx_buf[node].count == 0)
+ return;
+
+ cl = &nodes[node];
+ if (rte_ring_enqueue_bulk(cl->rx_q, (void **)cl_rx_buf[node].buffer,
+ cl_rx_buf[node].count) != 0){
+ for (j = 0; j < cl_rx_buf[node].count; j++)
+ rte_pktmbuf_free(cl_rx_buf[node].buffer[j]);
+ cl->stats.rx_drop += cl_rx_buf[node].count;
+ } else
+ cl->stats.rx += cl_rx_buf[node].count;
+
+ cl_rx_buf[node].count = 0;
+ }
+
+The second process, the back-end node, receives the packets from the shared
+ring with the distributor and send them out, if they belong to the node.
+
+At initialization, it attaches to the distributor process memory, to have
+access to the shared ring, parameters and statistics.
+
+.. code-block:: c
+
+ rx_ring = rte_ring_lookup(get_rx_queue_name(node_id));
+ if (rx_ring == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get RX ring - "
+ "is distributor process running?\n");
+
+ mp = rte_mempool_lookup(PKTMBUF_POOL_NAME);
+ if (mp == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get mempool for mbufs\n");
+
+ mz = rte_memzone_lookup(MZ_SHARED_INFO);
+ if (mz == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get port info structure\n");
+ info = mz->addr;
+ tx_stats = &(info->tx_stats[node_id]);
+ filter_stats = &(info->filter_stats[node_id]);
+
+Then, the hash table that contains the flows that will be handled
+by the node is created and populated.
+
+.. code-block:: c
+
+ static struct rte_hash *
+ create_hash_table(const struct shared_info *info)
+ {
+ uint32_t num_flows_node = info->num_flows / info->num_nodes;
+ char name[RTE_HASH_NAMESIZE];
+ struct rte_hash *h;
+
+ /* create table */
+ struct rte_hash_parameters hash_params = {
+ .entries = num_flows_node * 2, /* table load = 50% */
+ .key_len = sizeof(uint32_t), /* Store IPv4 dest IP address */
+ .socket_id = rte_socket_id(),
+ .hash_func_init_val = 0,
+ };
+
+ snprintf(name, sizeof(name), "hash_table_%d", node_id);
+ hash_params.name = name;
+ h = rte_hash_create(&hash_params);
+
+ if (h == NULL)
+ rte_exit(EXIT_FAILURE,
+ "Problem creating the hash table for node %d\n",
+ node_id);
+ return h;
+ }
+
+ static void
+ populate_hash_table(const struct rte_hash *h, const struct shared_info *info)
+ {
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint32_t num_flows_node = 0;
+ uint64_t target_node;
+
+ /* Add flows in table */
+ for (i = 0; i < info->num_flows; i++) {
+ target_node = i % info->num_nodes;
+ if (target_node != node_id)
+ continue;
+
+ ip_dst = rte_cpu_to_be_32(i);
+
+ ret = rte_hash_add_key(h, (void *) &ip_dst);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u "
+ "in hash table\n", i);
+ else
+ num_flows_node++;
+
+ }
+
+ printf("Hash table: Adding 0x%x keys\n", num_flows_node);
+ }
+
+After initialization, packets are dequeued from the shared ring
+(from the distributor) and, like in the distributor process,
+the IPv4 address from the packets is used as a key to look up in the hash table.
+If there is a hit, packet is stored in a buffer, to be eventually transmitted
+in one of the enabled ports. If key is not there, packet is dropped, since the
+flow is not handled by the node.
+
+.. code-block:: c
+
+ static inline void
+ handle_packets(struct rte_hash *h, struct rte_mbuf **bufs, uint16_t num_packets)
+ {
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[PKT_READ_SIZE];
+ const void *key_ptrs[PKT_READ_SIZE];
+ unsigned int i;
+ int32_t positions[PKT_READ_SIZE] = {0};
+
+ for (i = 0; i < num_packets; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(bufs[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = &ipv4_dst_ip[i];
+ }
+ /* Check if packets belongs to any flows handled by this node */
+ rte_hash_lookup_bulk(h, key_ptrs, num_packets, positions);
+
+ for (i = 0; i < num_packets; i++) {
+ if (likely(positions[i] >= 0)) {
+ filter_stats->passed++;
+ transmit_packet(bufs[i]);
+ } else {
+ filter_stats->drop++;
+ /* Drop packet, as flow is not handled by this node */
+ rte_pktmbuf_free(bufs[i]);
+ }
+ }
+ }
+
+Finally, note that both processes updates statistics, such as transmitted, received
+and dropped packets, which are shown and refreshed by the distributor app.
+
+.. code-block:: c
+
+ static void
+ do_stats_display(void)
+ {
+ unsigned int i, j;
+ const char clr[] = {27, '[', '2', 'J', '\0'};
+ const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
+ uint64_t port_tx[RTE_MAX_ETHPORTS], port_tx_drop[RTE_MAX_ETHPORTS];
+ uint64_t node_tx[MAX_NODES], node_tx_drop[MAX_NODES];
+
+ /* to get TX stats, we need to do some summing calculations */
+ memset(port_tx, 0, sizeof(port_tx));
+ memset(port_tx_drop, 0, sizeof(port_tx_drop));
+ memset(node_tx, 0, sizeof(node_tx));
+ memset(node_tx_drop, 0, sizeof(node_tx_drop));
+
+ for (i = 0; i < num_nodes; i++) {
+ const struct tx_stats *tx = &info->tx_stats[i];
+
+ for (j = 0; j < info->num_ports; j++) {
+ const uint64_t tx_val = tx->tx[info->id[j]];
+ const uint64_t drop_val = tx->tx_drop[info->id[j]];
+
+ port_tx[j] += tx_val;
+ port_tx_drop[j] += drop_val;
+ node_tx[i] += tx_val;
+ node_tx_drop[i] += drop_val;
+ }
+ }
+
+ /* Clear screen and move to top left */
+ printf("%s%s", clr, topLeft);
+
+ printf("PORTS\n");
+ printf("-----\n");
+ for (i = 0; i < info->num_ports; i++)
+ printf("Port %u: '%s'\t", (unsigned int)info->id[i],
+ get_printable_mac_addr(info->id[i]));
+ printf("\n\n");
+ for (i = 0; i < info->num_ports; i++) {
+ printf("Port %u - rx: %9"PRIu64"\t"
+ "tx: %9"PRIu64"\n",
+ (unsigned int)info->id[i], info->rx_stats.rx[i],
+ port_tx[i]);
+ }
+
+ printf("\nFLOW DISTRIBUTOR\n");
+ printf("-----\n");
+ printf("distributed: %9"PRIu64", drop: %9"PRIu64"\n",
+ flow_dist_stats.distributed, flow_dist_stats.drop);
+
+ printf("\nNODES\n");
+ printf("-------\n");
+ for (i = 0; i < num_nodes; i++) {
+ const unsigned long long rx = nodes[i].stats.rx;
+ const unsigned long long rx_drop = nodes[i].stats.rx_drop;
+ const struct filter_stats *filter = &info->filter_stats[i];
+
+ printf("Node %2u - rx: %9llu, rx_drop: %9llu\n"
+ " tx: %9"PRIu64", tx_drop: %9"PRIu64"\n"
+ " filter_passed: %9"PRIu64", "
+ "filter_drop: %9"PRIu64"\n",
+ i, rx, rx_drop, node_tx[i], node_tx_drop[i],
+ filter->passed, filter->drop);
+ }
+
+ printf("\n");
+ }
diff --git a/doc/guides/sample_app_ug/img/flow_distributor.svg b/doc/guides/sample_app_ug/img/flow_distributor.svg
new file mode 100644
index 0000000..9aee30b
--- /dev/null
+++ b/doc/guides/sample_app_ug/img/flow_distributor.svg
@@ -0,0 +1,1254 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i6.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="8.2496in" height="5.89673in"
+ viewBox="0 0 593.971 424.565" xml:space="preserve" color-interpolation-filters="sRGB" class="st27">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st5 {fill:#feffff;font-family:Calibri;font-size:0.75em}
+ .st6 {fill:none;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st7 {fill:none;stroke:#2e75b5;stroke-width:2.25}
+ .st8 {fill:#305497;stroke:#2e75b5;stroke-width:1}
+ .st9 {fill:#feffff;font-family:Calibri;font-size:0.833336em;font-weight:bold}
+ .st10 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2)}
+ .st11 {fill:#5b9bd5}
+ .st12 {stroke:#c7c8c8;stroke-width:0.25}
+ .st13 {fill:#acccea;stroke:#c7c8c8;stroke-width:0.25}
+ .st14 {fill:#feffff;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st15 {fill:#ed7d31;stroke:#c7c8c8;stroke-width:0.25}
+ .st16 {fill:#deebf6;stroke:#c7c8c8;stroke-width:0.25}
+ .st17 {marker-end:url(#mrkr5-212);stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st18 {fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st19 {fill:none;stroke:#2e75b5;stroke-width:1}
+ .st20 {fill:#5b9bd5;font-family:Calibri;font-size:1.00001em}
+ .st21 {fill:none;stroke:none;stroke-width:0.25}
+ .st22 {font-size:1em}
+ .st23 {fill:#ffffff}
+ .st24 {stroke:#5b9bd5;stroke-dasharray:1.5,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st25 {marker-end:url(#mrkr5-444);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st26 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st27 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-212" class="st18" v:arrowType="5" v:arrowSize="2" v:setback="5.8" refX="-5.8" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ <marker id="mrkr5-444" class="st26" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(319.501,-335.688)">
+ <title>Rectangle.58</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow3-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape4-7" v:mID="4" v:groupContext="shape" transform="translate(353.251,-335.688)">
+ <title>Rectangle.59</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow4-8" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape5-13" v:mID="5" v:groupContext="shape" transform="translate(400.501,-335.688)">
+ <title>Rectangle.60</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow5-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape6-19" v:mID="6" v:groupContext="shape" transform="translate(434.251,-335.688)">
+ <title>Rectangle.61</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow6-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape7-25" v:mID="7" v:groupContext="shape" transform="translate(481.501,-335.688)">
+ <title>Rectangle.62</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow7-26" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape8-30" v:mID="8" v:groupContext="shape" transform="translate(515.251,-335.688)">
+ <title>Rectangle.63</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow8-31" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape9-35" v:mID="9" v:groupContext="shape" transform="translate(319.501,-313.188)">
+ <title>Rectangle.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow9-36" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape10-40" v:mID="10" v:groupContext="shape" transform="translate(353.251,-313.188)">
+ <title>Rectangle.65</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow10-41" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape11-45" v:mID="11" v:groupContext="shape" transform="translate(400.501,-313.188)">
+ <title>Rectangle.66</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow11-46" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape12-50" v:mID="12" v:groupContext="shape" transform="translate(434.251,-313.188)">
+ <title>Rectangle.67</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow12-51" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape13-55" v:mID="13" v:groupContext="shape" transform="translate(481.501,-313.188)">
+ <title>Rectangle.68</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow13-56" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape14-60" v:mID="14" v:groupContext="shape" transform="translate(515.251,-313.188)">
+ <title>Rectangle.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow14-61" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape15-65" v:mID="15" v:groupContext="shape" transform="translate(319.501,-277.188)">
+ <title>Rectangle.70</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow15-66" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape16-71" v:mID="16" v:groupContext="shape" transform="translate(353.251,-277.188)">
+ <title>Rectangle.71</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow16-72" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape17-77" v:mID="17" v:groupContext="shape" transform="translate(400.501,-277.188)">
+ <title>Rectangle.72</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow17-78" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape18-83" v:mID="18" v:groupContext="shape" transform="translate(434.251,-277.188)">
+ <title>Rectangle.73</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow18-84" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape19-89" v:mID="19" v:groupContext="shape" transform="translate(481.501,-277.188)">
+ <title>Rectangle.74</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow19-90" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape20-95" v:mID="20" v:groupContext="shape" transform="translate(515.251,-277.188)">
+ <title>Rectangle.75</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow20-96" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape21-101" v:mID="21" v:groupContext="shape" transform="translate(319.501,-240.687)">
+ <title>Rectangle.76</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow21-102" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape22-106" v:mID="22" v:groupContext="shape" transform="translate(353.251,-240.687)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow22-107" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape23-111" v:mID="23" v:groupContext="shape" transform="translate(400.501,-240.687)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow23-112" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape24-116" v:mID="24" v:groupContext="shape" transform="translate(434.251,-240.687)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow24-117" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape25-121" v:mID="25" v:groupContext="shape" transform="translate(481.501,-240.687)">
+ <title>Rectangle.80</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow25-122" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape26-127" v:mID="26" v:groupContext="shape" transform="translate(515.251,-240.687)">
+ <title>Rectangle.81</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow26-128" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape27-133" v:mID="27" v:groupContext="shape" transform="translate(317.251,-231.687)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow27-134" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st6"/>
+ </g>
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st7"/>
+ </g>
+ <g id="shape28-138" v:mID="28" v:groupContext="shape" transform="translate(328.501,-362.688)">
+ <title>Sheet.28</title>
+ <desc>Local Table for N Specific Flows Serviced at Node 1</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="110.423" cy="418.94" width="220.85" height="11.25"/>
+ <rect x="0" y="413.315" width="220.846" height="11.25" class="st8"/>
+ <text x="5.77" y="421.94" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Local Table for N Specific Flows Serviced at Node 1</text> </g>
+ <g id="group34-141" transform="translate(66.0294,-165.569)" v:mID="34" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Device)"/>
+ <v:cp v:nameU="SubShapeType" v:lbl="SubShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Load balancer)"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Load balancer</title>
+ <g id="shape35-142" v:mID="35" v:groupContext="shape" transform="translate(0,-7.33146)">
+ <title>Sheet.35</title>
+ <g id="shadow35-143" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23 Z" class="st10"/>
+ <path d="M0 377.86 L72 377.86" class="st6"/>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23" class="st6"/>
+ </g>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23 Z" class="st11"/>
+ <path d="M0 377.86 L72 377.86" class="st12"/>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23" class="st12"/>
+ </g>
+ <g id="shape36-152" v:mID="36" v:groupContext="shape" transform="translate(8.03054,-12.9324)">
+ <title>Sheet.36</title>
+ <g id="shadow36-153" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M45.34 421.81 L41.2 422.66 L44.12 424.56 L49.68 423.16 L48.75 417.51 L45.8 415.59 L46.69 419.68 L36.97
+ 413.34 L35.6 415.45 L45.34 421.81 ZM50.83 405.36 L39.2 405.36 L39.2 407.88 L50.8 407.88 L47.82 410.83
+ L51.34 410.83 L55.21 406.61 L51.32 402.39 L47.83 402.39 L50.83 405.36 ZM46.49 392.01 L36.75 398.37
+ L38.13 400.48 L47.84 394.14 L46.96 398.23 L49.91 396.31 L50.84 390.66 L45.28 389.26 L42.36 391.16
+ L46.49 392.01 ZM27.71 397.16 C22.66 397.16 18.58 401.25 18.58 406.29 C18.58 411.33 22.66 415.42
+ 27.71 415.42 C32.75 415.42 36.84 411.33 36.84 406.29 C36.84 401.25 32.75 397.16 27.71 397.16 ZM27.71
+ 400.04 C31.15 400.04 33.96 402.84 33.96 406.29 C33.96 409.74 31.15 412.54 27.71 412.54 C24.26 412.54
+ 21.46 409.74 21.46 406.29 C21.46 402.84 24.26 400.04 27.71 400.04 ZM11.64 405.04 L0 405.04 L0 407.56
+ L11.6 407.56 L8.62 410.51 L12.14 410.51 L16.01 406.29 L12.12 402.07 L8.64 402.07 L11.64 405.04 Z"
+ class="st2"/>
+ </g>
+ <path d="M45.34 421.81 L41.2 422.66 L44.12 424.56 L49.68 423.16 L48.75 417.51 L45.8 415.59 L46.69 419.68 L36.97 413.34
+ L35.6 415.45 L45.34 421.81 ZM50.83 405.36 L39.2 405.36 L39.2 407.88 L50.8 407.88 L47.82 410.83 L51.34
+ 410.83 L55.21 406.61 L51.32 402.39 L47.83 402.39 L50.83 405.36 ZM46.49 392.01 L36.75 398.37 L38.13 400.48
+ L47.84 394.14 L46.96 398.23 L49.91 396.31 L50.84 390.66 L45.28 389.26 L42.36 391.16 L46.49 392.01 ZM27.71
+ 397.16 C22.66 397.16 18.58 401.25 18.58 406.29 C18.58 411.33 22.66 415.42 27.71 415.42 C32.75 415.42
+ 36.84 411.33 36.84 406.29 C36.84 401.25 32.75 397.16 27.71 397.16 ZM27.71 400.04 C31.15 400.04 33.96
+ 402.84 33.96 406.29 C33.96 409.74 31.15 412.54 27.71 412.54 C24.26 412.54 21.46 409.74 21.46 406.29
+ C21.46 402.84 24.26 400.04 27.71 400.04 ZM11.64 405.04 L0 405.04 L0 407.56 L11.6 407.56 L8.62 410.51
+ L12.14 410.51 L16.01 406.29 L12.12 402.07 L8.64 402.07 L11.64 405.04 Z" class="st13"/>
+ </g>
+ </g>
+ <g id="shape37-157" v:mID="37" v:groupContext="shape" transform="translate(21.0294,-45.4375)">
+ <title>Rectangle</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow37-158" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="336.433" width="135" height="88.1315" class="st2"/>
+ </g>
+ <rect x="0" y="336.433" width="135" height="88.1315" class="st3"/>
+ </g>
+ <g id="shape38-162" v:mID="38" v:groupContext="shape" transform="translate(34.693,-126.438)">
+ <title>Sheet.38</title>
+ <desc>EFD Table</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="49.3364" cy="415.565" width="98.68" height="18"/>
+ <rect x="0" y="406.565" width="98.6728" height="18" class="st8"/>
+ <text x="24.87" y="419.17" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>EFD Table</text> </g>
+ <g id="shape39-165" v:mID="39" v:groupContext="shape" transform="translate(30.0294,-99.4375)">
+ <title>Rectangle.39</title>
+ <desc>Group_id</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="26.9182" cy="415.565" width="53.84" height="18"/>
+ <g id="shadow39-166" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st15"/>
+ <text x="7.87" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group_id</text> </g>
+ <g id="shape40-171" v:mID="40" v:groupContext="shape" transform="translate(93.193,-99.4375)">
+ <title>Rectangle.40</title>
+ <desc>Hash index</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="26.9182" cy="415.565" width="53.84" height="18"/>
+ <g id="shadow40-172" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st15"/>
+ <text x="4.64" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Hash index</text> </g>
+ <g id="shape41-177" v:mID="41" v:groupContext="shape" transform="translate(30.193,-82.4275)">
+ <title>Rectangle.41</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow41-178" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape42-182" v:mID="42" v:groupContext="shape" transform="translate(30.193,-66.8125)">
+ <title>Rectangle.42</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow42-183" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape43-187" v:mID="43" v:groupContext="shape" transform="translate(30.1112,-52.1875)">
+ <title>Rectangle.43</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow43-188" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape44-192" v:mID="44" v:groupContext="shape" transform="translate(93.0294,-81.4375)">
+ <title>Rectangle.44</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow44-193" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape45-197" v:mID="45" v:groupContext="shape" transform="translate(93.193,-66.8125)">
+ <title>Rectangle.45</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow45-198" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape46-202" v:mID="46" v:groupContext="shape" transform="translate(93.193,-52.1875)">
+ <title>Rectangle.46</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow46-203" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape47-207" v:mID="47" v:groupContext="shape" transform="translate(374.924,544.022) rotate(135)">
+ <title>Sheet.47</title>
+ <path d="M-0 417.75 A40.674 18.0151 -156.2 0 0 40.24 422.15 L40.49 421.89" class="st17"/>
+ </g>
+ <g id="shape48-213" v:mID="48" v:groupContext="shape" transform="translate(21.0294,-19)">
+ <title>Sheet.48</title>
+ <desc>Supports X*N Flows</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="415.565" width="135" height="18"/>
+ <rect x="0" y="406.565" width="135" height="18" class="st19"/>
+ <text x="19.05" y="419.17" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Supports X*N Flows</text> </g>
+ <g id="shape49-216" v:mID="49" v:groupContext="shape" transform="translate(48.0294,-229.938)">
+ <title>Sheet.49</title>
+ <desc>Frontend Server or Load Balancer</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="400.94" width="108" height="47.25"/>
+ <rect x="0" y="377.315" width="108" height="47.25" class="st21"/>
+ <text x="14.56" y="397.34" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Frontend Server<v:newlineChar/><tspan
+ x="13.16" dy="1.2em" class="st22">or Load Balancer </tspan> </text> </g>
+ <g id="group51-220" transform="translate(223.876,-310.938)" v:mID="51" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server</title>
+ <g id="shape52-221" v:mID="52" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.52</title>
+ <g id="shadow52-222" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape53-226" v:mID="53" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.53</title>
+ <g id="shadow53-227" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape54-231" v:mID="54" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.54</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow54-232" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape59-239" v:mID="59" v:groupContext="shape" transform="translate(277.876,-373.938)">
+ <title>Sheet.59</title>
+ <path d="M-0 424.56 A111.108 53.2538 42.31 0 1 93.83 421.21 L94.14 421.41" class="st17"/>
+ </g>
+ <g id="shape60-244" v:mID="60" v:groupContext="shape" transform="translate(205.876,-283.938)">
+ <title>Sheet.60</title>
+ <desc>Backend Server 1</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.93" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server 1</text> </g>
+ <g id="group61-247" transform="translate(223.876,-207.438)" v:mID="61" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server.61</title>
+ <g id="shape62-248" v:mID="62" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.62</title>
+ <g id="shadow62-249" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape63-253" v:mID="63" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.63</title>
+ <g id="shadow63-254" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape64-258" v:mID="64" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow64-259" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape65-266" v:mID="65" v:groupContext="shape" transform="translate(205.876,-180.437)">
+ <title>Sheet.65</title>
+ <desc>Backend Server 2</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.93" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server 2</text> </g>
+ <g id="group66-269" transform="translate(219.029,-58.9375)" v:mID="66" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server.66</title>
+ <g id="shape67-270" v:mID="67" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.67</title>
+ <g id="shadow67-271" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape68-275" v:mID="68" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.68</title>
+ <g id="shadow68-276" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape69-280" v:mID="69" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow69-281" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape70-288" v:mID="70" v:groupContext="shape" transform="translate(201.029,-26.056)">
+ <title>Sheet.70</title>
+ <desc>Backend Server X</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.86" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server X</text> </g>
+ <g id="shape71-291" v:mID="71" v:groupContext="shape" transform="translate(684.44,239.627) rotate(90)">
+ <title>Sheet.71</title>
+ <path d="M0 424.56 L45 424.56" class="st24"/>
+ </g>
+ <g id="shape72-294" v:mID="72" v:groupContext="shape" transform="translate(6.85967,-22.443) rotate(-38.1076)">
+ <title>Sheet.72</title>
+ <path d="M-0 424.56 A96.1331 44.4001 55.03 0 1 68.24 420.56 L68.51 420.79" class="st17"/>
+ </g>
+ <g id="shape73-299" v:mID="73" v:groupContext="shape" transform="translate(328.501,-135.937)">
+ <title>Rectangle.73</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow73-300" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape74-305" v:mID="74" v:groupContext="shape" transform="translate(362.251,-135.937)">
+ <title>Rectangle.74</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow74-306" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape75-311" v:mID="75" v:groupContext="shape" transform="translate(409.501,-135.937)">
+ <title>Rectangle.75</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow75-312" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape76-317" v:mID="76" v:groupContext="shape" transform="translate(443.251,-135.937)">
+ <title>Rectangle.76</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow76-318" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape77-323" v:mID="77" v:groupContext="shape" transform="translate(490.501,-135.937)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow77-324" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape78-328" v:mID="78" v:groupContext="shape" transform="translate(524.251,-135.937)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow78-329" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape79-333" v:mID="79" v:groupContext="shape" transform="translate(328.501,-113.437)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow79-334" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape80-338" v:mID="80" v:groupContext="shape" transform="translate(362.251,-113.437)">
+ <title>Rectangle.80</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow80-339" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape81-343" v:mID="81" v:groupContext="shape" transform="translate(409.501,-113.437)">
+ <title>Rectangle.81</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow81-344" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape82-348" v:mID="82" v:groupContext="shape" transform="translate(443.251,-113.437)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow82-349" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape83-353" v:mID="83" v:groupContext="shape" transform="translate(490.501,-113.437)">
+ <title>Rectangle.83</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow83-354" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape84-358" v:mID="84" v:groupContext="shape" transform="translate(524.251,-113.437)">
+ <title>Rectangle.84</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow84-359" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape85-363" v:mID="85" v:groupContext="shape" transform="translate(328.501,-77.4375)">
+ <title>Rectangle.85</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow85-364" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape86-369" v:mID="86" v:groupContext="shape" transform="translate(362.251,-77.4375)">
+ <title>Rectangle.86</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow86-370" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape87-375" v:mID="87" v:groupContext="shape" transform="translate(409.501,-77.4375)">
+ <title>Rectangle.87</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow87-376" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape88-381" v:mID="88" v:groupContext="shape" transform="translate(443.251,-77.4375)">
+ <title>Rectangle.88</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow88-382" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape89-387" v:mID="89" v:groupContext="shape" transform="translate(490.501,-77.4375)">
+ <title>Rectangle.89</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow89-388" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape90-393" v:mID="90" v:groupContext="shape" transform="translate(524.251,-77.4375)">
+ <title>Rectangle.90</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow90-394" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape91-399" v:mID="91" v:groupContext="shape" transform="translate(328.501,-40.9375)">
+ <title>Rectangle.91</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow91-400" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape92-404" v:mID="92" v:groupContext="shape" transform="translate(362.251,-40.9375)">
+ <title>Rectangle.92</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow92-405" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape93-409" v:mID="93" v:groupContext="shape" transform="translate(409.501,-40.9375)">
+ <title>Rectangle.93</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow93-410" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape94-414" v:mID="94" v:groupContext="shape" transform="translate(443.251,-40.9375)">
+ <title>Rectangle.94</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow94-415" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape95-419" v:mID="95" v:groupContext="shape" transform="translate(490.501,-40.9375)">
+ <title>Rectangle.95</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow95-420" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape96-425" v:mID="96" v:groupContext="shape" transform="translate(524.251,-40.9375)">
+ <title>Rectangle.96</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow96-426" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape97-431" v:mID="97" v:groupContext="shape" transform="translate(326.251,-31.9375)">
+ <title>Rectangle.97</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow97-432" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st6"/>
+ </g>
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st7"/>
+ </g>
+ <g id="shape98-436" v:mID="98" v:groupContext="shape" transform="translate(337.501,-162.938)">
+ <title>Sheet.98</title>
+ <desc>Local Table for N Specific Flows Serviced at Node X</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="110.423" cy="418.94" width="220.85" height="11.25"/>
+ <rect x="0" y="413.315" width="220.846" height="11.25" class="st8"/>
+ <text x="5.55" y="421.94" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Local Table for N Specific Flows Serviced at Node X</text> </g>
+ <g id="shape99-439" v:mID="99" v:groupContext="shape" transform="translate(-204.342,-29.4449) rotate(-53.7462)">
+ <title>Sheet.99</title>
+ <path d="M0 424.56 L160.37 424.56" class="st25"/>
+ </g>
+ <g id="shape100-445" v:mID="100" v:groupContext="shape" transform="translate(-37.6568,-164.882) rotate(-24.444)">
+ <title>Sheet.100</title>
+ <path d="M0 424.56 L101.71 424.56" class="st25"/>
+ </g>
+ <g id="shape101-450" v:mID="101" v:groupContext="shape" transform="translate(464.049,-50.8578) rotate(50.099)">
+ <title>Sheet.101</title>
+ <path d="M0 424.56 L139.8 424.56" class="st25"/>
+ </g>
+ <g id="shape102-455" v:mID="102" v:groupContext="shape" transform="translate(372.376,-207.438)">
+ <title>Sheet.102</title>
+ <desc>Supports N Flows</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="415.565" width="135" height="18"/>
+ <rect x="0" y="406.565" width="135" height="18" class="st19"/>
+ <text x="25.15" y="419.17" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Supports N Flows</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/sample_app_ug/index.rst b/doc/guides/sample_app_ug/index.rst
index 775e2f7..260f6a5 100644
--- a/doc/guides/sample_app_ug/index.rst
+++ b/doc/guides/sample_app_ug/index.rst
@@ -57,6 +57,7 @@ Sample Applications User Guides
l3_forward_virtual
link_status_intr
load_balancer
+ flow_distributor
multi_process
qos_metering
qos_scheduler
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [dpdk-dev] [PATCH v7 0/6] Elastic Flow Distributor
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 0/6] " Pablo de Lara
` (5 preceding siblings ...)
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 6/6] doc: add flow distributor guide Pablo de Lara
@ 2017-01-17 22:18 ` De Lara Guarch, Pablo
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 " Pablo de Lara
7 siblings, 0 replies; 63+ messages in thread
From: De Lara Guarch, Pablo @ 2017-01-17 22:18 UTC (permalink / raw)
To: dev
> -----Original Message-----
> From: De Lara Guarch, Pablo
> Sent: Tuesday, January 17, 2017 10:11 PM
> To: dev@dpdk.org
> Cc: De Lara Guarch, Pablo
> Subject: [PATCH v7 0/6] Elastic Flow Distributor
>
NACK, added .rej file by mistake.
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v8 0/6] Elastic Flow Distributor
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 0/6] " Pablo de Lara
` (6 preceding siblings ...)
2017-01-17 22:18 ` [dpdk-dev] [PATCH v7 0/6] Elastic Flow Distributor De Lara Guarch, Pablo
@ 2017-01-17 22:23 ` Pablo de Lara
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 1/6] efd: new Elastic Flow Distributor library Pablo de Lara
` (6 more replies)
7 siblings, 7 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-17 22:23 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara
EFD is a distributor library that uses perfect hashing to determine a
target/value for a given incoming flow key. It has the following advantages:
first, because it uses perfect hashing it does not store the key itself and
hence lookup performance is not dependent on the key size. Second, the
target/value can be any arbitrary value hence the system designer and/or
operator can better optimize service rates and inter-cluster network traffic
locating. Third, since the storage requirement is much smaller than a
hash-based flow table (i.e. better fit for CPU cache), EFD can scale to millions
of flow keys. Finally, with current optimized library implementation performance
is fully scalable with number of CPU cores.
The basic idea of EFD is when a given key is to be inserted, a family of hash
functions is searched until the correct hash function that maps the input key to
the correct value is found. However, rather than explicitly storing all keys and
their associated values, EFD stores only indices of hash functions that map keys
to values, and thereby consumes much less space than conventional flow-based
tables. The lookup operation is very simple, similar to computational-based
scheme, given an input key the lookup operation is reduced to hashing that key
with the correct hash function.
Intuitively, finding a hash function that maps each of a large number (millions)
of input keys to the correct output value is effectively impossible, as a result
EFD, breaks the problem into smaller pieces (divide and conquer). EFD divides
the entire input key set into many small groups. Each group consists of
approximately 20-28 keys (a configurable parameter for the library), then, for
each small group, a brute force search to find a hash function that produces the
correct outputs for each key in the group.
It should be mentioned that since in the online lookup table for EFD doesn’t
store the key itself, the size of the EFD table is independent of the key size
and hence EFD lookup performance which is almost constant irrespective of the
length of the key which is a highly desirable feature especially for longer
keys.
Library code is included in the patch, plus an sample application that shows
how the library can be used.
RFC for this library was already sent:
http://dpdk.org/ml/archives/dev/2016-October/049238.html
Changes in v8:
- Removed .rej file added in v7 by mistake
Changes in v7:
- Fixed figure reference in documentation
- Removed unnecessary library dependencies
- Separated x86 SIMD code in different patch
- Added extra comments
- Fixed ARM compilation issue
Changes in v6:
- Separated x86 SIMD code in different file
- Fixed shared library compilation
- Fixed figure reference in documentation
- Added missing parameter documentation
Changes in v5:
- Removed trailing whitespace in doc
Changes in v4:
- Added References section
Changes in v3:
- Fixed SVG files
- Fixed copyright dates
- Reformatted parts of documentation
Changes in v2:
- Added documentation for library and sample app
- Fixed checkpatch errors/warnings
- Added functional and performance tests
- Made key size variable at runtime
- Made code multi-architecture compatible at runtime
Pablo de Lara (6):
efd: new Elastic Flow Distributor library
efd: add AVX2 vect lookup function
app/test: add EFD functional and perf tests
examples/flow_distributor: sample app to demonstrate EFD usage
doc: add EFD library section in Programmers guide
doc: add flow distributor guide
MAINTAINERS | 9 +
app/test/Makefile | 5 +-
app/test/test_efd.c | 494 ++++++++
app/test/test_efd_perf.c | 407 +++++++
config/common_base | 5 +
doc/api/doxy-api-index.md | 3 +-
doc/api/doxy-api.conf | 1 +
doc/api/examples.dox | 4 +
doc/guides/prog_guide/efd_lib.rst | 440 +++++++
doc/guides/prog_guide/img/efd_i1.svg | 130 ++
doc/guides/prog_guide/img/efd_i10.svg | 384 ++++++
doc/guides/prog_guide/img/efd_i11.svg | 319 +++++
doc/guides/prog_guide/img/efd_i12.svg | 1008 ++++++++++++++++
doc/guides/prog_guide/img/efd_i2.svg | 280 +++++
doc/guides/prog_guide/img/efd_i3.svg | 634 ++++++++++
doc/guides/prog_guide/img/efd_i4.svg | 203 ++++
doc/guides/prog_guide/img/efd_i5.svg | 183 +++
doc/guides/prog_guide/img/efd_i6.svg | 1254 +++++++++++++++++++
doc/guides/prog_guide/img/efd_i7.svg | 790 ++++++++++++
doc/guides/prog_guide/img/efd_i8.svg | 182 +++
doc/guides/prog_guide/img/efd_i9.svg | 390 ++++++
doc/guides/prog_guide/index.rst | 23 +
doc/guides/rel_notes/release_17_02.rst | 14 +
doc/guides/sample_app_ug/flow_distributor.rst | 494 ++++++++
doc/guides/sample_app_ug/img/flow_distributor.svg | 1254 +++++++++++++++++++
doc/guides/sample_app_ug/index.rst | 1 +
examples/Makefile | 1 +
examples/flow_distributor/Makefile | 44 +
examples/flow_distributor/distributor/Makefile | 57 +
examples/flow_distributor/distributor/args.c | 200 +++
examples/flow_distributor/distributor/args.h | 39 +
examples/flow_distributor/distributor/init.c | 371 ++++++
examples/flow_distributor/distributor/init.h | 76 ++
examples/flow_distributor/distributor/main.c | 362 ++++++
examples/flow_distributor/node/Makefile | 48 +
examples/flow_distributor/node/node.c | 417 +++++++
examples/flow_distributor/shared/common.h | 99 ++
lib/Makefile | 3 +-
lib/librte_eal/common/include/rte_log.h | 3 +-
lib/librte_efd/Makefile | 56 +
lib/librte_efd/rte_efd.c | 1344 +++++++++++++++++++++
lib/librte_efd/rte_efd.h | 300 +++++
lib/librte_efd/rte_efd_version.map | 13 +
lib/librte_efd/rte_efd_x86.h | 86 ++
mk/rte.app.mk | 3 +-
45 files changed, 12428 insertions(+), 5 deletions(-)
create mode 100644 app/test/test_efd.c
create mode 100644 app/test/test_efd_perf.c
create mode 100644 doc/guides/prog_guide/efd_lib.rst
create mode 100644 doc/guides/prog_guide/img/efd_i1.svg
create mode 100644 doc/guides/prog_guide/img/efd_i10.svg
create mode 100644 doc/guides/prog_guide/img/efd_i11.svg
create mode 100644 doc/guides/prog_guide/img/efd_i12.svg
create mode 100644 doc/guides/prog_guide/img/efd_i2.svg
create mode 100644 doc/guides/prog_guide/img/efd_i3.svg
create mode 100644 doc/guides/prog_guide/img/efd_i4.svg
create mode 100644 doc/guides/prog_guide/img/efd_i5.svg
create mode 100644 doc/guides/prog_guide/img/efd_i6.svg
create mode 100644 doc/guides/prog_guide/img/efd_i7.svg
create mode 100644 doc/guides/prog_guide/img/efd_i8.svg
create mode 100644 doc/guides/prog_guide/img/efd_i9.svg
create mode 100644 doc/guides/sample_app_ug/flow_distributor.rst
create mode 100644 doc/guides/sample_app_ug/img/flow_distributor.svg
create mode 100644 examples/flow_distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/args.c
create mode 100644 examples/flow_distributor/distributor/args.h
create mode 100644 examples/flow_distributor/distributor/init.c
create mode 100644 examples/flow_distributor/distributor/init.h
create mode 100644 examples/flow_distributor/distributor/main.c
create mode 100644 examples/flow_distributor/node/Makefile
create mode 100644 examples/flow_distributor/node/node.c
create mode 100644 examples/flow_distributor/shared/common.h
create mode 100644 lib/librte_efd/Makefile
create mode 100644 lib/librte_efd/rte_efd.c
create mode 100644 lib/librte_efd/rte_efd.h
create mode 100644 lib/librte_efd/rte_efd_version.map
create mode 100644 lib/librte_efd/rte_efd_x86.h
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v8 1/6] efd: new Elastic Flow Distributor library
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 " Pablo de Lara
@ 2017-01-17 22:23 ` Pablo de Lara
2017-01-18 18:56 ` Thomas Monjalon
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 2/6] efd: add AVX2 vect lookup function Pablo de Lara
` (5 subsequent siblings)
6 siblings, 1 reply; 63+ messages in thread
From: Pablo de Lara @ 2017-01-17 22:23 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Byron Marohn, Saikrishna Edupuganti
Elastic Flow Distributor (EFD) is a distributor library that uses
perfect hashing to determine a target/value for a given incoming flow key.
It has the following advantages:
- First, because it uses perfect hashing, it does not store
the key itself and hence lookup performance is not dependent
on the key size.
- Second, the target/value can be any arbitrary value hence
the system designer and/or operator can better optimize service rates
and inter-cluster network traffic locating.
- Third, since the storage requirement is much smaller than a hash-based
flow table (i.e. better fit for CPU cache), EFD can scale to
millions of flow keys.
Finally, with current optimized library implementation performance
is fully scalable with number of CPU cores.
Signed-off-by: Byron Marohn <byron.marohn@intel.com>
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 5 +
config/common_base | 5 +
doc/api/doxy-api-index.md | 3 +-
doc/api/doxy-api.conf | 1 +
doc/guides/rel_notes/release_17_02.rst | 11 +
lib/Makefile | 3 +-
lib/librte_eal/common/include/rte_log.h | 3 +-
lib/librte_efd/Makefile | 56 ++
lib/librte_efd/rte_efd.c | 1324 +++++++++++++++++++++++++++++++
lib/librte_efd/rte_efd.h | 300 +++++++
lib/librte_efd/rte_efd_version.map | 13 +
mk/rte.app.mk | 3 +-
12 files changed, 1723 insertions(+), 4 deletions(-)
create mode 100644 lib/librte_efd/Makefile
create mode 100644 lib/librte_efd/rte_efd.c
create mode 100644 lib/librte_efd/rte_efd.h
create mode 100644 lib/librte_efd/rte_efd_version.map
diff --git a/MAINTAINERS b/MAINTAINERS
index 9645c9b..9c60d67 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -528,6 +528,11 @@ F: app/test/test_acl.*
F: examples/l3fwd-acl/
F: doc/guides/sample_app_ug/l3_forward_access_ctrl.rst
+EFD
+M: Byron Marohn <byron.marohn@intel.com>
+M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
+F: lib/librte_efd/
+
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
M: Pablo de Lara <pablo.de.lara.guarch@intel.com>
diff --git a/config/common_base b/config/common_base
index 8e9dcfa..869d8fb 100644
--- a/config/common_base
+++ b/config/common_base
@@ -467,6 +467,11 @@ CONFIG_RTE_LIBRTE_HASH=y
CONFIG_RTE_LIBRTE_HASH_DEBUG=n
#
+# Compile librte_efd
+#
+CONFIG_RTE_LIBRTE_EFD=y
+
+#
# Compile librte_jobstats
#
CONFIG_RTE_LIBRTE_JOBSTATS=y
diff --git a/doc/api/doxy-api-index.md b/doc/api/doxy-api-index.md
index 72d59b2..0d34e2f 100644
--- a/doc/api/doxy-api-index.md
+++ b/doc/api/doxy-api-index.md
@@ -90,7 +90,8 @@ There are many libraries, so their headers may be grouped by topics:
[frag/reass] (@ref rte_ip_frag.h),
[LPM IPv4 route] (@ref rte_lpm.h),
[LPM IPv6 route] (@ref rte_lpm6.h),
- [ACL] (@ref rte_acl.h)
+ [ACL] (@ref rte_acl.h),
+ [EFD] (@ref rte_efd.h)
- **QoS**:
[metering] (@ref rte_meter.h),
diff --git a/doc/api/doxy-api.conf b/doc/api/doxy-api.conf
index b340fcf..6892315 100644
--- a/doc/api/doxy-api.conf
+++ b/doc/api/doxy-api.conf
@@ -40,6 +40,7 @@ INPUT = doc/api/doxy-api-index.md \
lib/librte_compat \
lib/librte_cryptodev \
lib/librte_distributor \
+ lib/librte_efd \
lib/librte_ether \
lib/librte_hash \
lib/librte_ip_frag \
diff --git a/doc/guides/rel_notes/release_17_02.rst b/doc/guides/rel_notes/release_17_02.rst
index 4a3b947..32ea8d9 100644
--- a/doc/guides/rel_notes/release_17_02.rst
+++ b/doc/guides/rel_notes/release_17_02.rst
@@ -96,6 +96,17 @@ New Features
See the :ref:`Virtio Interrupt Mode <virtio_interrupt_mode>` documentation
for more information.
+* **Added Elastic Flow Distributor library (rte_efd).**
+
+ This new library uses perfect hashing to determine a target/value for a
+ given incoming flow key.
+
+ It does not store the key itself for lookup operations, and therefore,
+ lookup performance is not dependent on the key size. Also, the target/value
+ can be any arbitrary value (8 bits by default). Finally, the storage requirement
+ is much smaller than a hash-based flow table and therefore, it can better fit for
+ CPU cache, being able to scale to millions of flow keys.
+
Resolved Issues
---------------
diff --git a/lib/Makefile b/lib/Makefile
index 990f23a..4178325 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -1,6 +1,6 @@
# BSD LICENSE
#
-# Copyright(c) 2010-2015 Intel Corporation. All rights reserved.
+# Copyright(c) 2010-2017 Intel Corporation. All rights reserved.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -43,6 +43,7 @@ DIRS-$(CONFIG_RTE_LIBRTE_ETHER) += librte_ether
DIRS-$(CONFIG_RTE_LIBRTE_CRYPTODEV) += librte_cryptodev
DIRS-$(CONFIG_RTE_LIBRTE_VHOST) += librte_vhost
DIRS-$(CONFIG_RTE_LIBRTE_HASH) += librte_hash
+DIRS-$(CONFIG_RTE_LIBRTE_EFD) += librte_efd
DIRS-$(CONFIG_RTE_LIBRTE_LPM) += librte_lpm
DIRS-$(CONFIG_RTE_LIBRTE_ACL) += librte_acl
DIRS-$(CONFIG_RTE_LIBRTE_NET) += librte_net
diff --git a/lib/librte_eal/common/include/rte_log.h b/lib/librte_eal/common/include/rte_log.h
index 671e274..954b96c 100644
--- a/lib/librte_eal/common/include/rte_log.h
+++ b/lib/librte_eal/common/include/rte_log.h
@@ -1,7 +1,7 @@
/*-
* BSD LICENSE
*
- * Copyright(c) 2010-2014 Intel Corporation. All rights reserved.
+ * Copyright(c) 2010-2017 Intel Corporation. All rights reserved.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -79,6 +79,7 @@ extern struct rte_logs rte_logs;
#define RTE_LOGTYPE_PIPELINE 0x00008000 /**< Log related to pipeline. */
#define RTE_LOGTYPE_MBUF 0x00010000 /**< Log related to mbuf. */
#define RTE_LOGTYPE_CRYPTODEV 0x00020000 /**< Log related to cryptodev. */
+#define RTE_LOGTYPE_EFD 0x00040000 /**< Log related to EFD. */
/* these log types can be used in an application */
#define RTE_LOGTYPE_USER1 0x01000000 /**< User-defined log type 1. */
diff --git a/lib/librte_efd/Makefile b/lib/librte_efd/Makefile
new file mode 100644
index 0000000..d8e1f94
--- /dev/null
+++ b/lib/librte_efd/Makefile
@@ -0,0 +1,56 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+# library name
+LIB = librte_efd.a
+
+LDLIBS += -lm
+
+CFLAGS += -O3
+CFLAGS += $(WERROR_FLAGS) -I$(SRCDIR)
+
+EXPORT_MAP := rte_efd_version.map
+
+LIBABIVER := 1
+
+# all source are stored in SRCS-y
+SRCS-$(CONFIG_RTE_LIBRTE_EFD) := rte_efd.c
+
+# install this header file
+SYMLINK-$(CONFIG_RTE_LIBRTE_EFD)-include := rte_efd.h
+
+# this lib depends upon:
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_eal
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_ring
+
+include $(RTE_SDK)/mk/rte.lib.mk
diff --git a/lib/librte_efd/rte_efd.c b/lib/librte_efd/rte_efd.c
new file mode 100644
index 0000000..2bcfd62
--- /dev/null
+++ b/lib/librte_efd/rte_efd.c
@@ -0,0 +1,1324 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <math.h>
+#include <sys/queue.h>
+
+#include <rte_log.h>
+#include <rte_eal_memconfig.h>
+#include <rte_errno.h>
+#include <rte_malloc.h>
+#include <rte_memzone.h>
+#include <rte_prefetch.h>
+#include <rte_branch_prediction.h>
+#include <rte_memcpy.h>
+#include <rte_ring.h>
+#include <rte_jhash.h>
+#include <rte_hash_crc.h>
+
+#include "rte_efd.h"
+
+#define EFD_KEY(key_idx, table) (table->keys + ((key_idx) * table->key_len))
+/** Hash function used to determine chunk_id and bin_id for a group */
+#define EFD_HASH(key, table) \
+ (uint32_t)(rte_jhash(key, table->key_len, 0xbc9f1d34))
+/** Hash function used as constant component of perfect hash search */
+#define EFD_HASHFUNCA(key, table) \
+ (uint32_t)(rte_hash_crc(key, table->key_len, 0xbc9f1d35))
+/** Hash function used as multiplicative component of perfect hash search */
+#define EFD_HASHFUNCB(key, table) \
+ (uint32_t)(rte_hash_crc(key, table->key_len, 0xbc9f1d36))
+
+/*************************************************************************
+ * Fixed constants
+ *************************************************************************/
+
+/* These parameters are fixed by the efd_bin_to_group balancing table */
+#define EFD_CHUNK_NUM_GROUPS (64)
+#define EFD_CHUNK_NUM_BINS (256)
+#define EFD_CHUNK_NUM_BIN_TO_GROUP_SETS \
+ (EFD_CHUNK_NUM_BINS / EFD_CHUNK_NUM_GROUPS)
+
+/*
+ * Target number of rules that each chunk is created to handle.
+ * Used when initially allocating the table
+ */
+#define EFD_TARGET_CHUNK_NUM_RULES \
+ (EFD_CHUNK_NUM_GROUPS * EFD_TARGET_GROUP_NUM_RULES)
+/*
+ * Max number of rules that each chunk is created to handle.
+ * Used when initially allocating the table
+ */
+#define EFD_TARGET_CHUNK_MAX_NUM_RULES \
+ (EFD_CHUNK_NUM_GROUPS * EFD_MAX_GROUP_NUM_RULES)
+
+/** This is fixed based on the bin_to_group permutation array */
+#define EFD_MAX_GROUP_NUM_BINS (16)
+
+/**
+ * The end of the chunks array needs some extra padding to ensure
+ * that vectorization over-reads on the last online chunk stay within
+allocated memory
+ */
+#define EFD_NUM_CHUNK_PADDING_BYTES (256)
+
+/* All different internal lookup functions */
+enum efd_lookup_internal_function {
+ EFD_LOOKUP_SCALAR = 0,
+ EFD_LOOKUP_NUM
+};
+
+TAILQ_HEAD(rte_efd_list, rte_tailq_entry);
+
+static struct rte_tailq_elem rte_efd_tailq = {
+ .name = "RTE_EFD",
+};
+EAL_REGISTER_TAILQ(rte_efd_tailq);
+
+/** Internal permutation array used to shuffle bins into pseudorandom groups */
+const uint32_t efd_bin_to_group[EFD_CHUNK_NUM_BIN_TO_GROUP_SETS][EFD_CHUNK_NUM_BINS] = {
+ {
+ 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3,
+ 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
+ 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11,
+ 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15,
+ 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19,
+ 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23,
+ 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27,
+ 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31,
+ 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35,
+ 36, 36, 36, 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39,
+ 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, 42, 43, 43, 43, 43,
+ 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47,
+ 48, 48, 48, 48, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51,
+ 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, 54, 55, 55, 55, 55,
+ 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59,
+ 60, 60, 60, 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63
+ },
+ {
+ 34, 33, 48, 59, 0, 21, 36, 18, 9, 49, 54, 38, 51, 23, 31, 5,
+ 44, 23, 37, 52, 11, 4, 58, 20, 38, 40, 38, 22, 26, 28, 42, 6,
+ 46, 16, 31, 28, 46, 14, 60, 0, 35, 53, 16, 58, 16, 29, 39, 7,
+ 1, 54, 15, 11, 48, 3, 62, 9, 58, 5, 30, 43, 17, 7, 36, 34,
+ 6, 36, 2, 14, 10, 1, 47, 47, 20, 45, 62, 56, 34, 25, 39, 18,
+ 51, 41, 61, 25, 56, 40, 41, 37, 52, 35, 30, 57, 11, 42, 37, 27,
+ 54, 19, 26, 13, 48, 31, 46, 15, 12, 10, 16, 20, 43, 17, 12, 55,
+ 45, 18, 8, 41, 7, 31, 42, 63, 12, 14, 21, 57, 24, 40, 5, 41,
+ 13, 44, 23, 59, 25, 57, 52, 50, 62, 1, 2, 49, 32, 57, 26, 43,
+ 56, 60, 55, 5, 49, 6, 3, 50, 46, 39, 27, 33, 17, 4, 53, 13,
+ 2, 19, 36, 51, 63, 0, 22, 33, 59, 28, 29, 23, 45, 33, 53, 27,
+ 22, 21, 40, 56, 4, 18, 44, 47, 28, 17, 4, 50, 21, 62, 8, 39,
+ 0, 8, 15, 24, 29, 24, 9, 11, 48, 61, 35, 55, 43, 1, 54, 42,
+ 53, 60, 22, 3, 32, 52, 25, 8, 15, 60, 7, 55, 27, 63, 19, 10,
+ 63, 24, 61, 19, 12, 38, 6, 29, 13, 37, 10, 3, 45, 32, 32, 30,
+ 49, 61, 44, 14, 20, 58, 35, 30, 2, 26, 34, 51, 9, 59, 47, 50
+ },
+ {
+ 32, 35, 32, 34, 55, 5, 6, 23, 49, 11, 6, 23, 52, 37, 29, 54,
+ 55, 40, 63, 50, 29, 52, 61, 25, 12, 56, 39, 38, 29, 11, 46, 1,
+ 40, 11, 19, 56, 7, 28, 51, 16, 15, 48, 21, 51, 60, 31, 14, 22,
+ 41, 47, 59, 56, 53, 28, 58, 26, 43, 27, 41, 33, 24, 52, 44, 38,
+ 13, 59, 48, 51, 60, 15, 3, 30, 15, 0, 10, 62, 44, 14, 28, 51,
+ 38, 2, 41, 26, 25, 49, 10, 12, 55, 57, 27, 35, 19, 33, 0, 30,
+ 5, 36, 47, 53, 5, 53, 20, 43, 34, 37, 52, 41, 21, 63, 59, 9,
+ 24, 1, 45, 24, 39, 44, 45, 16, 9, 17, 7, 50, 57, 22, 18, 28,
+ 25, 45, 2, 40, 58, 15, 17, 3, 1, 27, 61, 39, 19, 0, 19, 21,
+ 57, 62, 54, 60, 54, 40, 48, 33, 36, 37, 4, 42, 1, 43, 58, 8,
+ 13, 42, 10, 56, 35, 22, 48, 61, 63, 10, 49, 9, 24, 9, 25, 57,
+ 33, 18, 13, 31, 42, 36, 36, 55, 30, 37, 53, 34, 59, 4, 4, 23,
+ 8, 16, 58, 14, 30, 11, 12, 63, 49, 62, 2, 39, 47, 22, 2, 60,
+ 18, 8, 46, 31, 6, 20, 32, 29, 46, 42, 20, 31, 32, 61, 34, 4,
+ 47, 26, 20, 43, 26, 21, 7, 3, 16, 35, 18, 44, 27, 62, 13, 23,
+ 6, 50, 12, 8, 45, 17, 3, 46, 50, 7, 14, 5, 17, 54, 38, 0
+ },
+ {
+ 29, 56, 5, 7, 54, 48, 23, 37, 35, 44, 52, 40, 33, 49, 60, 0,
+ 59, 51, 28, 12, 41, 26, 2, 23, 34, 5, 59, 40, 3, 19, 6, 26,
+ 35, 53, 45, 49, 29, 57, 28, 62, 58, 59, 19, 53, 59, 62, 6, 54,
+ 13, 15, 48, 50, 45, 21, 41, 12, 34, 40, 24, 56, 19, 21, 35, 18,
+ 55, 45, 9, 61, 47, 61, 19, 15, 16, 39, 17, 31, 3, 51, 21, 50,
+ 17, 25, 25, 11, 44, 16, 18, 28, 14, 2, 37, 61, 58, 27, 62, 4,
+ 14, 17, 1, 9, 46, 28, 37, 0, 53, 43, 57, 7, 57, 46, 21, 41,
+ 39, 14, 52, 60, 44, 53, 49, 60, 49, 63, 13, 11, 29, 1, 55, 47,
+ 55, 12, 60, 43, 54, 37, 13, 6, 42, 10, 36, 13, 9, 8, 34, 51,
+ 31, 32, 12, 7, 57, 2, 26, 14, 3, 30, 63, 3, 32, 1, 5, 11,
+ 27, 24, 26, 44, 31, 23, 56, 38, 62, 0, 40, 30, 6, 23, 38, 2,
+ 47, 5, 15, 27, 16, 10, 31, 25, 22, 63, 30, 25, 20, 33, 32, 50,
+ 29, 43, 55, 10, 50, 45, 56, 20, 4, 7, 27, 46, 11, 16, 22, 52,
+ 35, 20, 41, 54, 46, 33, 42, 18, 63, 8, 22, 58, 36, 4, 51, 42,
+ 38, 32, 38, 22, 17, 0, 47, 8, 48, 8, 48, 1, 61, 36, 33, 20,
+ 24, 39, 39, 18, 30, 36, 9, 43, 42, 24, 10, 58, 4, 15, 34, 52
+ },
+};
+
+/*************************************************************************
+ * Offline region structures
+ *************************************************************************/
+
+/** Online group containing number of rules, values, keys and their bins
+ * for EFD_MAX_GROUP_NUM_RULES rules.
+ */
+struct efd_offline_group_rules {
+ uint32_t num_rules;
+ /**< Sum of the number of rules in all bins assigned to this group. */
+
+ uint32_t key_idx[EFD_MAX_GROUP_NUM_RULES];
+ /**< Array with all keys of the group. */
+ efd_value_t value[EFD_MAX_GROUP_NUM_RULES];
+ /**< Array with all values of the keys of the group. */
+
+ uint8_t bin_id[EFD_MAX_GROUP_NUM_RULES];
+ /**< Stores the bin for each correspending key to
+ * avoid having to recompute it
+ */
+};
+
+/** Offline chunk record, containing EFD_TARGET_CHUNK_NUM_RULES rules.
+ * Those rules are split into EFD_CHUNK_NUM_GROUPS groups per chunk.
+ */
+struct efd_offline_chunk_rules {
+ uint16_t num_rules;
+ /**< Number of rules in the entire chunk;
+ * used to detect unbalanced groups
+ */
+
+ struct efd_offline_group_rules group_rules[EFD_CHUNK_NUM_GROUPS];
+ /**< Array of all groups in the chunk. */
+};
+
+/*************************************************************************
+ * Online region structures
+ *************************************************************************/
+
+/** Online group containing values for EFD_MAX_GROUP_NUM_RULES rules. */
+struct efd_online_group_entry {
+ efd_hashfunc_t hash_idx[RTE_EFD_VALUE_NUM_BITS];
+ efd_lookuptbl_t lookup_table[RTE_EFD_VALUE_NUM_BITS];
+} __attribute__((__packed__));
+
+/**
+ * A single chunk record, containing EFD_TARGET_CHUNK_NUM_RULES rules.
+ * Those rules are split into EFD_CHUNK_NUM_GROUPS groups per chunk.
+ */
+struct efd_online_chunk {
+ uint8_t bin_choice_list[(EFD_CHUNK_NUM_BINS * 2 + 7) / 8];
+ /**< This is a packed indirection index into the 'groups' array.
+ * Each byte contains four two-bit values which index into
+ * the efd_bin_to_group array.
+ * The efd_bin_to_group array returns the index into the groups array
+ */
+
+ struct efd_online_group_entry groups[EFD_CHUNK_NUM_GROUPS];
+ /**< Array of all the groups in the chunk. */
+} __attribute__((__packed__));
+
+/**
+ * EFD table structure
+ */
+struct rte_efd_table {
+ char name[RTE_EFD_NAMESIZE]; /**< Name of the efd table. */
+
+ uint32_t key_len; /**< Length of the key stored offline */
+
+ uint32_t max_num_rules;
+ /**< Static maximum number of entries the table was constructed to hold. */
+
+ uint32_t num_rules;
+ /**< Number of entries currently in the table . */
+
+ uint32_t num_chunks;
+ /**< Number of chunks in the table needed to support num_rules. */
+
+ uint32_t num_chunks_shift;
+ /**< Bits to shift to get chunk id, instead of dividing by num_chunk. */
+
+ enum efd_lookup_internal_function lookup_fn;
+ /**< Indicates which lookup function to use. */
+
+ struct efd_online_chunk *chunks[RTE_MAX_NUMA_NODES];
+ /**< Dynamic array of size num_chunks of chunk records. */
+
+ struct efd_offline_chunk_rules *offline_chunks;
+ /**< Dynamic array of size num_chunks of key-value pairs. */
+
+ struct rte_ring *free_slots;
+ /**< Ring that stores all indexes of the free slots in the key table */
+
+ uint8_t *keys; /**< Dynamic array of size max_num_rules of keys */
+};
+
+/**
+ * Computes the chunk ID for a given key hash
+ *
+ * @param table
+ * EFD table to reference
+ * @param hashed_key
+ * 32-bit key hash returned by EFD_HASH
+ *
+ * @return
+ * chunk ID containing this key hash
+ */
+static inline uint32_t
+efd_get_chunk_id(const struct rte_efd_table * const table,
+ const uint32_t hashed_key)
+{
+ return hashed_key & (table->num_chunks - 1);
+}
+
+/**
+ * Computes the bin ID for a given key hash
+ *
+ * @param table
+ * EFD table to reference
+ * @param hashed_key
+ * 32-bit key hash returned by EFD_HASH
+ *
+ * @return bin ID containing this key hash
+ */
+static inline uint32_t
+efd_get_bin_id(const struct rte_efd_table * const table,
+ const uint32_t hashed_key)
+{
+ return (hashed_key >> table->num_chunks_shift) & (EFD_CHUNK_NUM_BINS - 1);
+}
+
+/**
+ * Looks up the current permutation choice for a particular bin in the online table
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to look up existing values (ideally caller's socket id)
+ * @param chunk_id
+ * Chunk ID of bin to look up
+ * @param bin_id
+ * Bin ID to look up
+ *
+ * @return
+ * Currently active permutation choice in the online table
+ */
+static inline uint8_t
+efd_get_choice(const struct rte_efd_table * const table,
+ const unsigned int socket_id, const uint32_t chunk_id,
+ const uint32_t bin_id)
+{
+ struct efd_online_chunk *chunk = &table->chunks[socket_id][chunk_id];
+
+ /*
+ * Grab the chunk (byte) that contains the choices
+ * for four neighboring bins.
+ */
+ uint8_t choice_chunk =
+ chunk->bin_choice_list[bin_id / EFD_CHUNK_NUM_BIN_TO_GROUP_SETS];
+
+ /*
+ * Compute the offset into the chunk that contains
+ * the group_id lookup position
+ */
+ int offset = (bin_id & 0x3) * 2;
+
+ /* Extract from the byte just the desired lookup position */
+ return (uint8_t) ((choice_chunk >> offset) & 0x3);
+}
+
+/**
+ * Compute the chunk_id and bin_id for a given key
+ *
+ * @param table
+ * EFD table to reference
+ * @param key
+ * Key to hash and find location of
+ * @param chunk_id
+ * Computed chunk ID
+ * @param bin_id
+ * Computed bin ID
+ *
+ */
+static inline void
+efd_compute_ids(const struct rte_efd_table * const table,
+ const void *key, uint32_t * const chunk_id, uint32_t * const bin_id)
+{
+ /* Compute the position of the entry in the hash table */
+ uint32_t h = EFD_HASH(key, table);
+
+ /* Compute the chunk_id where that entry can be found */
+ *chunk_id = efd_get_chunk_id(table, h);
+
+ /*
+ * Compute the bin within that chunk where the entry
+ * can be found (0 - 255)
+ */
+ *bin_id = efd_get_bin_id(table, h);
+}
+
+/**
+ * Search for a hash function for a group that satisfies all group results
+ */
+static inline int
+efd_search_hash(struct rte_efd_table * const table,
+ const struct efd_offline_group_rules * const off_group,
+ struct efd_online_group_entry * const on_group)
+{
+ efd_hashfunc_t hash_idx;
+ efd_hashfunc_t start_hash_idx[RTE_EFD_VALUE_NUM_BITS];
+ efd_lookuptbl_t start_lookup_table[RTE_EFD_VALUE_NUM_BITS];
+
+ uint32_t i, j, rule_id;
+ uint32_t hash_val_a[EFD_MAX_GROUP_NUM_RULES];
+ uint32_t hash_val_b[EFD_MAX_GROUP_NUM_RULES];
+ uint32_t hash_val[EFD_MAX_GROUP_NUM_RULES];
+
+
+ rte_prefetch0(off_group->value);
+
+ /*
+ * Prepopulate the hash_val tables by running the two hash functions
+ * for each provided rule
+ */
+ for (i = 0; i < off_group->num_rules; i++) {
+ void *key_stored = EFD_KEY(off_group->key_idx[i], table);
+ hash_val_b[i] = EFD_HASHFUNCB(key_stored, table);
+ hash_val_a[i] = EFD_HASHFUNCA(key_stored, table);
+ }
+
+ for (i = 0; i < RTE_EFD_VALUE_NUM_BITS; i++) {
+ hash_idx = on_group->hash_idx[i];
+ start_hash_idx[i] = hash_idx;
+ start_lookup_table[i] = on_group->lookup_table[i];
+
+ do {
+ efd_lookuptbl_t lookup_table = 0;
+ efd_lookuptbl_t lookup_table_complement = 0;
+
+ for (rule_id = 0; rule_id < off_group->num_rules; rule_id++)
+ hash_val[rule_id] = hash_val_a[rule_id] + (hash_idx *
+ hash_val_b[rule_id]);
+
+ /*
+ * The goal here is to find a hash function for this
+ * particular bit entry that meets the following criteria:
+ * The most significant bits of the hash result define a
+ * shift into the lookup table where the bit will be stored
+ */
+
+ /* Iterate over each provided rule */
+ for (rule_id = 0; rule_id < off_group->num_rules;
+ rule_id++) {
+ /*
+ * Use the few most significant bits (number based on
+ * EFD_LOOKUPTBL_SIZE) to see what position the
+ * expected bit should be set in the lookup_table
+ */
+ uint32_t bucket_idx = hash_val[rule_id] >>
+ EFD_LOOKUPTBL_SHIFT;
+
+ /*
+ * Get the current bit of interest.
+ * This only find an appropriate hash function
+ * for one bit at a time of the rule
+ */
+ efd_lookuptbl_t expected =
+ (off_group->value[rule_id] >> i) & 0x1;
+
+ /*
+ * Add the expected bit (if set) to a map
+ * (lookup_table). Also set its complement
+ * in lookup_table_complement
+ */
+ lookup_table |= expected << bucket_idx;
+ lookup_table_complement |= (1 - expected)
+ << bucket_idx;
+
+ /*
+ * If ever the hash function of two different
+ * elements result in different values at the
+ * same location in the lookup_table,
+ * the current hash_idx is not valid.
+ */
+ if (lookup_table & lookup_table_complement)
+ break;
+ }
+
+ /*
+ * Check if the previous loop completed without
+ * breaking early
+ */
+ if (rule_id == off_group->num_rules) {
+ /*
+ * Current hash function worked, store it
+ * for the current group
+ */
+ on_group->hash_idx[i] = hash_idx;
+ on_group->lookup_table[i] = lookup_table;
+
+ /*
+ * Make sure that the hash function has changed
+ * from the starting value
+ */
+ hash_idx = start_hash_idx[i] + 1;
+ break;
+ }
+ hash_idx++;
+
+ } while (hash_idx != start_hash_idx[i]);
+
+ /* Failed to find perfect hash for this group */
+ if (hash_idx == start_hash_idx[i]) {
+ /*
+ * Restore previous hash_idx and lookup_table
+ * for all value bits
+ */
+ for (j = 0; j < i; j++) {
+ on_group->hash_idx[j] = start_hash_idx[j];
+ on_group->lookup_table[j] = start_lookup_table[j];
+ }
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+struct rte_efd_table *
+rte_efd_create(const char *name, uint32_t max_num_rules, uint32_t key_len,
+ uint8_t online_cpu_socket_bitmask, uint8_t offline_cpu_socket)
+{
+ struct rte_efd_table *table = NULL;
+ uint8_t *key_array = NULL;
+ uint32_t num_chunks, num_chunks_shift;
+ uint8_t socket_id;
+ struct rte_efd_list *efd_list = NULL;
+ struct rte_tailq_entry *te;
+ uint64_t offline_table_size;
+ char ring_name[RTE_RING_NAMESIZE];
+ struct rte_ring *r = NULL;
+ unsigned int i;
+
+ efd_list = RTE_TAILQ_CAST(rte_efd_tailq.head, rte_efd_list);
+
+ if (online_cpu_socket_bitmask == 0) {
+ RTE_LOG(ERR, EFD, "At least one CPU socket must be enabled "
+ "in the bitmask\n");
+ return NULL;
+ }
+
+ if (max_num_rules == 0) {
+ RTE_LOG(ERR, EFD, "Max num rules must be higher than 0\n");
+ return NULL;
+ }
+
+ /*
+ * Compute the minimum number of chunks (smallest power of 2)
+ * that can hold all of the rules
+ */
+ if (max_num_rules % EFD_TARGET_CHUNK_NUM_RULES == 0)
+ num_chunks = rte_align32pow2(max_num_rules /
+ EFD_TARGET_CHUNK_NUM_RULES);
+ else
+ num_chunks = rte_align32pow2((max_num_rules /
+ EFD_TARGET_CHUNK_NUM_RULES) + 1);
+
+ num_chunks_shift = log2(num_chunks);
+
+ rte_rwlock_write_lock(RTE_EAL_TAILQ_RWLOCK);
+
+ /*
+ * Guarantee there's no existing: this is normally already checked
+ * by ring creation above
+ */
+ TAILQ_FOREACH(te, efd_list, next)
+ {
+ table = (struct rte_efd_table *) te->data;
+ if (strncmp(name, table->name, RTE_EFD_NAMESIZE) == 0)
+ break;
+ }
+
+ table = NULL;
+ if (te != NULL) {
+ rte_errno = EEXIST;
+ te = NULL;
+ goto error_unlock_exit;
+ }
+
+ te = rte_zmalloc("EFD_TAILQ_ENTRY", sizeof(*te), 0);
+ if (te == NULL) {
+ RTE_LOG(ERR, EFD, "tailq entry allocation failed\n");
+ goto error_unlock_exit;
+ }
+
+ /* Create a new EFD table management structure */
+ table = (struct rte_efd_table *) rte_zmalloc_socket(NULL,
+ sizeof(struct rte_efd_table),
+ RTE_CACHE_LINE_SIZE,
+ offline_cpu_socket);
+ if (table == NULL) {
+ RTE_LOG(ERR, EFD, "Allocating EFD table management structure"
+ " on socket %u failed\n",
+ offline_cpu_socket);
+ goto error_unlock_exit;
+ }
+
+
+ RTE_LOG(DEBUG, EFD, "Allocated EFD table management structure "
+ "on socket %u\n", offline_cpu_socket);
+
+ table->max_num_rules = num_chunks * EFD_TARGET_CHUNK_MAX_NUM_RULES;
+ table->num_rules = 0;
+ table->num_chunks = num_chunks;
+ table->num_chunks_shift = num_chunks_shift;
+ table->key_len = key_len;
+
+ /* key_array */
+ key_array = (uint8_t *) rte_zmalloc_socket(NULL,
+ table->max_num_rules * table->key_len,
+ RTE_CACHE_LINE_SIZE,
+ offline_cpu_socket);
+ if (key_array == NULL) {
+ RTE_LOG(ERR, EFD, "Allocating key array"
+ " on socket %u failed\n",
+ offline_cpu_socket);
+ goto error_unlock_exit;
+ }
+ table->keys = key_array;
+ snprintf(table->name, sizeof(table->name), "%s", name);
+
+ RTE_LOG(DEBUG, EFD, "Creating an EFD table with %u chunks,"
+ " which potentially supports %u entries\n",
+ num_chunks, table->max_num_rules);
+
+ /* Make sure all the allocatable table pointers are NULL initially */
+ for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++)
+ table->chunks[socket_id] = NULL;
+ table->offline_chunks = NULL;
+
+ /*
+ * Allocate one online table per socket specified
+ * in the user-supplied bitmask
+ */
+ uint64_t online_table_size = num_chunks * sizeof(struct efd_online_chunk) +
+ EFD_NUM_CHUNK_PADDING_BYTES;
+
+ for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++) {
+ if ((online_cpu_socket_bitmask >> socket_id) & 0x01) {
+ /*
+ * Allocate all of the EFD table chunks (the online portion)
+ * as a continuous block
+ */
+ table->chunks[socket_id] =
+ (struct efd_online_chunk *) rte_zmalloc_socket(
+ NULL,
+ online_table_size,
+ RTE_CACHE_LINE_SIZE,
+ socket_id);
+ if (table->chunks[socket_id] == NULL) {
+ RTE_LOG(ERR, EFD,
+ "Allocating EFD online table on "
+ "socket %u failed\n",
+ socket_id);
+ goto error_unlock_exit;
+ }
+ RTE_LOG(DEBUG, EFD,
+ "Allocated EFD online table of size "
+ "%"PRIu64" bytes (%.2f MB) on socket %u\n",
+ online_table_size,
+ (float) online_table_size /
+ (1024.0F * 1024.0F),
+ socket_id);
+ }
+ }
+
+ table->lookup_fn = EFD_LOOKUP_SCALAR;
+
+ /*
+ * Allocate the EFD table offline portion (with the actual rules
+ * mapping keys to values) as a continuous block.
+ * This could be several gigabytes of memory.
+ */
+ offline_table_size = num_chunks * sizeof(struct efd_offline_chunk_rules);
+ table->offline_chunks =
+ (struct efd_offline_chunk_rules *) rte_zmalloc_socket(NULL,
+ offline_table_size,
+ RTE_CACHE_LINE_SIZE,
+ offline_cpu_socket);
+ if (table->offline_chunks == NULL) {
+ RTE_LOG(ERR, EFD, "Allocating EFD offline table on socket %u "
+ "failed\n", offline_cpu_socket);
+ goto error_unlock_exit;
+ }
+
+ RTE_LOG(DEBUG, EFD,
+ "Allocated EFD offline table of size %"PRIu64" bytes "
+ " (%.2f MB) on socket %u\n", offline_table_size,
+ (float) offline_table_size / (1024.0F * 1024.0F),
+ offline_cpu_socket);
+
+ te->data = (void *) table;
+ TAILQ_INSERT_TAIL(efd_list, te, next);
+ rte_rwlock_write_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+ snprintf(ring_name, sizeof(ring_name), "HT_%s", table->name);
+ /* Create ring (Dummy slot index is not enqueued) */
+ r = rte_ring_create(ring_name, rte_align32pow2(table->max_num_rules),
+ offline_cpu_socket, 0);
+ if (r == NULL) {
+ RTE_LOG(ERR, EFD, "memory allocation failed\n");
+ goto error_unlock_exit;
+ }
+
+ /* Populate free slots ring. Entry zero is reserved for key misses. */
+ for (i = 0; i < table->max_num_rules; i++)
+ rte_ring_sp_enqueue(r, (void *) ((uintptr_t) i));
+
+ table->free_slots = r;
+ return table;
+
+error_unlock_exit:
+ rte_rwlock_write_unlock(RTE_EAL_TAILQ_RWLOCK);
+ rte_efd_free(table);
+
+ return NULL;
+}
+
+struct rte_efd_table *
+rte_efd_find_existing(const char *name)
+{
+ struct rte_efd_table *table = NULL;
+ struct rte_tailq_entry *te;
+ struct rte_efd_list *efd_list;
+
+ efd_list = RTE_TAILQ_CAST(rte_efd_tailq.head, rte_efd_list);
+
+ rte_rwlock_read_lock(RTE_EAL_TAILQ_RWLOCK);
+
+ TAILQ_FOREACH(te, efd_list, next)
+ {
+ table = (struct rte_efd_table *) te->data;
+ if (strncmp(name, table->name, RTE_EFD_NAMESIZE) == 0)
+ break;
+ }
+ rte_rwlock_read_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+ if (te == NULL) {
+ rte_errno = ENOENT;
+ return NULL;
+ }
+ return table;
+}
+
+void
+rte_efd_free(struct rte_efd_table *table)
+{
+ uint8_t socket_id;
+
+ if (table == NULL)
+ return;
+
+ for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++)
+ rte_free(table->chunks[socket_id]);
+
+ rte_ring_free(table->free_slots);
+ rte_free(table->offline_chunks);
+ rte_free(table->keys);
+ rte_free(table);
+}
+
+/**
+ * Applies a previously computed table entry to the specified table for all
+ * socket-local copies of the online table.
+ * Intended to apply an update for only a single change
+ * to a key/value pair at a time
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing values (ideally caller's socket id)
+ * @param chunk_id
+ * Chunk index to update
+ * @param group_id
+ * Group index to update
+ * @param bin_id
+ * Bin within the group that this update affects
+ * @param new_bin_choice
+ * Newly chosen permutation which this bin should use - only lower 2 bits
+ * @param new_group_entry
+ * Previously computed updated chunk/group entry
+ */
+static inline void
+efd_apply_update(struct rte_efd_table * const table, const unsigned int socket_id,
+ const uint32_t chunk_id, const uint32_t group_id,
+ const uint32_t bin_id, const uint8_t new_bin_choice,
+ const struct efd_online_group_entry * const new_group_entry)
+{
+ int i;
+ struct efd_online_chunk *chunk = &table->chunks[socket_id][chunk_id];
+ uint8_t bin_index = bin_id / EFD_CHUNK_NUM_BIN_TO_GROUP_SETS;
+
+ /*
+ * Grab the current byte that contains the choices
+ * for four neighboring bins
+ */
+ uint8_t choice_chunk =
+ chunk->bin_choice_list[bin_index];
+
+
+ /* Compute the offset into the chunk that needs to be updated */
+ int offset = (bin_id & 0x3) * 2;
+
+ /* Zero the two bits of interest and set them to new_bin_choice */
+ choice_chunk = (choice_chunk & (~(0x03 << offset)))
+ | ((new_bin_choice & 0x03) << offset);
+
+ /* Update the online table with the new data across all sockets */
+ for (i = 0; i < RTE_MAX_NUMA_NODES; i++) {
+ if (table->chunks[i] != NULL) {
+ memcpy(&(table->chunks[i][chunk_id].groups[group_id]),
+ new_group_entry,
+ sizeof(struct efd_online_group_entry));
+ table->chunks[i][chunk_id].bin_choice_list[bin_index] =
+ choice_chunk;
+ }
+ }
+}
+
+/*
+ * Move the bin from prev group to the new group
+ */
+static inline void
+move_groups(uint32_t bin_id, uint8_t bin_size,
+ struct efd_offline_group_rules *new_group,
+ struct efd_offline_group_rules * const current_group)
+{
+
+ uint8_t empty_idx = 0;
+ unsigned int i;
+
+ if (new_group == current_group)
+ return;
+
+ for (i = 0; i < current_group->num_rules; i++) {
+ /*
+ * Move keys that belong to the same bin
+ * to the new group
+ */
+ if (current_group->bin_id[i] == bin_id) {
+ new_group->key_idx[new_group->num_rules] =
+ current_group->key_idx[i];
+ new_group->value[new_group->num_rules] =
+ current_group->value[i];
+ new_group->bin_id[new_group->num_rules] =
+ current_group->bin_id[i];
+ new_group->num_rules++;
+ } else {
+ if (i != empty_idx) {
+ /*
+ * Need to move this key towards
+ * the top of the array
+ */
+ current_group->key_idx[empty_idx] =
+ current_group->key_idx[i];
+ current_group->value[empty_idx] =
+ current_group->value[i];
+ current_group->bin_id[empty_idx] =
+ current_group->bin_id[i];
+ }
+ empty_idx++;
+ }
+
+ }
+ current_group->num_rules -= bin_size;
+}
+
+/*
+ * Revert group/s to their previous state before
+ * trying to insert/add a new key
+ */
+static inline void
+revert_groups(struct efd_offline_group_rules *previous_group,
+ struct efd_offline_group_rules *current_group, uint8_t bin_size)
+{
+ unsigned int i;
+
+ if (current_group == previous_group)
+ return;
+
+ /* Move keys back to previous group */
+ for (i = current_group->num_rules - bin_size;
+ i < current_group->num_rules; i++) {
+ previous_group->key_idx[previous_group->num_rules] =
+ current_group->key_idx[i];
+ previous_group->value[previous_group->num_rules] =
+ current_group->value[i];
+ previous_group->bin_id[previous_group->num_rules] =
+ current_group->bin_id[i];
+ previous_group->num_rules++;
+ }
+
+ /*
+ * Decrease number of rules after the move
+ * in the new group
+ */
+ current_group->num_rules -= bin_size;
+}
+
+/**
+ * Computes an updated table entry where the supplied key points to a new host.
+ * If no entry exists, one is inserted.
+ *
+ * This function does NOT modify the online table(s)
+ * This function DOES modify the offline table
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing values (ideally caller's socket id)
+ * @param key
+ * Key to insert
+ * @param value
+ * Value to associate with key
+ * @param chunk_id
+ * Chunk ID of the chunk that was modified
+ * @param group_id
+ * Group ID of the group that was modified
+ * @param bin_id
+ * Bin ID that was modified
+ * @param new_bin_choice
+ * Newly chosen permutation which this bin will use
+ * @param entry
+ * Newly computed online entry to apply later with efd_apply_update
+ *
+ * @return
+ * RTE_EFD_UPDATE_WARN_GROUP_FULL
+ * Operation is insert, and the last available space in the
+ * key's group was just used. Future inserts may fail as groups fill up.
+ * This operation was still successful, and entry contains a valid update
+ * RTE_EFD_UPDATE_FAILED
+ * Either the EFD failed to find a suitable perfect hash or the group was full
+ * This is a fatal error, and the table is now in an indeterminite state
+ * RTE_EFD_UPDATE_NO_CHANGE
+ * Operation resulted in no change to the table (same value already exists)
+ * 0
+ * Insert or update was successful, and the new efd_online_group_entry
+ * is stored in *entry
+ *
+ * @warning
+ * Note that entry will be UNCHANGED if the update has no effect, and thus any
+ * subsequent use of the entry content will likely be invalid
+ */
+static inline int
+efd_compute_update(struct rte_efd_table * const table,
+ const unsigned int socket_id, const void *key,
+ const efd_value_t value, uint32_t * const chunk_id,
+ uint32_t * const group_id, uint32_t * const bin_id,
+ uint8_t * const new_bin_choice,
+ struct efd_online_group_entry * const entry)
+{
+ unsigned int i;
+ int ret;
+ uint32_t new_idx;
+ void *new_k, *slot_id = NULL;
+ int status = EXIT_SUCCESS;
+ unsigned int found = 0;
+
+ efd_compute_ids(table, key, chunk_id, bin_id);
+
+ struct efd_offline_chunk_rules * const chunk =
+ &table->offline_chunks[*chunk_id];
+ struct efd_offline_group_rules *new_group;
+
+ uint8_t current_choice = efd_get_choice(table, socket_id,
+ *chunk_id, *bin_id);
+ uint32_t current_group_id = efd_bin_to_group[current_choice][*bin_id];
+ struct efd_offline_group_rules * const current_group =
+ &chunk->group_rules[current_group_id];
+ uint8_t bin_size = 0;
+ uint8_t key_changed_index = 0;
+ efd_value_t key_changed_previous_value = 0;
+ uint32_t key_idx_previous = 0;
+
+ /* Scan the current group and see if the key is already present */
+ for (i = 0; i < current_group->num_rules; i++) {
+ if (current_group->bin_id[i] == *bin_id)
+ bin_size++;
+ else
+ continue;
+
+ void *key_stored = EFD_KEY(current_group->key_idx[i], table);
+ if (found == 0 && unlikely(memcmp(key_stored, key,
+ table->key_len) == 0)) {
+ /* Key is already present */
+
+ /*
+ * If previous value is same as new value,
+ * no additional work is required
+ */
+ if (current_group->value[i] == value)
+ return RTE_EFD_UPDATE_NO_CHANGE;
+
+ key_idx_previous = current_group->key_idx[i];
+ key_changed_previous_value = current_group->value[i];
+ key_changed_index = i;
+ current_group->value[i] = value;
+ found = 1;
+ }
+ }
+
+ if (found == 0) {
+ /* Key does not exist. Insert the rule into the bin/group */
+ if (unlikely(current_group->num_rules >= EFD_MAX_GROUP_NUM_RULES)) {
+ RTE_LOG(ERR, EFD,
+ "Fatal: No room remaining for insert into "
+ "chunk %u group %u bin %u\n",
+ *chunk_id,
+ current_group_id, *bin_id);
+ return RTE_EFD_UPDATE_FAILED;
+ }
+
+ if (unlikely(current_group->num_rules ==
+ (EFD_MAX_GROUP_NUM_RULES - 1))) {
+ RTE_LOG(INFO, EFD, "Warn: Insert into last "
+ "available slot in chunk %u "
+ "group %u bin %u\n", *chunk_id,
+ current_group_id, *bin_id);
+ status = RTE_EFD_UPDATE_WARN_GROUP_FULL;
+ }
+
+ if (rte_ring_sc_dequeue(table->free_slots, &slot_id) != 0)
+ return RTE_EFD_UPDATE_FAILED;
+
+ new_k = RTE_PTR_ADD(table->keys, (uintptr_t) slot_id *
+ table->key_len);
+ rte_prefetch0(new_k);
+ new_idx = (uint32_t) ((uintptr_t) slot_id);
+
+ rte_memcpy(EFD_KEY(new_idx, table), key, table->key_len);
+ current_group->key_idx[current_group->num_rules] = new_idx;
+ current_group->value[current_group->num_rules] = value;
+ current_group->bin_id[current_group->num_rules] = *bin_id;
+ current_group->num_rules++;
+ table->num_rules++;
+ bin_size++;
+ } else {
+ uint32_t last = current_group->num_rules - 1;
+ /* Swap the key with the last key inserted*/
+ current_group->key_idx[key_changed_index] =
+ current_group->key_idx[last];
+ current_group->value[key_changed_index] =
+ current_group->value[last];
+ current_group->bin_id[key_changed_index] =
+ current_group->bin_id[last];
+
+ /*
+ * Key to be updated will always be available
+ * at the end of the group
+ */
+ current_group->key_idx[last] = key_idx_previous;
+ current_group->value[last] = value;
+ current_group->bin_id[last] = *bin_id;
+ }
+
+ *new_bin_choice = current_choice;
+ *group_id = current_group_id;
+ new_group = current_group;
+
+ /* Group need to be rebalanced when it starts to get loaded */
+ if (current_group->num_rules > EFD_MIN_BALANCED_NUM_RULES) {
+
+ /*
+ * Subtract the number of entries in the bin from
+ * the original group
+ */
+ current_group->num_rules -= bin_size;
+
+ /*
+ * Figure out which of the available groups that this bin
+ * can map to is the smallest (using the current group
+ * as baseline)
+ */
+ uint8_t smallest_choice = current_choice;
+ uint8_t smallest_size = current_group->num_rules;
+ uint32_t smallest_group_id = current_group_id;
+ unsigned char choice;
+
+ for (choice = 0; choice < EFD_CHUNK_NUM_BIN_TO_GROUP_SETS;
+ choice++) {
+ uint32_t test_group_id =
+ efd_bin_to_group[choice][*bin_id];
+ uint32_t num_rules =
+ chunk->group_rules[test_group_id].num_rules;
+ if (num_rules < smallest_size) {
+ smallest_choice = choice;
+ smallest_size = num_rules;
+ smallest_group_id = test_group_id;
+ }
+ }
+
+ *new_bin_choice = smallest_choice;
+ *group_id = smallest_group_id;
+ new_group = &chunk->group_rules[smallest_group_id];
+ current_group->num_rules += bin_size;
+
+ }
+
+ uint8_t choice = 0;
+ for (;;) {
+ if (current_group != new_group &&
+ new_group->num_rules + bin_size >
+ EFD_MAX_GROUP_NUM_RULES) {
+ RTE_LOG(DEBUG, EFD,
+ "Unable to move_groups to dest group "
+ "containing %u entries."
+ "bin_size:%u choice:%02x\n",
+ new_group->num_rules, bin_size,
+ choice - 1);
+ goto next_choice;
+ }
+ move_groups(*bin_id, bin_size, new_group, current_group);
+ /*
+ * Recompute the hash function for the modified group,
+ * and return it to the caller
+ */
+ ret = efd_search_hash(table, new_group, entry);
+
+ if (!ret)
+ return status;
+
+ RTE_LOG(DEBUG, EFD,
+ "Failed to find perfect hash for group "
+ "containing %u entries. bin_size:%u choice:%02x\n",
+ new_group->num_rules, bin_size, choice - 1);
+ /* Restore groups modified to their previous state */
+ revert_groups(current_group, new_group, bin_size);
+
+next_choice:
+ if (choice == EFD_CHUNK_NUM_BIN_TO_GROUP_SETS)
+ break;
+ *new_bin_choice = choice;
+ *group_id = efd_bin_to_group[choice][*bin_id];
+ new_group = &chunk->group_rules[*group_id];
+ choice++;
+ }
+
+ if (!found) {
+ current_group->num_rules--;
+ table->num_rules--;
+ } else
+ current_group->value[current_group->num_rules - 1] =
+ key_changed_previous_value;
+ return RTE_EFD_UPDATE_FAILED;
+}
+
+int
+rte_efd_update(struct rte_efd_table * const table, const unsigned int socket_id,
+ const void *key, const efd_value_t value)
+{
+ uint32_t chunk_id = 0, group_id = 0, bin_id = 0;
+ uint8_t new_bin_choice = 0;
+ struct efd_online_group_entry entry;
+
+ int status = efd_compute_update(table, socket_id, key, value,
+ &chunk_id, &group_id, &bin_id,
+ &new_bin_choice, &entry);
+
+ if (status == RTE_EFD_UPDATE_NO_CHANGE)
+ return EXIT_SUCCESS;
+
+ if (status == RTE_EFD_UPDATE_FAILED)
+ return status;
+
+ efd_apply_update(table, socket_id, chunk_id, group_id, bin_id,
+ new_bin_choice, &entry);
+ return status;
+}
+
+int
+rte_efd_delete(struct rte_efd_table * const table, const unsigned int socket_id,
+ const void *key, efd_value_t * const prev_value)
+{
+ unsigned int i;
+ uint32_t chunk_id, bin_id;
+ uint8_t not_found = 1;
+
+ efd_compute_ids(table, key, &chunk_id, &bin_id);
+
+ struct efd_offline_chunk_rules * const chunk =
+ &table->offline_chunks[chunk_id];
+
+ uint8_t current_choice = efd_get_choice(table, socket_id,
+ chunk_id, bin_id);
+ uint32_t current_group_id = efd_bin_to_group[current_choice][bin_id];
+ struct efd_offline_group_rules * const current_group =
+ &chunk->group_rules[current_group_id];
+
+ /*
+ * Search the current group for the specified key.
+ * If it exists, remove it and re-pack the other values
+ */
+ for (i = 0; i < current_group->num_rules; i++) {
+ if (not_found) {
+ /* Found key that needs to be removed */
+ if (memcmp(EFD_KEY(current_group->key_idx[i], table),
+ key, table->key_len) == 0) {
+ /* Store previous value if requested by caller */
+ if (prev_value != NULL)
+ *prev_value = current_group->value[i];
+
+ not_found = 0;
+ rte_ring_sp_enqueue(table->free_slots,
+ (void *)((uintptr_t)current_group->key_idx[i]));
+ }
+ } else {
+ /*
+ * If the desired key has been found,
+ * need to shift other values up one
+ */
+
+ /* Need to shift this entry back up one index */
+ current_group->key_idx[i - 1] = current_group->key_idx[i];
+ current_group->value[i - 1] = current_group->value[i];
+ current_group->bin_id[i - 1] = current_group->bin_id[i];
+ }
+ }
+
+ if (not_found == 0) {
+ table->num_rules--;
+ current_group->num_rules--;
+ }
+
+ return not_found;
+}
+
+static inline efd_value_t
+efd_lookup_internal_scalar(const efd_hashfunc_t *group_hash_idx,
+ const efd_lookuptbl_t *group_lookup_table,
+ const uint32_t hash_val_a, const uint32_t hash_val_b)
+{
+ efd_value_t value = 0;
+ uint32_t i;
+
+ for (i = 0; i < RTE_EFD_VALUE_NUM_BITS; i++) {
+ value <<= 1;
+ uint32_t h = hash_val_a + (hash_val_b *
+ group_hash_idx[RTE_EFD_VALUE_NUM_BITS - i - 1]);
+ uint16_t bucket_idx = h >> EFD_LOOKUPTBL_SHIFT;
+ value |= (group_lookup_table[
+ RTE_EFD_VALUE_NUM_BITS - i - 1] >>
+ bucket_idx) & 0x1;
+ }
+
+ return value;
+}
+
+
+static inline efd_value_t
+efd_lookup_internal(const struct efd_online_group_entry * const group,
+ const uint32_t hash_val_a, const uint32_t hash_val_b,
+ enum efd_lookup_internal_function lookup_fn)
+{
+ efd_value_t value = 0;
+
+ switch (lookup_fn) {
+
+ case EFD_LOOKUP_SCALAR:
+ /* Fall-through */
+ default:
+ return efd_lookup_internal_scalar(group->hash_idx,
+ group->lookup_table,
+ hash_val_a,
+ hash_val_b);
+ }
+
+ return value;
+}
+
+efd_value_t
+rte_efd_lookup(const struct rte_efd_table * const table,
+ const unsigned int socket_id, const void *key)
+{
+ uint32_t chunk_id, group_id, bin_id;
+ uint8_t bin_choice;
+ const struct efd_online_group_entry *group;
+ const struct efd_online_chunk * const chunks = table->chunks[socket_id];
+
+ /* Determine the chunk and group location for the given key */
+ efd_compute_ids(table, key, &chunk_id, &bin_id);
+ bin_choice = efd_get_choice(table, socket_id, chunk_id, bin_id);
+ group_id = efd_bin_to_group[bin_choice][bin_id];
+ group = &chunks[chunk_id].groups[group_id];
+
+ return efd_lookup_internal(group,
+ EFD_HASHFUNCA(key, table),
+ EFD_HASHFUNCB(key, table),
+ table->lookup_fn);
+}
+
+void rte_efd_lookup_bulk(const struct rte_efd_table * const table,
+ const unsigned int socket_id, const int num_keys,
+ const void **key_list, efd_value_t * const value_list)
+{
+ int i;
+ uint32_t chunk_id_list[RTE_EFD_BURST_MAX];
+ uint32_t bin_id_list[RTE_EFD_BURST_MAX];
+ uint8_t bin_choice_list[RTE_EFD_BURST_MAX];
+ uint32_t group_id_list[RTE_EFD_BURST_MAX];
+ struct efd_online_group_entry *group;
+
+ struct efd_online_chunk *chunks = table->chunks[socket_id];
+
+ for (i = 0; i < num_keys; i++) {
+ efd_compute_ids(table, key_list[i], &chunk_id_list[i],
+ &bin_id_list[i]);
+ rte_prefetch0(&chunks[chunk_id_list[i]].bin_choice_list);
+ }
+
+ for (i = 0; i < num_keys; i++) {
+ bin_choice_list[i] = efd_get_choice(table, socket_id,
+ chunk_id_list[i], bin_id_list[i]);
+ group_id_list[i] =
+ efd_bin_to_group[bin_choice_list[i]][bin_id_list[i]];
+ group = &chunks[chunk_id_list[i]].groups[group_id_list[i]];
+ rte_prefetch0(group);
+ }
+
+ for (i = 0; i < num_keys; i++) {
+ group = &chunks[chunk_id_list[i]].groups[group_id_list[i]];
+ value_list[i] = efd_lookup_internal(group,
+ EFD_HASHFUNCA(key_list[i], table),
+ EFD_HASHFUNCB(key_list[i], table),
+ table->lookup_fn);
+ }
+}
diff --git a/lib/librte_efd/rte_efd.h b/lib/librte_efd/rte_efd.h
new file mode 100644
index 0000000..1a1cb5b
--- /dev/null
+++ b/lib/librte_efd/rte_efd.h
@@ -0,0 +1,300 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _RTE_EFD_H_
+#define _RTE_EFD_H_
+
+/**
+ * @file
+ *
+ * RTE EFD Table
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*************************************************************************
+ * User selectable constants
+ *************************************************************************/
+
+/*
+ * If possible, best lookup performance will be achieved by ensuring that
+ * the entire table fits in the L3 cache.
+ *
+ * Some formulas for calculating various sizes are listed below:
+ *
+ * # of chunks =
+ * 2 ^ (ceiling(log2((requested # of rules) /
+ * (EFD_CHUNK_NUM_GROUPS * EFD_TARGET_GROUP_NUM_RULES))))
+ *
+ * Target # of rules = (# of chunks) * EFD_CHUNK_NUM_GROUPS *
+ * EFD_TARGET_GROUP_NUM_RULES
+ *
+ * Group Size (in bytes) = 4 (per value bit)
+ *
+ * Table size (in bytes) = RTE_EFD_VALUE_NUM_BITS * (# of chunks) *
+ * EFD_CHUNK_NUM_GROUPS * (group size)
+ */
+
+/**
+ * !!! This parameter should be adjusted for your application !!!
+ *
+ * This parameter adjusts the number of bits of value that can be
+ * stored in the table.
+ * For example, setting the number of bits to 3 will allow storing 8 values
+ * in the table (between 0 and 7).
+ *
+ * This number directly affects the performance of both lookups and insertion.
+ * In general, performance decreases as more bits are stored in the table.
+ *
+ * This number is directly proportional to the size of the online region
+ * used for lookups.
+ *
+ * Note that due to the way the CPU operates on memory, best lookup performance
+ * will be achieved when RTE_EFD_VALUE_NUM_BITS is a multiple of 8.
+ * These values align the hash indexes on 16-byte boundaries.
+ * The greatest performance drop is moving from 8->9 bits, 16->17 bits, etc.
+ *
+ * This value must be between 1 and 32
+ */
+#ifndef RTE_EFD_VALUE_NUM_BITS
+#define RTE_EFD_VALUE_NUM_BITS (8)
+#endif
+
+/*
+ * EFD_TARGET_GROUP_NUM_RULES:
+ * Adjusts how many groups/chunks are allocated at table creation time
+ * to support the requested number of rules. Higher values pack entries
+ * more tightly in memory, resulting in a smaller memory footprint
+ * for the online table.
+ * This comes at the cost of lower insert/update performance.
+ *
+ * EFD_MAX_GROUP_NUM_RULES:
+ * This adjusts the amount of offline memory allocated to store key/value
+ * pairs for the table. The recommended numbers are upper-bounds for
+ * this parameter
+ * - any higher and it becomes very unlikely that a perfect hash function
+ * can be found for that group size. This value should be at
+ * least 40% larger than EFD_TARGET_GROUP_NUM_RULES
+ *
+ * Recommended values for various lookuptable and hashfunc sizes are:
+ *
+ * HASH_FUNC_SIZE = 16, LOOKUPTBL_SIZE = 16:
+ * EFD_TARGET_GROUP_NUM_RULES = 22
+ * EFD_MAX_GROUP_NUM_RULES = 28
+ */
+#define EFD_TARGET_GROUP_NUM_RULES (22)
+#define EFD_MAX_GROUP_NUM_RULES (28LU)
+
+#define EFD_MIN_BALANCED_NUM_RULES 5
+
+/**
+ * Maximum number of keys that can be looked up in one call to efd_lookup_bulk
+ */
+#ifndef RTE_EFD_BURST_MAX
+#define RTE_EFD_BURST_MAX (32)
+#endif
+
+/** Maximum number of characters in efd name.*/
+#define RTE_EFD_NAMESIZE 32
+
+#if (RTE_EFD_VALUE_NUM_BITS > 0 && RTE_EFD_VALUE_NUM_BITS <= 8)
+typedef uint8_t efd_value_t;
+#elif (RTE_EFD_VALUE_NUM_BITS > 8 && RTE_EFD_VALUE_NUM_BITS <= 16)
+typedef uint16_t efd_value_t;
+#elif (RTE_EFD_VALUE_NUM_BITS > 16 && RTE_EFD_VALUE_NUM_BITS <= 32)
+typedef uint32_t efd_value_t;
+#else
+#error("RTE_EFD_VALUE_NUM_BITS must be in the range [1:32]")
+#endif
+
+#define EFD_LOOKUPTBL_SHIFT (32 - 4)
+typedef uint16_t efd_lookuptbl_t;
+typedef uint16_t efd_hashfunc_t;
+
+/**
+ * Creates an EFD table with a single offline region and multiple per-socket
+ * internally-managed copies of the online table used for lookups
+ *
+ * @param name
+ * EFD table name
+ * @param max_num_rules
+ * Minimum number of rules the table should be sized to hold.
+ * Will be rounded up to the next smallest valid table size
+ * @param key_len
+ * Length of the key
+ * @param online_cpu_socket_bitmask
+ * Bitmask specifying which sockets should get a copy of the online table.
+ * LSB = socket 0, etc.
+ * @param offline_cpu_socket
+ * Identifies the socket where the offline table will be allocated
+ * (and most efficiently accessed in the case of updates/insertions)
+ *
+ * @return
+ * EFD table, or NULL if table allocation failed or the bitmask is invalid
+ */
+struct rte_efd_table *
+rte_efd_create(const char *name, uint32_t max_num_rules, uint32_t key_len,
+ uint8_t online_cpu_socket_bitmask, uint8_t offline_cpu_socket);
+
+/**
+ * Releases the resources from an EFD table
+ *
+ * @param table
+ * Table to free
+ */
+void
+rte_efd_free(struct rte_efd_table *table);
+
+/**
+ * Find an existing EFD table object and return a pointer to it.
+ *
+ * @param name
+ * Name of the EFD table as passed to rte_efd_create()
+ * @return
+ * Pointer to EFD table or NULL if object not found
+ * with rte_errno set appropriately. Possible rte_errno values include:
+ * - ENOENT - value not available for return
+ */
+struct rte_efd_table*
+rte_efd_find_existing(const char *name);
+
+#define RTE_EFD_UPDATE_WARN_GROUP_FULL (1)
+#define RTE_EFD_UPDATE_NO_CHANGE (2)
+#define RTE_EFD_UPDATE_FAILED (3)
+
+/**
+ * Computes an updated table entry for the supplied key/value pair.
+ * The update is then immediately applied to the provided table and
+ * all socket-local copies of the chunks are updated.
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param key
+ * EFD table key to modify
+ * @param value
+ * Value to associate with the key
+ *
+ * @return
+ * RTE_EFD_UPDATE_WARN_GROUP_FULL
+ * Operation is insert, and the last available space in the
+ * key's group was just used
+ * Future inserts may fail as groups fill up
+ * This operation was still successful, and entry contains a valid update
+ * RTE_EFD_UPDATE_FAILED
+ * Either the EFD failed to find a suitable perfect hash or the group was full
+ * This is a fatal error, and the table is now in an indeterminite state
+ * RTE_EFD_UPDATE_NO_CHANGE
+ * Operation resulted in no change to the table (same value already exists)
+ * 0 - success
+ */
+int
+rte_efd_update(struct rte_efd_table *table, unsigned int socket_id,
+ const void *key, efd_value_t value);
+
+/**
+ * Removes any value currently associated with the specified key from the table
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param key
+ * EFD table key to delete
+ * @param prev_value
+ * If not NULL, will store the previous value here before deleting it
+ *
+ * @return
+ * 0 - successfully found and deleted the key
+ * nonzero otherwise
+ */
+int
+rte_efd_delete(struct rte_efd_table *table, unsigned int socket_id,
+ const void *key, efd_value_t *prev_value);
+
+/**
+ * Looks up the value associated with a key
+ *
+ * NOTE: Lookups will *always* succeed - this is a property of
+ * using a perfect hash table.
+ * If the specified key was never inserted, a pseudorandom answer will be returned.
+ * There is no way to know based on the lookup if the key was ever inserted
+ * originally, so this must be tracked elsewhere.
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param key
+ * EFD table key to look up
+ *
+ * @return
+ * Value associated with the key, or random junk if they key was never inserted
+ */
+efd_value_t
+rte_efd_lookup(const struct rte_efd_table *table, unsigned int socket_id,
+ const void *key);
+
+/**
+ * Looks up the value associated with several keys.
+ *
+ * NOTE: Lookups will *always* succeed - this is a property of
+ * using a perfect hash table.
+ * If the specified key was never inserted, a pseudorandom answer will be returned.
+ * There is no way to know based on the lookup if the key was ever inserted
+ * originally, so this must be tracked elsewhere.
+ *
+ * @param table
+ * EFD table to reference
+ * @param socket_id
+ * Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param num_keys
+ * Number of keys in the key_list array, must be less than RTE_EFD_BURST_MAX
+ * @param key_list
+ * Array of num_keys pointers which point to keys to look up
+ * @param value_list
+ * Array of size num_keys where lookup values will be stored
+ */
+void
+rte_efd_lookup_bulk(const struct rte_efd_table *table, unsigned int socket_id,
+ int num_keys, const void **key_list,
+ efd_value_t *value_list);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RTE_EFD_H_ */
diff --git a/lib/librte_efd/rte_efd_version.map b/lib/librte_efd/rte_efd_version.map
new file mode 100644
index 0000000..ae60a64
--- /dev/null
+++ b/lib/librte_efd/rte_efd_version.map
@@ -0,0 +1,13 @@
+DPDK_17.02 {
+ global:
+
+ rte_efd_create;
+ rte_efd_delete;
+ rte_efd_find_existing;
+ rte_efd_free;
+ rte_efd_lookup;
+ rte_efd_lookup_bulk;
+ rte_efd_update;
+
+ local: *;
+};
diff --git a/mk/rte.app.mk b/mk/rte.app.mk
index f75f0e2..ed1e68a 100644
--- a/mk/rte.app.mk
+++ b/mk/rte.app.mk
@@ -1,6 +1,6 @@
# BSD LICENSE
#
-# Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+# Copyright(c) 2010-2017 Intel Corporation. All rights reserved.
# Copyright(c) 2014-2015 6WIND S.A.
# All rights reserved.
#
@@ -86,6 +86,7 @@ _LDLIBS-y += --whole-archive
_LDLIBS-$(CONFIG_RTE_LIBRTE_TIMER) += -lrte_timer
_LDLIBS-$(CONFIG_RTE_LIBRTE_HASH) += -lrte_hash
+_LDLIBS-$(CONFIG_RTE_LIBRTE_EFD) += -lrte_efd
_LDLIBS-$(CONFIG_RTE_LIBRTE_VHOST) += -lrte_vhost
_LDLIBS-$(CONFIG_RTE_LIBRTE_KVARGS) += -lrte_kvargs
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v8 2/6] efd: add AVX2 vect lookup function
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 " Pablo de Lara
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 1/6] efd: new Elastic Flow Distributor library Pablo de Lara
@ 2017-01-17 22:23 ` Pablo de Lara
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 3/6] app/test: add EFD functional and perf tests Pablo de Lara
` (4 subsequent siblings)
6 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-17 22:23 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Byron Marohn, Saikrishna Edupuganti
Signed-off-by: Byron Marohn <byron.marohn@intel.com>
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
lib/librte_efd/rte_efd.c | 22 +++++++++++-
lib/librte_efd/rte_efd_x86.h | 86 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 107 insertions(+), 1 deletion(-)
create mode 100644 lib/librte_efd/rte_efd_x86.h
diff --git a/lib/librte_efd/rte_efd.c b/lib/librte_efd/rte_efd.c
index 2bcfd62..68e6dab 100644
--- a/lib/librte_efd/rte_efd.c
+++ b/lib/librte_efd/rte_efd.c
@@ -52,6 +52,9 @@
#include <rte_hash_crc.h>
#include "rte_efd.h"
+#if defined(RTE_ARCH_X86)
+#include "rte_efd_x86.h"
+#endif
#define EFD_KEY(key_idx, table) (table->keys + ((key_idx) * table->key_len))
/** Hash function used to determine chunk_id and bin_id for a group */
@@ -100,6 +103,7 @@ allocated memory
/* All different internal lookup functions */
enum efd_lookup_internal_function {
EFD_LOOKUP_SCALAR = 0,
+ EFD_LOOKUP_AVX2,
EFD_LOOKUP_NUM
};
@@ -662,7 +666,16 @@ rte_efd_create(const char *name, uint32_t max_num_rules, uint32_t key_len,
}
}
- table->lookup_fn = EFD_LOOKUP_SCALAR;
+#if defined(RTE_ARCH_X86)
+ /*
+ * For less than 4 bits, scalar function performs better
+ * than vectorised version
+ */
+ if (RTE_EFD_VALUE_NUM_BITS > 3 && rte_cpu_get_flag_enabled(RTE_CPUFLAG_AVX2))
+ table->lookup_fn = EFD_LOOKUP_AVX2;
+ else
+#endif
+ table->lookup_fn = EFD_LOOKUP_SCALAR;
/*
* Allocate the EFD table offline portion (with the actual rules
@@ -1253,6 +1266,13 @@ efd_lookup_internal(const struct efd_online_group_entry * const group,
switch (lookup_fn) {
+#if defined(RTE_ARCH_X86)
+ case EFD_LOOKUP_AVX2:
+ return efd_lookup_internal_avx2(group->hash_idx,
+ group->lookup_table,
+ hash_val_a,
+ hash_val_b);
+#endif
case EFD_LOOKUP_SCALAR:
/* Fall-through */
default:
diff --git a/lib/librte_efd/rte_efd_x86.h b/lib/librte_efd/rte_efd_x86.h
new file mode 100644
index 0000000..34f37d7
--- /dev/null
+++ b/lib/librte_efd/rte_efd_x86.h
@@ -0,0 +1,86 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* rte_efd_x86.h
+ * This file holds all x86 specific EFD functions
+ */
+#include <immintrin.h>
+
+#if (RTE_EFD_VALUE_NUM_BITS == 8 || RTE_EFD_VALUE_NUM_BITS == 16 || \
+ RTE_EFD_VALUE_NUM_BITS == 24 || RTE_EFD_VALUE_NUM_BITS == 32)
+#define EFD_LOAD_SI128(val) _mm_load_si128(val)
+#else
+#define EFD_LOAD_SI128(val) _mm_lddqu_si128(val)
+#endif
+
+static inline efd_value_t
+efd_lookup_internal_avx2(const efd_hashfunc_t *group_hash_idx,
+ const efd_lookuptbl_t *group_lookup_table,
+ const uint32_t hash_val_a, const uint32_t hash_val_b)
+{
+#ifdef RTE_MACHINE_CPUFLAG_AVX2
+ efd_value_t value = 0;
+ uint32_t i = 0;
+ __m256i vhash_val_a = _mm256_set1_epi32(hash_val_a);
+ __m256i vhash_val_b = _mm256_set1_epi32(hash_val_b);
+
+ for (; i < RTE_EFD_VALUE_NUM_BITS; i += 8) {
+ __m256i vhash_idx =
+ _mm256_cvtepu16_epi32(EFD_LOAD_SI128(
+ (__m128i const *) &group_hash_idx[i]));
+ __m256i vlookup_table = _mm256_cvtepu16_epi32(
+ EFD_LOAD_SI128((__m128i const *)
+ &group_lookup_table[i]));
+ __m256i vhash = _mm256_add_epi32(vhash_val_a,
+ _mm256_mullo_epi32(vhash_idx, vhash_val_b));
+ __m256i vbucket_idx = _mm256_srli_epi32(vhash,
+ EFD_LOOKUPTBL_SHIFT);
+ __m256i vresult = _mm256_srlv_epi32(vlookup_table,
+ vbucket_idx);
+
+ value |= (_mm256_movemask_ps(
+ (__m256) _mm256_slli_epi32(vresult, 31))
+ & ((1 << (RTE_EFD_VALUE_NUM_BITS - i)) - 1)) << i;
+ }
+
+ return value;
+#else
+ RTE_SET_USED(group_hash_idx);
+ RTE_SET_USED(group_lookup_table);
+ RTE_SET_USED(hash_val_a);
+ RTE_SET_USED(hash_val_b);
+ /* Return dummy value, only to avoid compilation breakage */
+ return 0;
+#endif
+
+}
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v8 3/6] app/test: add EFD functional and perf tests
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 " Pablo de Lara
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 1/6] efd: new Elastic Flow Distributor library Pablo de Lara
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 2/6] efd: add AVX2 vect lookup function Pablo de Lara
@ 2017-01-17 22:23 ` Pablo de Lara
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 4/6] examples/flow_distributor: sample app to demonstrate EFD usage Pablo de Lara
` (3 subsequent siblings)
6 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-17 22:23 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Byron Marohn, Karla Saur, Saikrishna Edupuganti
Signed-off-by: Byron Marohn <byron.marohn@intel.com>
Signed-off-by: Karla Saur <karla.saur@intel.com>
Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 1 +
app/test/Makefile | 5 +-
app/test/test_efd.c | 494 +++++++++++++++++++++++++++++++++++++++++++++++
app/test/test_efd_perf.c | 407 ++++++++++++++++++++++++++++++++++++++
4 files changed, 906 insertions(+), 1 deletion(-)
create mode 100644 app/test/test_efd.c
create mode 100644 app/test/test_efd_perf.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 9c60d67..d812962 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -532,6 +532,7 @@ EFD
M: Byron Marohn <byron.marohn@intel.com>
M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
F: lib/librte_efd/
+F: app/test/test_efd*
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
diff --git a/app/test/Makefile b/app/test/Makefile
index 5be023a..9de301f 100644
--- a/app/test/Makefile
+++ b/app/test/Makefile
@@ -1,6 +1,6 @@
# BSD LICENSE
#
-# Copyright(c) 2010-2015 Intel Corporation. All rights reserved.
+# Copyright(c) 2010-2017 Intel Corporation. All rights reserved.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -123,6 +123,9 @@ SRCS-y += test_logs.c
SRCS-y += test_memcpy.c
SRCS-y += test_memcpy_perf.c
+SRCS-$(CONFIG_RTE_LIBRTE_EFD) += test_efd.c
+SRCS-$(CONFIG_RTE_LIBRTE_EFD) += test_efd_perf.c
+
SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_hash.c
SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_thash.c
SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_hash_perf.c
diff --git a/app/test/test_efd.c b/app/test/test_efd.c
new file mode 100644
index 0000000..d5c3bd9
--- /dev/null
+++ b/app/test/test_efd.c
@@ -0,0 +1,494 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rte_memcpy.h>
+#include <rte_malloc.h>
+#include <rte_efd.h>
+#include <rte_byteorder.h>
+#include <rte_random.h>
+#include <rte_debug.h>
+#include <rte_ip.h>
+
+#include "test.h"
+
+#define EFD_TEST_KEY_LEN 8
+#define TABLE_SIZE (1 << 21)
+#define ITERATIONS 3
+static unsigned int test_socket_id;
+
+/* 5-tuple key type */
+struct flow_key {
+ uint32_t ip_src;
+ uint32_t ip_dst;
+ uint16_t port_src;
+ uint16_t port_dst;
+ uint8_t proto;
+} __attribute__((packed));
+/*
+ * Print out result of unit test efd operation.
+ */
+#if defined(UNIT_TEST_EFD_VERBOSE)
+
+static void print_key_info(const char *msg, const struct flow_key *key,
+ efd_value_t val)
+{
+ const uint8_t *p = (const uint8_t *) key;
+ unsigned int i;
+
+ printf("%s key:0x", msg);
+ for (i = 0; i < sizeof(struct flow_key); i++)
+ printf("%02X", p[i]);
+
+ printf(" @ val %d\n", val);
+}
+#else
+
+static void print_key_info(__attribute__((unused)) const char *msg,
+ __attribute__((unused)) const struct flow_key *key,
+ __attribute__((unused)) efd_value_t val)
+{
+}
+#endif
+
+/* Keys used by unit test functions */
+static struct flow_key keys[5] = {
+ {
+ .ip_src = IPv4(0x03, 0x02, 0x01, 0x00),
+ .ip_dst = IPv4(0x07, 0x06, 0x05, 0x04),
+ .port_src = 0x0908,
+ .port_dst = 0x0b0a,
+ .proto = 0x0c,
+ },
+ {
+ .ip_src = IPv4(0x13, 0x12, 0x11, 0x10),
+ .ip_dst = IPv4(0x17, 0x16, 0x15, 0x14),
+ .port_src = 0x1918,
+ .port_dst = 0x1b1a,
+ .proto = 0x1c,
+ },
+ {
+ .ip_src = IPv4(0x23, 0x22, 0x21, 0x20),
+ .ip_dst = IPv4(0x27, 0x26, 0x25, 0x24),
+ .port_src = 0x2928,
+ .port_dst = 0x2b2a,
+ .proto = 0x2c,
+ },
+ {
+ .ip_src = IPv4(0x33, 0x32, 0x31, 0x30),
+ .ip_dst = IPv4(0x37, 0x36, 0x35, 0x34),
+ .port_src = 0x3938,
+ .port_dst = 0x3b3a,
+ .proto = 0x3c,
+ },
+ {
+ .ip_src = IPv4(0x43, 0x42, 0x41, 0x40),
+ .ip_dst = IPv4(0x47, 0x46, 0x45, 0x44),
+ .port_src = 0x4948,
+ .port_dst = 0x4b4a,
+ .proto = 0x4c,
+ }
+};
+/* Array to store the data */
+efd_value_t data[5];
+
+static inline uint8_t efd_get_all_sockets_bitmask(void)
+{
+ uint8_t all_cpu_sockets_bitmask = 0;
+ unsigned int i;
+ unsigned int next_lcore = rte_get_master_lcore();
+ const int val_true = 1, val_false = 0;
+ for (i = 0; i < rte_lcore_count(); i++) {
+ all_cpu_sockets_bitmask |= 1 << rte_lcore_to_socket_id(next_lcore);
+ next_lcore = rte_get_next_lcore(next_lcore, val_false, val_true);
+ }
+
+ return all_cpu_sockets_bitmask;
+}
+
+/*
+ * Basic sequence of operations for a single key:
+ * - add
+ * - lookup (hit)
+ * - delete
+ * Note: lookup (miss) is not applicable since this is a filter
+ */
+static int test_add_delete(void)
+{
+ struct rte_efd_table *handle;
+ /* test with standard add/lookup/delete functions */
+ efd_value_t prev_value;
+ printf("Entering %s\n", __func__);
+
+ handle = rte_efd_create("test_add_delete",
+ TABLE_SIZE, sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the EFD table\n");
+
+ data[0] = mrand48() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id, &keys[0],
+ data[0]),
+ "Error inserting the key");
+ print_key_info("Add", &keys[0], data[0]);
+
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id, &keys[0]),
+ data[0],
+ "failed to find key");
+
+ TEST_ASSERT_SUCCESS(rte_efd_delete(handle, test_socket_id, &keys[0],
+ &prev_value),
+ "failed to delete key");
+ TEST_ASSERT_EQUAL(prev_value, data[0],
+ "failed to delete the expected value, got %d, "
+ "expected %d", prev_value, data[0]);
+ print_key_info("Del", &keys[0], data[0]);
+
+ rte_efd_free(handle);
+
+ return 0;
+}
+
+/*
+ * Sequence of operations for a single key:
+ * - add
+ * - lookup: hit
+ * - add: update
+ * - lookup: hit (updated data)
+ * - delete: hit
+ */
+static int test_add_update_delete(void)
+{
+ struct rte_efd_table *handle;
+ printf("Entering %s\n", __func__);
+ /* test with standard add/lookup/delete functions */
+ efd_value_t prev_value;
+ data[1] = mrand48() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+
+ handle = rte_efd_create("test_add_update_delete", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the efd table\n");
+
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id, &keys[1],
+ data[1]), "Error inserting the key");
+ print_key_info("Add", &keys[1], data[1]);
+
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id, &keys[1]),
+ data[1], "failed to find key");
+ print_key_info("Lkp", &keys[1], data[1]);
+
+ data[1] = data[1] + 1;
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id, &keys[1],
+ data[1]), "Error re-inserting the key");
+ print_key_info("Add", &keys[1], data[1]);
+
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id, &keys[1]),
+ data[1], "failed to find key");
+ print_key_info("Lkp", &keys[1], data[1]);
+
+ TEST_ASSERT_SUCCESS(rte_efd_delete(handle, test_socket_id, &keys[1],
+ &prev_value), "failed to delete key");
+ TEST_ASSERT_EQUAL(prev_value, data[1],
+ "failed to delete the expected value, got %d, "
+ "expected %d", prev_value, data[1]);
+ print_key_info("Del", &keys[1], data[1]);
+
+
+ rte_efd_free(handle);
+ return 0;
+}
+
+/*
+ * Sequence of operations for find existing EFD table
+ *
+ * - create table
+ * - find existing table: hit
+ * - find non-existing table: miss
+ *
+ */
+static int test_efd_find_existing(void)
+{
+ struct rte_efd_table *handle = NULL, *result = NULL;
+
+ printf("Entering %s\n", __func__);
+
+ /* Create EFD table. */
+ handle = rte_efd_create("efd_find_existing", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the efd table\n");
+
+ /* Try to find existing EFD table */
+ result = rte_efd_find_existing("efd_find_existing");
+ TEST_ASSERT_EQUAL(result, handle, "could not find existing efd table");
+
+ /* Try to find non-existing EFD table */
+ result = rte_efd_find_existing("efd_find_non_existing");
+ TEST_ASSERT_NULL(result, "found table that shouldn't exist");
+
+ /* Cleanup. */
+ rte_efd_free(handle);
+
+ return 0;
+}
+
+/*
+ * Sequence of operations for 5 keys
+ * - add keys
+ * - lookup keys: hit (bulk)
+ * - add keys (update)
+ * - lookup keys: hit (updated data)
+ * - delete keys : hit
+ */
+static int test_five_keys(void)
+{
+ struct rte_efd_table *handle;
+ const void *key_array[5] = {0};
+ efd_value_t result[5] = {0};
+ efd_value_t prev_value;
+ unsigned int i;
+ printf("Entering %s\n", __func__);
+
+ handle = rte_efd_create("test_five_keys", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(handle, "Error creating the efd table\n");
+
+ /* Setup data */
+ for (i = 0; i < 5; i++)
+ data[i] = mrand48() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+
+ /* Add */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id,
+ &keys[i], data[i]),
+ "Error inserting the key");
+ print_key_info("Add", &keys[i], data[i]);
+ }
+
+ /* Lookup */
+ for (i = 0; i < 5; i++)
+ key_array[i] = &keys[i];
+
+ rte_efd_lookup_bulk(handle, test_socket_id, 5,
+ (const void **) (void *) &key_array, result);
+
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_EQUAL(result[i], data[i],
+ "bulk: failed to find key. Expected %d, got %d",
+ data[i], result[i]);
+ print_key_info("Lkp", &keys[i], data[i]);
+ }
+
+ /* Modify data (bulk) */
+ for (i = 0; i < 5; i++)
+ data[i] = data[i] + 1;
+
+ /* Add - update */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_SUCCESS(rte_efd_update(handle, test_socket_id,
+ &keys[i], data[i]),
+ "Error inserting the key");
+ print_key_info("Add", &keys[i], data[i]);
+ }
+
+ /* Lookup */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_EQUAL(rte_efd_lookup(handle, test_socket_id,
+ &keys[i]), data[i],
+ "failed to find key");
+ print_key_info("Lkp", &keys[i], data[i]);
+ }
+
+ /* Delete */
+ for (i = 0; i < 5; i++) {
+ TEST_ASSERT_SUCCESS(rte_efd_delete(handle, test_socket_id,
+ &keys[i], &prev_value),
+ "failed to delete key");
+ TEST_ASSERT_EQUAL(prev_value, data[i],
+ "failed to delete the expected value, got %d, "
+ "expected %d", prev_value, data[i]);
+ print_key_info("Del", &keys[i], data[i]);
+ }
+
+
+ rte_efd_free(handle);
+
+ return 0;
+}
+
+/*
+ * Test to see the average table utilization (entries added/max entries)
+ * before hitting a random entry that cannot be added
+ */
+static int test_average_table_utilization(void)
+{
+ struct rte_efd_table *handle = NULL;
+ uint32_t num_rules_in = TABLE_SIZE;
+ uint8_t simple_key[EFD_TEST_KEY_LEN];
+ unsigned int i, j;
+ unsigned int added_keys, average_keys_added = 0;
+
+ printf("Evaluating table utilization and correctness, please wait\n");
+ fflush(stdout);
+
+ for (j = 0; j < ITERATIONS; j++) {
+ handle = rte_efd_create("test_efd", num_rules_in,
+ EFD_TEST_KEY_LEN, efd_get_all_sockets_bitmask(),
+ test_socket_id);
+ if (handle == NULL) {
+ printf("efd table creation failed\n");
+ return -1;
+ }
+
+ unsigned int succeeded = 0;
+ unsigned int lost_keys = 0;
+
+ /* Add random entries until key cannot be added */
+ for (added_keys = 0; added_keys < num_rules_in; added_keys++) {
+
+ for (i = 0; i < EFD_TEST_KEY_LEN; i++)
+ simple_key[i] = rte_rand() & 0xFF;
+
+ efd_value_t val = simple_key[0];
+
+ if (rte_efd_update(handle, test_socket_id, simple_key,
+ val))
+ break; /* continue;*/
+ if (rte_efd_lookup(handle, test_socket_id, simple_key)
+ != val)
+ lost_keys++;
+ else
+ succeeded++;
+ }
+
+ average_keys_added += succeeded;
+
+ /* Reset the table */
+ rte_efd_free(handle);
+
+ /* Print progress on operations */
+ printf("Added %10u Succeeded %10u Lost %10u\n",
+ added_keys, succeeded, lost_keys);
+ fflush(stdout);
+ }
+
+ average_keys_added /= ITERATIONS;
+
+ printf("\nAverage table utilization = %.2f%% (%u/%u)\n",
+ ((double) average_keys_added / num_rules_in * 100),
+ average_keys_added, num_rules_in);
+
+ return 0;
+}
+
+/*
+ * Do tests for EFD creation with bad parameters.
+ */
+static int test_efd_creation_with_bad_parameters(void)
+{
+ struct rte_efd_table *handle, *tmp;
+ printf("Entering %s, **Errors are expected **\n", __func__);
+
+ handle = rte_efd_create("creation_with_bad_parameters_0", TABLE_SIZE, 0,
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ if (handle != NULL) {
+ rte_efd_free(handle);
+ printf("Impossible creating EFD table successfully "
+ "if key_len in parameter is zero\n");
+ return -1;
+ }
+
+ handle = rte_efd_create("creation_with_bad_parameters_1", TABLE_SIZE,
+ sizeof(struct flow_key), 0, test_socket_id);
+ if (handle != NULL) {
+ rte_efd_free(handle);
+ printf("Impossible creating EFD table successfully "
+ "with invalid socket bitmask\n");
+ return -1;
+ }
+
+ handle = rte_efd_create("creation_with_bad_parameters_2", TABLE_SIZE,
+ sizeof(struct flow_key), efd_get_all_sockets_bitmask(),
+ 255);
+ if (handle != NULL) {
+ rte_efd_free(handle);
+ printf("Impossible creating EFD table successfully "
+ "with invalid socket\n");
+ return -1;
+ }
+
+ /* test with same name should fail */
+ handle = rte_efd_create("same_name", TABLE_SIZE,
+ sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), 0);
+ if (handle == NULL) {
+ printf("Cannot create first EFD table with 'same_name'\n");
+ return -1;
+ }
+ tmp = rte_efd_create("same_name", TABLE_SIZE, sizeof(struct flow_key),
+ efd_get_all_sockets_bitmask(), 0);
+ if (tmp != NULL) {
+ printf("Creation of EFD table with same name should fail\n");
+ rte_efd_free(handle);
+ rte_efd_free(tmp);
+ return -1;
+ }
+ rte_efd_free(handle);
+
+ printf("# Test successful. No more errors expected\n");
+
+ return 0;
+}
+
+static int
+test_efd(void)
+{
+
+ /* Unit tests */
+ if (test_add_delete() < 0)
+ return -1;
+ if (test_efd_find_existing() < 0)
+ return -1;
+ if (test_add_update_delete() < 0)
+ return -1;
+ if (test_five_keys() < 0)
+ return -1;
+ if (test_efd_creation_with_bad_parameters() < 0)
+ return -1;
+ if (test_average_table_utilization() < 0)
+ return -1;
+
+ return 0;
+}
+
+REGISTER_TEST_COMMAND(efd_autotest, test_efd);
diff --git a/app/test/test_efd_perf.c b/app/test/test_efd_perf.c
new file mode 100644
index 0000000..998a25b
--- /dev/null
+++ b/app/test/test_efd_perf.c
@@ -0,0 +1,407 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <inttypes.h>
+
+#include <rte_lcore.h>
+#include <rte_cycles.h>
+#include <rte_malloc.h>
+#include <rte_random.h>
+#include <rte_efd.h>
+#include <rte_memcpy.h>
+#include <rte_thash.h>
+
+#include "test.h"
+#define NUM_KEYSIZES 10
+#define NUM_SHUFFLES 10
+#define MAX_KEYSIZE 64
+#define MAX_ENTRIES (1 << 19)
+#define KEYS_TO_ADD (MAX_ENTRIES * 3 / 4) /* 75% table utilization */
+#define NUM_LOOKUPS (KEYS_TO_ADD * 5) /* Loop among keys added, several times */
+static unsigned int test_socket_id;
+
+static inline uint8_t efd_get_all_sockets_bitmask(void)
+{
+ uint8_t all_cpu_sockets_bitmask = 0;
+ unsigned int i;
+ unsigned int next_lcore = rte_get_master_lcore();
+ const int val_true = 1, val_false = 0;
+ for (i = 0; i < rte_lcore_count(); i++) {
+ all_cpu_sockets_bitmask |= 1 << rte_lcore_to_socket_id(next_lcore);
+ next_lcore = rte_get_next_lcore(next_lcore, val_false, val_true);
+ }
+
+ return all_cpu_sockets_bitmask;
+}
+
+enum operations {
+ ADD = 0,
+ LOOKUP,
+ LOOKUP_MULTI,
+ DELETE,
+ NUM_OPERATIONS
+};
+
+struct efd_perf_params {
+ struct rte_efd_table *efd_table;
+ uint32_t key_size;
+ unsigned int cycle;
+};
+
+static uint32_t hashtest_key_lens[] = {
+ /* standard key sizes */
+ 4, 8, 16, 32, 48, 64,
+ /* IPv4 SRC + DST + protocol, unpadded */
+ 9,
+ /* IPv4 5-tuple, unpadded */
+ 13,
+ /* IPv6 5-tuple, unpadded */
+ 37,
+ /* IPv6 5-tuple, padded to 8-byte boundary */
+ 40
+};
+
+/* Array to store number of cycles per operation */
+uint64_t cycles[NUM_KEYSIZES][NUM_OPERATIONS];
+
+/* Array to store the data */
+efd_value_t data[KEYS_TO_ADD];
+
+/* Array to store all input keys */
+uint8_t keys[KEYS_TO_ADD][MAX_KEYSIZE];
+
+/* Shuffle the keys that have been added, so lookups will be totally random */
+static void
+shuffle_input_keys(struct efd_perf_params *params)
+{
+ efd_value_t temp_data;
+ unsigned int i;
+ uint32_t swap_idx;
+ uint8_t temp_key[MAX_KEYSIZE];
+
+ for (i = KEYS_TO_ADD - 1; i > 0; i--) {
+ swap_idx = rte_rand() % i;
+
+ memcpy(temp_key, keys[i], hashtest_key_lens[params->cycle]);
+ temp_data = data[i];
+
+ memcpy(keys[i], keys[swap_idx], hashtest_key_lens[params->cycle]);
+ data[i] = data[swap_idx];
+
+ memcpy(keys[swap_idx], temp_key, hashtest_key_lens[params->cycle]);
+ data[swap_idx] = temp_data;
+ }
+}
+
+static int key_compare(const void *key1, const void *key2)
+{
+ return memcmp(key1, key2, MAX_KEYSIZE);
+}
+
+/*
+ * TODO: we could "error proof" these as done in test_hash_perf.c ln 165:
+ *
+ * The current setup may give errors if too full in some cases which we check
+ * for. However, since EFD allows for ~99% capacity, these errors are rare for
+ * #"KEYS_TO_ADD" which is 75% capacity.
+ */
+static int
+setup_keys_and_data(struct efd_perf_params *params, unsigned int cycle)
+{
+ unsigned int i, j;
+ int num_duplicates;
+
+ params->key_size = hashtest_key_lens[cycle];
+ params->cycle = cycle;
+
+ /* Reset all arrays */
+ for (i = 0; i < params->key_size; i++)
+ keys[0][i] = 0;
+
+ /* Generate a list of keys, some of which may be duplicates */
+ for (i = 0; i < KEYS_TO_ADD; i++) {
+ for (j = 0; j < params->key_size; j++)
+ keys[i][j] = rte_rand() & 0xFF;
+
+ data[i] = rte_rand() & ((1 << RTE_EFD_VALUE_NUM_BITS) - 1);
+ }
+
+ /* Remove duplicates from the keys array */
+ do {
+ num_duplicates = 0;
+
+ /* Sort the list of keys to make it easier to find duplicates */
+ qsort(keys, KEYS_TO_ADD, MAX_KEYSIZE, key_compare);
+
+ /* Sift through the list of keys and look for duplicates */
+ int num_duplicates = 0;
+ for (i = 0; i < KEYS_TO_ADD - 1; i++) {
+ if (memcmp(keys[i], keys[i + 1], params->key_size) == 0) {
+ /* This key already exists, try again */
+ num_duplicates++;
+ for (j = 0; j < params->key_size; j++)
+ keys[i][j] = rte_rand() & 0xFF;
+ }
+ }
+ } while (num_duplicates != 0);
+
+ /* Shuffle the random values again */
+ shuffle_input_keys(params);
+
+ params->efd_table = rte_efd_create("test_efd_perf",
+ MAX_ENTRIES, params->key_size,
+ efd_get_all_sockets_bitmask(), test_socket_id);
+ TEST_ASSERT_NOT_NULL(params->efd_table, "Error creating the efd table\n");
+
+ return 0;
+}
+
+static int
+timed_adds(struct efd_perf_params *params)
+{
+ const uint64_t start_tsc = rte_rdtsc();
+ unsigned int i, a;
+ int32_t ret;
+
+ for (i = 0; i < KEYS_TO_ADD; i++) {
+ ret = rte_efd_update(params->efd_table, test_socket_id, keys[i],
+ data[i]);
+ if (ret != 0) {
+ printf("Error %d in rte_efd_update - key=0x", ret);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x", keys[i][a]);
+ printf(" value=%d\n", data[i]);
+
+ return -1;
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][ADD] = time_taken / KEYS_TO_ADD;
+ return 0;
+}
+
+static int
+timed_lookups(struct efd_perf_params *params)
+{
+ unsigned int i, j, a;
+ const uint64_t start_tsc = rte_rdtsc();
+ efd_value_t ret_data;
+
+ for (i = 0; i < NUM_LOOKUPS / KEYS_TO_ADD; i++) {
+ for (j = 0; j < KEYS_TO_ADD; j++) {
+ ret_data = rte_efd_lookup(params->efd_table,
+ test_socket_id, keys[j]);
+ if (ret_data != data[j]) {
+ printf("Value mismatch using rte_efd_lookup: "
+ "key #%d (0x", i);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x", keys[i][a]);
+ printf(")\n");
+ printf(" Expected %d, got %d\n", data[i],
+ ret_data);
+
+ return -1;
+ }
+
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][LOOKUP] = time_taken / NUM_LOOKUPS;
+
+ return 0;
+}
+
+static int
+timed_lookups_multi(struct efd_perf_params *params)
+{
+ unsigned int i, j, k, a;
+ efd_value_t result[RTE_EFD_BURST_MAX] = {0};
+ const void *keys_burst[RTE_EFD_BURST_MAX];
+ const uint64_t start_tsc = rte_rdtsc();
+
+ for (i = 0; i < NUM_LOOKUPS / KEYS_TO_ADD; i++) {
+ for (j = 0; j < KEYS_TO_ADD / RTE_EFD_BURST_MAX; j++) {
+ for (k = 0; k < RTE_EFD_BURST_MAX; k++)
+ keys_burst[k] = keys[j * RTE_EFD_BURST_MAX + k];
+
+ rte_efd_lookup_bulk(params->efd_table, test_socket_id,
+ RTE_EFD_BURST_MAX,
+ keys_burst, result);
+
+ for (k = 0; k < RTE_EFD_BURST_MAX; k++) {
+ uint32_t data_idx = j * RTE_EFD_BURST_MAX + k;
+ if (result[k] != data[data_idx]) {
+ printf("Value mismatch using "
+ "rte_efd_lookup_bulk: key #%d "
+ "(0x", i);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x",
+ keys[data_idx][a]);
+ printf(")\n");
+ printf(" Expected %d, got %d\n",
+ data[data_idx], result[k]);
+
+ return -1;
+ }
+ }
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][LOOKUP_MULTI] = time_taken / NUM_LOOKUPS;
+
+ return 0;
+}
+
+static int
+timed_deletes(struct efd_perf_params *params)
+{
+ unsigned int i, a;
+ const uint64_t start_tsc = rte_rdtsc();
+ int32_t ret;
+
+ for (i = 0; i < KEYS_TO_ADD; i++) {
+ ret = rte_efd_delete(params->efd_table, test_socket_id, keys[i],
+ NULL);
+
+ if (ret != 0) {
+ printf("Error %d in rte_efd_delete - key=0x", ret);
+ for (a = 0; a < params->key_size; a++)
+ printf("%02x", keys[i][a]);
+ printf("\n");
+
+ return -1;
+ }
+ }
+
+ const uint64_t end_tsc = rte_rdtsc();
+ const uint64_t time_taken = end_tsc - start_tsc;
+
+ cycles[params->cycle][DELETE] = time_taken / KEYS_TO_ADD;
+
+ return 0;
+}
+
+static void
+perform_frees(struct efd_perf_params *params)
+{
+ if (params->efd_table != NULL) {
+ rte_efd_free(params->efd_table);
+ params->efd_table = NULL;
+ }
+}
+
+static int
+exit_with_fail(const char *testname, struct efd_perf_params *params,
+ unsigned int i)
+{
+
+ printf("<<<<<Test %s failed at keysize %d iteration %d >>>>>\n",
+ testname, hashtest_key_lens[params->cycle], i);
+ perform_frees(params);
+ return -1;
+}
+
+static int
+run_all_tbl_perf_tests(void)
+{
+ unsigned int i, j;
+ struct efd_perf_params params;
+
+ printf("Measuring performance, please wait\n");
+ fflush(stdout);
+
+ test_socket_id = rte_socket_id();
+
+ for (i = 0; i < NUM_KEYSIZES; i++) {
+
+ if (setup_keys_and_data(¶ms, i) < 0) {
+ printf("Could not create keys/data/table\n");
+ return -1;
+ }
+
+ if (timed_adds(¶ms) < 0)
+ return exit_with_fail("timed_adds", ¶ms, i);
+
+ for (j = 0; j < NUM_SHUFFLES; j++)
+ shuffle_input_keys(¶ms);
+
+ if (timed_lookups(¶ms) < 0)
+ return exit_with_fail("timed_lookups", ¶ms, i);
+
+ if (timed_lookups_multi(¶ms) < 0)
+ return exit_with_fail("timed_lookups_multi", ¶ms, i);
+
+ if (timed_deletes(¶ms) < 0)
+ return exit_with_fail("timed_deletes", ¶ms, i);
+
+ /* Print a dot to show progress on operations */
+ printf(".");
+ fflush(stdout);
+
+ perform_frees(¶ms);
+ }
+
+ printf("\nResults (in CPU cycles/operation)\n");
+ printf("-----------------------------------\n");
+ printf("\n%-18s%-18s%-18s%-18s%-18s\n",
+ "Keysize", "Add", "Lookup", "Lookup_bulk", "Delete");
+ for (i = 0; i < NUM_KEYSIZES; i++) {
+ printf("%-18d", hashtest_key_lens[i]);
+ for (j = 0; j < NUM_OPERATIONS; j++)
+ printf("%-18"PRIu64, cycles[i][j]);
+ printf("\n");
+ }
+ return 0;
+}
+
+static int
+test_efd_perf(void)
+{
+
+ if (run_all_tbl_perf_tests() < 0)
+ return -1;
+
+ return 0;
+}
+
+REGISTER_TEST_COMMAND(efd_perf_autotest, test_efd_perf);
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v8 4/6] examples/flow_distributor: sample app to demonstrate EFD usage
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 " Pablo de Lara
` (2 preceding siblings ...)
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 3/6] app/test: add EFD functional and perf tests Pablo de Lara
@ 2017-01-17 22:23 ` Pablo de Lara
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 5/6] doc: add EFD library section in Programmers guide Pablo de Lara
` (2 subsequent siblings)
6 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-17 22:23 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Saikrishna Edupuganti
This new sample app, based on the client/server sample app,
shows the user an scenario using the EFD library.
It consists of:
- A front-end server which has an EFD table that stores the
node id for each flow key, which will distribute the incoming
packets to the different nodes
- A back-end node, which has a hash table where node checks,
after reading packets coming from the server, whether the packet
is meant to be used in such node, in which case it will be TXed,
or not, in which case, packet will be dropped.
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 1 +
doc/api/examples.dox | 4 +
examples/Makefile | 1 +
examples/flow_distributor/Makefile | 44 +++
examples/flow_distributor/distributor/Makefile | 57 ++++
examples/flow_distributor/distributor/args.c | 200 ++++++++++++
examples/flow_distributor/distributor/args.h | 39 +++
examples/flow_distributor/distributor/init.c | 371 ++++++++++++++++++++++
examples/flow_distributor/distributor/init.h | 76 +++++
examples/flow_distributor/distributor/main.c | 362 +++++++++++++++++++++
examples/flow_distributor/node/Makefile | 48 +++
examples/flow_distributor/node/node.c | 417 +++++++++++++++++++++++++
examples/flow_distributor/shared/common.h | 99 ++++++
13 files changed, 1719 insertions(+)
create mode 100644 examples/flow_distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/Makefile
create mode 100644 examples/flow_distributor/distributor/args.c
create mode 100644 examples/flow_distributor/distributor/args.h
create mode 100644 examples/flow_distributor/distributor/init.c
create mode 100644 examples/flow_distributor/distributor/init.h
create mode 100644 examples/flow_distributor/distributor/main.c
create mode 100644 examples/flow_distributor/node/Makefile
create mode 100644 examples/flow_distributor/node/node.c
create mode 100644 examples/flow_distributor/shared/common.h
diff --git a/MAINTAINERS b/MAINTAINERS
index d812962..b124f6e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -533,6 +533,7 @@ M: Byron Marohn <byron.marohn@intel.com>
M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
F: lib/librte_efd/
F: app/test/test_efd*
+F: examples/flow_distributor/
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
diff --git a/doc/api/examples.dox b/doc/api/examples.dox
index 1626852..c13e574 100644
--- a/doc/api/examples.dox
+++ b/doc/api/examples.dox
@@ -52,6 +52,10 @@
@example load_balancer/init.c
@example load_balancer/main.c
@example load_balancer/runtime.c
+@example flow_distributor/distributor/args.c
+@example flow_distributor/distributor/init.c
+@example flow_distributor/distributor/main.c
+@example flow_distributor/node/node.c
@example multi_process/client_server_mp/mp_client/client.c
@example multi_process/client_server_mp/mp_server/args.c
@example multi_process/client_server_mp/mp_server/init.c
diff --git a/examples/Makefile b/examples/Makefile
index d49c7f2..b404982 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -45,6 +45,7 @@ DIRS-y += dpdk_qat
endif
DIRS-y += ethtool
DIRS-y += exception_path
+DIRS-$(CONFIG_RTE_LIBRTE_EFD) += flow_distributor
DIRS-y += helloworld
DIRS-$(CONFIG_RTE_LIBRTE_PIPELINE) += ip_pipeline
ifeq ($(CONFIG_RTE_LIBRTE_LPM),y)
diff --git a/examples/flow_distributor/Makefile b/examples/flow_distributor/Makefile
new file mode 100644
index 0000000..5bae706
--- /dev/null
+++ b/examples/flow_distributor/Makefile
@@ -0,0 +1,44 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overridden by command line or environment
+RTE_TARGET ?= x86_64-native-linuxapp-gcc
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+DIRS-$(CONFIG_RTE_EXEC_ENV_LINUXAPP) += distributor
+DIRS-$(CONFIG_RTE_EXEC_ENV_LINUXAPP) += node
+
+include $(RTE_SDK)/mk/rte.extsubdir.mk
diff --git a/examples/flow_distributor/distributor/Makefile b/examples/flow_distributor/distributor/Makefile
new file mode 100644
index 0000000..8714151
--- /dev/null
+++ b/examples/flow_distributor/distributor/Makefile
@@ -0,0 +1,57 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overridden by command line or environment
+RTE_TARGET ?= x86_64-native-linuxapp-gcc
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+ifneq ($(CONFIG_RTE_EXEC_ENV), "linuxapp")
+$(error This application can only operate in a linuxapp environment, \
+please change the definition of the RTE_TARGET environment variable)
+endif
+
+# binary name
+APP = distributor
+
+# all source are stored in SRCS-y
+SRCS-y := main.c init.c args.c
+
+INC := $(wildcard *.h)
+
+CFLAGS += $(WERROR_FLAGS) -O3
+CFLAGS += -I$(SRCDIR)/../shared
+
+include $(RTE_SDK)/mk/rte.extapp.mk
diff --git a/examples/flow_distributor/distributor/args.c b/examples/flow_distributor/distributor/args.c
new file mode 100644
index 0000000..ee29203
--- /dev/null
+++ b/examples/flow_distributor/distributor/args.c
@@ -0,0 +1,200 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <getopt.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include <rte_memory.h>
+#include <rte_string_fns.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+/* 1M flows by default */
+#define DEFAULT_NUM_FLOWS 0x100000
+
+/* global var for number of nodes - extern in header */
+uint8_t num_nodes;
+/* global var for number of flows - extern in header */
+uint32_t num_flows = DEFAULT_NUM_FLOWS;
+
+static const char *progname;
+
+/**
+ * Prints out usage information to stdout
+ */
+static void
+usage(void)
+{
+ printf("%s [EAL options] -- -p PORTMASK -n NUM_NODES -f NUM_FLOWS\n"
+ " -p PORTMASK: hexadecimal bitmask of ports to use\n"
+ " -n NUM_NODES: number of node processes to use\n"
+ " -f NUM_FLOWS: number of flows to be added in the EFD table\n",
+ progname);
+}
+
+/**
+ * The ports to be used by the application are passed in
+ * the form of a bitmask. This function parses the bitmask
+ * and places the port numbers to be used into the port[]
+ * array variable
+ */
+static int
+parse_portmask(uint8_t max_ports, const char *portmask)
+{
+ char *end = NULL;
+ unsigned long pm;
+ uint8_t count = 0;
+
+ if (portmask == NULL || *portmask == '\0')
+ return -1;
+
+ /* convert parameter to a number and verify */
+ pm = strtoul(portmask, &end, 16);
+ if (end == NULL || *end != '\0' || pm == 0)
+ return -1;
+
+ /* loop through bits of the mask and mark ports */
+ while (pm != 0) {
+ if (pm & 0x01) { /* bit is set in mask, use port */
+ if (count >= max_ports)
+ printf("WARNING: requested port %u not present"
+ " - ignoring\n", (unsigned int)count);
+ else
+ info->id[info->num_ports++] = count;
+ }
+ pm = (pm >> 1);
+ count++;
+ }
+
+ return 0;
+}
+
+/**
+ * Take the number of nodes parameter passed to the app
+ * and convert to a number to store in the num_nodes variable
+ */
+static int
+parse_num_nodes(const char *nodes)
+{
+ char *end = NULL;
+ unsigned long temp;
+
+ if (nodes == NULL || *nodes == '\0')
+ return -1;
+
+ temp = strtoul(nodes, &end, 10);
+ if (end == NULL || *end != '\0' || temp == 0)
+ return -1;
+
+ num_nodes = (uint8_t)temp;
+ return 0;
+}
+
+static int
+parse_num_flows(const char *flows)
+{
+ char *end = NULL;
+
+ /* parse hexadecimal string */
+ num_flows = strtoul(flows, &end, 16);
+ if ((flows[0] == '\0') || (end == NULL) || (*end != '\0'))
+ return -1;
+
+ if (num_flows == 0)
+ return -1;
+
+ return 0;
+}
+
+/**
+ * The application specific arguments follow the DPDK-specific
+ * arguments which are stripped by the DPDK init. This function
+ * processes these application arguments, printing usage info
+ * on error.
+ */
+int
+parse_app_args(uint8_t max_ports, int argc, char *argv[])
+{
+ int option_index, opt;
+ char **argvopt = argv;
+ static struct option lgopts[] = { /* no long options */
+ {NULL, 0, 0, 0 }
+ };
+ progname = argv[0];
+
+ while ((opt = getopt_long(argc, argvopt, "n:f:p:", lgopts,
+ &option_index)) != EOF) {
+ switch (opt) {
+ case 'p':
+ if (parse_portmask(max_ports, optarg) != 0) {
+ usage();
+ return -1;
+ }
+ break;
+ case 'n':
+ if (parse_num_nodes(optarg) != 0) {
+ usage();
+ return -1;
+ }
+ break;
+ case 'f':
+ if (parse_num_flows(optarg) != 0) {
+ usage();
+ return -1;
+ }
+ break;
+ default:
+ printf("ERROR: Unknown option '%c'\n", opt);
+ usage();
+ return -1;
+ }
+ }
+
+ if (info->num_ports == 0 || num_nodes == 0) {
+ usage();
+ return -1;
+ }
+
+ if (info->num_ports % 2 != 0) {
+ printf("ERROR: application requires an even "
+ "number of ports to use\n");
+ return -1;
+ }
+ return 0;
+}
diff --git a/examples/flow_distributor/distributor/args.h b/examples/flow_distributor/distributor/args.h
new file mode 100644
index 0000000..cacf395
--- /dev/null
+++ b/examples/flow_distributor/distributor/args.h
@@ -0,0 +1,39 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _ARGS_H_
+#define _ARGS_H_
+
+int parse_app_args(uint8_t max_ports, int argc, char *argv[]);
+
+#endif /* ifndef _ARGS_H_ */
diff --git a/examples/flow_distributor/distributor/init.c b/examples/flow_distributor/distributor/init.c
new file mode 100644
index 0000000..3b0aa85
--- /dev/null
+++ b/examples/flow_distributor/distributor/init.c
@@ -0,0 +1,371 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/queue.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <inttypes.h>
+
+#include <rte_common.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_byteorder.h>
+#include <rte_atomic.h>
+#include <rte_launch.h>
+#include <rte_per_lcore.h>
+#include <rte_lcore.h>
+#include <rte_branch_prediction.h>
+#include <rte_debug.h>
+#include <rte_ring.h>
+#include <rte_log.h>
+#include <rte_mempool.h>
+#include <rte_memcpy.h>
+#include <rte_mbuf.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ether.h>
+#include <rte_ethdev.h>
+#include <rte_malloc.h>
+#include <rte_string_fns.h>
+#include <rte_cycles.h>
+#include <rte_efd.h>
+#include <rte_hash.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+#define MBUFS_PER_NODE 1536
+#define MBUFS_PER_PORT 1536
+#define MBUF_CACHE_SIZE 512
+
+#define RTE_MP_RX_DESC_DEFAULT 512
+#define RTE_MP_TX_DESC_DEFAULT 512
+#define NODE_QUEUE_RINGSIZE 128
+
+#define NO_FLAGS 0
+
+/* The mbuf pool for packet rx */
+struct rte_mempool *pktmbuf_pool;
+
+/* array of info/queues for nodes */
+struct node *nodes;
+
+/* Flow distributor table */
+struct rte_efd_table *efd_table;
+
+/* Shared info between distributor and nodes */
+struct shared_info *info;
+
+/**
+ * Initialise the mbuf pool for packet reception for the NIC, and any other
+ * buffer pools needed by the app - currently none.
+ */
+static int
+init_mbuf_pools(void)
+{
+ const unsigned int num_mbufs = (num_nodes * MBUFS_PER_NODE) +
+ (info->num_ports * MBUFS_PER_PORT);
+
+ /*
+ * Don't pass single-producer/single-consumer flags to mbuf create as it
+ * seems faster to use a cache instead
+ */
+ printf("Creating mbuf pool '%s' [%u mbufs] ...\n",
+ PKTMBUF_POOL_NAME, num_mbufs);
+ pktmbuf_pool = rte_pktmbuf_pool_create(PKTMBUF_POOL_NAME, num_mbufs,
+ MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
+
+ return pktmbuf_pool == NULL; /* 0 on success */
+}
+
+/**
+ * Initialise an individual port:
+ * - configure number of rx and tx rings
+ * - set up each rx ring, to pull from the main mbuf pool
+ * - set up each tx ring
+ * - start the port and report its status to stdout
+ */
+static int
+init_port(uint8_t port_num)
+{
+ /* for port configuration all features are off by default */
+ const struct rte_eth_conf port_conf = {
+ .rxmode = {
+ .mq_mode = ETH_MQ_RX_RSS
+ }
+ };
+ const uint16_t rx_rings = 1, tx_rings = num_nodes;
+ const uint16_t rx_ring_size = RTE_MP_RX_DESC_DEFAULT;
+ const uint16_t tx_ring_size = RTE_MP_TX_DESC_DEFAULT;
+
+ uint16_t q;
+ int retval;
+
+ printf("Port %u init ... ", (unsigned int)port_num);
+ fflush(stdout);
+
+ /*
+ * Standard DPDK port initialisation - config port, then set up
+ * rx and tx rings.
+ */
+ retval = rte_eth_dev_configure(port_num, rx_rings, tx_rings, &port_conf);
+ if (retval != 0)
+ return retval;
+
+ for (q = 0; q < rx_rings; q++) {
+ retval = rte_eth_rx_queue_setup(port_num, q, rx_ring_size,
+ rte_eth_dev_socket_id(port_num),
+ NULL, pktmbuf_pool);
+ if (retval < 0)
+ return retval;
+ }
+
+ for (q = 0; q < tx_rings; q++) {
+ retval = rte_eth_tx_queue_setup(port_num, q, tx_ring_size,
+ rte_eth_dev_socket_id(port_num),
+ NULL);
+ if (retval < 0)
+ return retval;
+ }
+
+ rte_eth_promiscuous_enable(port_num);
+
+ retval = rte_eth_dev_start(port_num);
+ if (retval < 0)
+ return retval;
+
+ printf("done:\n");
+
+ return 0;
+}
+
+/**
+ * Set up the DPDK rings which will be used to pass packets, via
+ * pointers, between the multi-process distributor and node processes.
+ * Each node needs one RX queue.
+ */
+static int
+init_shm_rings(void)
+{
+ unsigned int i;
+ unsigned int socket_id;
+ const char *q_name;
+ const unsigned int ringsize = NODE_QUEUE_RINGSIZE;
+
+ nodes = rte_malloc("node details",
+ sizeof(*nodes) * num_nodes, 0);
+ if (nodes == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot allocate memory for "
+ "node program details\n");
+
+ for (i = 0; i < num_nodes; i++) {
+ /* Create an RX queue for each node */
+ socket_id = rte_socket_id();
+ q_name = get_rx_queue_name(i);
+ nodes[i].rx_q = rte_ring_create(q_name,
+ ringsize, socket_id,
+ RING_F_SP_ENQ | RING_F_SC_DEQ);
+ if (nodes[i].rx_q == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot create rx ring queue "
+ "for node %u\n", i);
+ }
+ return 0;
+}
+
+/*
+ * Create flow distributor table which will contain all the flows
+ * that will be distributed among the nodes
+ */
+static void
+create_flow_distributor_table(void)
+{
+ uint8_t socket_id = rte_socket_id();
+
+ /* create table */
+ efd_table = rte_efd_create("flow table", num_flows * 2, sizeof(uint32_t),
+ 1 << socket_id, socket_id);
+
+ if (efd_table == NULL)
+ rte_exit(EXIT_FAILURE, "Problem creating the flow table\n");
+}
+
+static void
+populate_flow_distributor_table(void)
+{
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint8_t socket_id = rte_socket_id();
+ uint64_t node_id;
+
+ /* Add flows in table */
+ for (i = 0; i < num_flows; i++) {
+ node_id = i % num_nodes;
+
+ ip_dst = rte_cpu_to_be_32(i);
+ ret = rte_efd_update(efd_table, socket_id,
+ (void *)&ip_dst, (efd_value_t)node_id);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u in "
+ "flow distributor table\n", i);
+ }
+
+ printf("EFD table: Adding 0x%x keys\n", num_flows);
+}
+
+/* Check the link status of all ports in up to 9s, and print them finally */
+static void
+check_all_ports_link_status(uint8_t port_num, uint32_t port_mask)
+{
+#define CHECK_INTERVAL 100 /* 100ms */
+#define MAX_CHECK_TIME 90 /* 9s (90 * 100ms) in total */
+ uint8_t portid, count, all_ports_up, print_flag = 0;
+ struct rte_eth_link link;
+
+ printf("\nChecking link status");
+ fflush(stdout);
+ for (count = 0; count <= MAX_CHECK_TIME; count++) {
+ all_ports_up = 1;
+ for (portid = 0; portid < port_num; portid++) {
+ if ((port_mask & (1 << info->id[portid])) == 0)
+ continue;
+ memset(&link, 0, sizeof(link));
+ rte_eth_link_get_nowait(info->id[portid], &link);
+ /* print link status if flag set */
+ if (print_flag == 1) {
+ if (link.link_status)
+ printf("Port %d Link Up - speed %u "
+ "Mbps - %s\n", info->id[portid],
+ (unsigned int)link.link_speed,
+ (link.link_duplex == ETH_LINK_FULL_DUPLEX) ?
+ ("full-duplex") : ("half-duplex\n"));
+ else
+ printf("Port %d Link Down\n",
+ (uint8_t)info->id[portid]);
+ continue;
+ }
+ /* clear all_ports_up flag if any link down */
+ if (link.link_status == ETH_LINK_DOWN) {
+ all_ports_up = 0;
+ break;
+ }
+ }
+ /* after finally printing all link status, get out */
+ if (print_flag == 1)
+ break;
+
+ if (all_ports_up == 0) {
+ printf(".");
+ fflush(stdout);
+ rte_delay_ms(CHECK_INTERVAL);
+ }
+
+ /* set the print_flag if all ports up or timeout */
+ if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
+ print_flag = 1;
+ printf("done\n");
+ }
+ }
+}
+
+/**
+ * Main init function for the multi-process distributor app,
+ * calls subfunctions to do each stage of the initialisation.
+ */
+int
+init(int argc, char *argv[])
+{
+ int retval;
+ const struct rte_memzone *mz;
+ uint8_t i, total_ports;
+
+ /* init EAL, parsing EAL args */
+ retval = rte_eal_init(argc, argv);
+ if (retval < 0)
+ return -1;
+ argc -= retval;
+ argv += retval;
+
+ /* get total number of ports */
+ total_ports = rte_eth_dev_count();
+
+ /* set up array for port data */
+ mz = rte_memzone_reserve(MZ_SHARED_INFO, sizeof(*info),
+ rte_socket_id(), NO_FLAGS);
+ if (mz == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot reserve memory zone "
+ "for port information\n");
+ memset(mz->addr, 0, sizeof(*info));
+ info = mz->addr;
+
+ /* parse additional, application arguments */
+ retval = parse_app_args(total_ports, argc, argv);
+ if (retval != 0)
+ return -1;
+
+ /* initialise mbuf pools */
+ retval = init_mbuf_pools();
+ if (retval != 0)
+ rte_exit(EXIT_FAILURE, "Cannot create needed mbuf pools\n");
+
+ /* now initialise the ports we will use */
+ for (i = 0; i < info->num_ports; i++) {
+ retval = init_port(info->id[i]);
+ if (retval != 0)
+ rte_exit(EXIT_FAILURE, "Cannot initialise port %u\n",
+ (unsigned int) i);
+ }
+
+ check_all_ports_link_status(info->num_ports, (~0x0));
+
+ /* initialise the node queues/rings for inter-eu comms */
+ init_shm_rings();
+
+ /* Create the flow distributor table */
+ create_flow_distributor_table();
+
+ /* Populate the flow distributor table */
+ populate_flow_distributor_table();
+
+ /* Share the total number of nodes */
+ info->num_nodes = num_nodes;
+
+ /* Share the total number of flows */
+ info->num_flows = num_flows;
+ return 0;
+}
diff --git a/examples/flow_distributor/distributor/init.h b/examples/flow_distributor/distributor/init.h
new file mode 100644
index 0000000..d11aacf
--- /dev/null
+++ b/examples/flow_distributor/distributor/init.h
@@ -0,0 +1,76 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _INIT_H_
+#define _INIT_H_
+
+/*
+ * #include <rte_ring.h>
+ * #include "args.h"
+ */
+
+/*
+ * Define a node structure with all needed info, including
+ * stats from the nodes.
+ */
+struct node {
+ struct rte_ring *rx_q;
+ unsigned int node_id;
+ /* these stats hold how many packets the node will actually receive,
+ * and how many packets were dropped because the node's queue was full.
+ * The port-info stats, in contrast, record how many packets were received
+ * or transmitted on an actual NIC port.
+ */
+ struct {
+ uint64_t rx;
+ uint64_t rx_drop;
+ } stats;
+};
+
+extern struct rte_efd_table *efd_table;
+extern struct node *nodes;
+
+/*
+ * shared information between distributor and nodes: number of clients,
+ * port numbers, rx and tx stats etc.
+ */
+extern struct shared_info *info;
+
+extern struct rte_mempool *pktmbuf_pool;
+extern uint8_t num_nodes;
+extern unsigned int num_sockets;
+extern uint32_t num_flows;
+
+int init(int argc, char *argv[]);
+
+#endif /* ifndef _INIT_H_ */
diff --git a/examples/flow_distributor/distributor/main.c b/examples/flow_distributor/distributor/main.c
new file mode 100644
index 0000000..f97f003
--- /dev/null
+++ b/examples/flow_distributor/distributor/main.c
@@ -0,0 +1,362 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <inttypes.h>
+#include <sys/queue.h>
+#include <errno.h>
+#include <netinet/ip.h>
+
+#include <rte_common.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_byteorder.h>
+#include <rte_launch.h>
+#include <rte_per_lcore.h>
+#include <rte_lcore.h>
+#include <rte_branch_prediction.h>
+#include <rte_atomic.h>
+#include <rte_ring.h>
+#include <rte_log.h>
+#include <rte_debug.h>
+#include <rte_mempool.h>
+#include <rte_memcpy.h>
+#include <rte_mbuf.h>
+#include <rte_ether.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ethdev.h>
+#include <rte_byteorder.h>
+#include <rte_malloc.h>
+#include <rte_string_fns.h>
+#include <rte_efd.h>
+#include <rte_ip.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+/*
+ * When doing reads from the NIC or the node queues,
+ * use this batch size
+ */
+#define PACKET_READ_SIZE 32
+
+/*
+ * Local buffers to put packets in, used to send packets in bursts to the
+ * nodes
+ */
+struct node_rx_buf {
+ struct rte_mbuf *buffer[PACKET_READ_SIZE];
+ uint16_t count;
+};
+
+struct flow_distributor_stats {
+ uint64_t distributed;
+ uint64_t drop;
+} flow_dist_stats;
+
+/* One buffer per node rx queue - dynamically allocate array */
+static struct node_rx_buf *cl_rx_buf;
+
+static const char *
+get_printable_mac_addr(uint8_t port)
+{
+ static const char err_address[] = "00:00:00:00:00:00";
+ static char addresses[RTE_MAX_ETHPORTS][sizeof(err_address)];
+ struct ether_addr mac;
+
+ if (unlikely(port >= RTE_MAX_ETHPORTS))
+ return err_address;
+ if (unlikely(addresses[port][0] == '\0')) {
+ rte_eth_macaddr_get(port, &mac);
+ snprintf(addresses[port], sizeof(addresses[port]),
+ "%02x:%02x:%02x:%02x:%02x:%02x\n",
+ mac.addr_bytes[0], mac.addr_bytes[1],
+ mac.addr_bytes[2], mac.addr_bytes[3],
+ mac.addr_bytes[4], mac.addr_bytes[5]);
+ }
+ return addresses[port];
+}
+
+/*
+ * This function displays the recorded statistics for each port
+ * and for each node. It uses ANSI terminal codes to clear
+ * screen when called. It is called from a single non-master
+ * thread in the distributor process, when the process is run with more
+ * than one lcore enabled.
+ */
+static void
+do_stats_display(void)
+{
+ unsigned int i, j;
+ const char clr[] = {27, '[', '2', 'J', '\0'};
+ const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
+ uint64_t port_tx[RTE_MAX_ETHPORTS], port_tx_drop[RTE_MAX_ETHPORTS];
+ uint64_t node_tx[MAX_NODES], node_tx_drop[MAX_NODES];
+
+ /* to get TX stats, we need to do some summing calculations */
+ memset(port_tx, 0, sizeof(port_tx));
+ memset(port_tx_drop, 0, sizeof(port_tx_drop));
+ memset(node_tx, 0, sizeof(node_tx));
+ memset(node_tx_drop, 0, sizeof(node_tx_drop));
+
+ for (i = 0; i < num_nodes; i++) {
+ const struct tx_stats *tx = &info->tx_stats[i];
+
+ for (j = 0; j < info->num_ports; j++) {
+ const uint64_t tx_val = tx->tx[info->id[j]];
+ const uint64_t drop_val = tx->tx_drop[info->id[j]];
+
+ port_tx[j] += tx_val;
+ port_tx_drop[j] += drop_val;
+ node_tx[i] += tx_val;
+ node_tx_drop[i] += drop_val;
+ }
+ }
+
+ /* Clear screen and move to top left */
+ printf("%s%s", clr, topLeft);
+
+ printf("PORTS\n");
+ printf("-----\n");
+ for (i = 0; i < info->num_ports; i++)
+ printf("Port %u: '%s'\t", (unsigned int)info->id[i],
+ get_printable_mac_addr(info->id[i]));
+ printf("\n\n");
+ for (i = 0; i < info->num_ports; i++) {
+ printf("Port %u - rx: %9"PRIu64"\t"
+ "tx: %9"PRIu64"\n",
+ (unsigned int)info->id[i], info->rx_stats.rx[i],
+ port_tx[i]);
+ }
+
+ printf("\nFLOW DISTRIBUTOR\n");
+ printf("-----\n");
+ printf("distributed: %9"PRIu64", drop: %9"PRIu64"\n",
+ flow_dist_stats.distributed, flow_dist_stats.drop);
+
+ printf("\nNODES\n");
+ printf("-------\n");
+ for (i = 0; i < num_nodes; i++) {
+ const unsigned long long rx = nodes[i].stats.rx;
+ const unsigned long long rx_drop = nodes[i].stats.rx_drop;
+ const struct filter_stats *filter = &info->filter_stats[i];
+
+ printf("Node %2u - rx: %9llu, rx_drop: %9llu\n"
+ " tx: %9"PRIu64", tx_drop: %9"PRIu64"\n"
+ " filter_passed: %9"PRIu64", "
+ "filter_drop: %9"PRIu64"\n",
+ i, rx, rx_drop, node_tx[i], node_tx_drop[i],
+ filter->passed, filter->drop);
+ }
+
+ printf("\n");
+}
+
+/*
+ * The function called from each non-master lcore used by the process.
+ * The test_and_set function is used to randomly pick a single lcore on which
+ * the code to display the statistics will run. Otherwise, the code just
+ * repeatedly sleeps.
+ */
+static int
+sleep_lcore(__attribute__((unused)) void *dummy)
+{
+ /* Used to pick a display thread - static, so zero-initialised */
+ static rte_atomic32_t display_stats;
+
+ /* Only one core should display stats */
+ if (rte_atomic32_test_and_set(&display_stats)) {
+ const unsigned int sleeptime = 1;
+
+ printf("Core %u displaying statistics\n", rte_lcore_id());
+
+ /* Longer initial pause so above printf is seen */
+ sleep(sleeptime * 3);
+
+ /* Loop forever: sleep always returns 0 or <= param */
+ while (sleep(sleeptime) <= sleeptime)
+ do_stats_display();
+ }
+ return 0;
+}
+
+/*
+ * Function to set all the node statistic values to zero.
+ * Called at program startup.
+ */
+static void
+clear_stats(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_nodes; i++)
+ nodes[i].stats.rx = nodes[i].stats.rx_drop = 0;
+}
+
+/*
+ * send a burst of traffic to a node, assuming there are packets
+ * available to be sent to this node
+ */
+static void
+flush_rx_queue(uint16_t node)
+{
+ uint16_t j;
+ struct node *cl;
+
+ if (cl_rx_buf[node].count == 0)
+ return;
+
+ cl = &nodes[node];
+ if (rte_ring_enqueue_bulk(cl->rx_q, (void **)cl_rx_buf[node].buffer,
+ cl_rx_buf[node].count) != 0){
+ for (j = 0; j < cl_rx_buf[node].count; j++)
+ rte_pktmbuf_free(cl_rx_buf[node].buffer[j]);
+ cl->stats.rx_drop += cl_rx_buf[node].count;
+ } else
+ cl->stats.rx += cl_rx_buf[node].count;
+
+ cl_rx_buf[node].count = 0;
+}
+
+/*
+ * marks a packet down to be sent to a particular node process
+ */
+static inline void
+enqueue_rx_packet(uint8_t node, struct rte_mbuf *buf)
+{
+ cl_rx_buf[node].buffer[cl_rx_buf[node].count++] = buf;
+}
+
+/*
+ * This function takes a group of packets and routes them
+ * individually to the node process. Very simply round-robins the packets
+ * without checking any of the packet contents.
+ */
+static void
+process_packets(uint32_t port_num __rte_unused, struct rte_mbuf *pkts[],
+ uint16_t rx_count, unsigned int socket_id)
+{
+ uint16_t i;
+ uint8_t node;
+ efd_value_t data[RTE_EFD_BURST_MAX];
+ const void *key_ptrs[RTE_EFD_BURST_MAX];
+
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[RTE_EFD_BURST_MAX];
+
+ for (i = 0; i < rx_count; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(pkts[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = (void *)&ipv4_dst_ip[i];
+ }
+
+ rte_efd_lookup_bulk(efd_table, socket_id, rx_count,
+ (const void **) key_ptrs, data);
+ for (i = 0; i < rx_count; i++) {
+ node = (uint8_t) ((uintptr_t)data[i]);
+
+ if (node >= num_nodes) {
+ /*
+ * Node is out of range, which means that
+ * flow has not been inserted
+ */
+ flow_dist_stats.drop++;
+ rte_pktmbuf_free(pkts[i]);
+ } else {
+ flow_dist_stats.distributed++;
+ enqueue_rx_packet(node, pkts[i]);
+ }
+ }
+
+ for (i = 0; i < num_nodes; i++)
+ flush_rx_queue(i);
+}
+
+/*
+ * Function called by the master lcore of the DPDK process.
+ */
+static void
+do_packet_forwarding(void)
+{
+ unsigned int port_num = 0; /* indexes the port[] array */
+ unsigned int socket_id = rte_socket_id();
+
+ for (;;) {
+ struct rte_mbuf *buf[PACKET_READ_SIZE];
+ uint16_t rx_count;
+
+ /* read a port */
+ rx_count = rte_eth_rx_burst(info->id[port_num], 0,
+ buf, PACKET_READ_SIZE);
+ info->rx_stats.rx[port_num] += rx_count;
+
+ /* Now process the NIC packets read */
+ if (likely(rx_count > 0))
+ process_packets(port_num, buf, rx_count, socket_id);
+
+ /* move to next port */
+ if (++port_num == info->num_ports)
+ port_num = 0;
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ /* initialise the system */
+ if (init(argc, argv) < 0)
+ return -1;
+ RTE_LOG(INFO, APP, "Finished Process Init.\n");
+
+ cl_rx_buf = calloc(num_nodes, sizeof(cl_rx_buf[0]));
+
+ /* clear statistics */
+ clear_stats();
+
+ /* put all other cores to sleep bar master */
+ rte_eal_mp_remote_launch(sleep_lcore, NULL, SKIP_MASTER);
+
+ do_packet_forwarding();
+ return 0;
+}
diff --git a/examples/flow_distributor/node/Makefile b/examples/flow_distributor/node/Makefile
new file mode 100644
index 0000000..8cf7b65
--- /dev/null
+++ b/examples/flow_distributor/node/Makefile
@@ -0,0 +1,48 @@
+# BSD LICENSE
+#
+# Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overridden by command line or environment
+include $(RTE_SDK)/mk/rte.vars.mk
+
+# binary name
+APP = node
+
+# all source are stored in SRCS-y
+SRCS-y := node.c
+
+CFLAGS += $(WERROR_FLAGS) -O3
+CFLAGS += -I$(SRCDIR)/../shared
+
+include $(RTE_SDK)/mk/rte.extapp.mk
diff --git a/examples/flow_distributor/node/node.c b/examples/flow_distributor/node/node.c
new file mode 100644
index 0000000..1f1e7e7
--- /dev/null
+++ b/examples/flow_distributor/node/node.c
@@ -0,0 +1,417 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <sys/queue.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_malloc.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_atomic.h>
+#include <rte_branch_prediction.h>
+#include <rte_log.h>
+#include <rte_per_lcore.h>
+#include <rte_launch.h>
+#include <rte_lcore.h>
+#include <rte_ring.h>
+#include <rte_launch.h>
+#include <rte_lcore.h>
+#include <rte_debug.h>
+#include <rte_mempool.h>
+#include <rte_mbuf.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ether.h>
+#include <rte_ethdev.h>
+#include <rte_string_fns.h>
+#include <rte_ip.h>
+
+#include "common.h"
+
+/* Number of packets to attempt to read from queue */
+#define PKT_READ_SIZE ((uint16_t)32)
+
+/*
+ * Our node id number - tells us which rx queue to read, and NIC TX
+ * queue to write to.
+ */
+static uint8_t node_id;
+
+#define MBQ_CAPACITY 32
+
+/* maps input ports to output ports for packets */
+static uint8_t output_ports[RTE_MAX_ETHPORTS];
+
+/* buffers up a set of packet that are ready to send */
+struct rte_eth_dev_tx_buffer *tx_buffer[RTE_MAX_ETHPORTS];
+
+/* shared data from distributor. We update statistics here */
+static struct tx_stats *tx_stats;
+
+static struct filter_stats *filter_stats;
+
+/*
+ * print a usage message
+ */
+static void
+usage(const char *progname)
+{
+ printf("Usage: %s [EAL args] -- -n <node_id>\n\n", progname);
+}
+
+/*
+ * Convert the node id number from a string to an int.
+ */
+static int
+parse_node_num(const char *node)
+{
+ char *end = NULL;
+ unsigned long temp;
+
+ if (node == NULL || *node == '\0')
+ return -1;
+
+ temp = strtoul(node, &end, 10);
+ if (end == NULL || *end != '\0')
+ return -1;
+
+ node_id = (uint8_t)temp;
+ return 0;
+}
+
+/*
+ * Parse the application arguments to the node app.
+ */
+static int
+parse_app_args(int argc, char *argv[])
+{
+ int option_index, opt;
+ char **argvopt = argv;
+ const char *progname = NULL;
+ static struct option lgopts[] = { /* no long options */
+ {NULL, 0, 0, 0 }
+ };
+ progname = argv[0];
+
+ while ((opt = getopt_long(argc, argvopt, "n:", lgopts,
+ &option_index)) != EOF) {
+ switch (opt) {
+ case 'n':
+ if (parse_node_num(optarg) != 0) {
+ usage(progname);
+ return -1;
+ }
+ break;
+ default:
+ usage(progname);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Tx buffer error callback
+ */
+static void
+flush_tx_error_callback(struct rte_mbuf **unsent, uint16_t count,
+ void *userdata) {
+ int i;
+ uint8_t port_id = (uintptr_t)userdata;
+
+ tx_stats->tx_drop[port_id] += count;
+
+ /* free the mbufs which failed from transmit */
+ for (i = 0; i < count; i++)
+ rte_pktmbuf_free(unsent[i]);
+
+}
+
+static void
+configure_tx_buffer(uint8_t port_id, uint16_t size)
+{
+ int ret;
+
+ /* Initialize TX buffers */
+ tx_buffer[port_id] = rte_zmalloc_socket("tx_buffer",
+ RTE_ETH_TX_BUFFER_SIZE(size), 0,
+ rte_eth_dev_socket_id(port_id));
+ if (tx_buffer[port_id] == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot allocate buffer for tx "
+ "on port %u\n", (unsigned int) port_id);
+
+ rte_eth_tx_buffer_init(tx_buffer[port_id], size);
+
+ ret = rte_eth_tx_buffer_set_err_callback(tx_buffer[port_id],
+ flush_tx_error_callback, (void *)(intptr_t)port_id);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Cannot set error callback for "
+ "tx buffer on port %u\n", (unsigned int) port_id);
+}
+
+/*
+ * set up output ports so that all traffic on port gets sent out
+ * its paired port. Index using actual port numbers since that is
+ * what comes in the mbuf structure.
+ */
+static void
+configure_output_ports(const struct shared_info *info)
+{
+ int i;
+
+ if (info->num_ports > RTE_MAX_ETHPORTS)
+ rte_exit(EXIT_FAILURE, "Too many ethernet ports. "
+ "RTE_MAX_ETHPORTS = %u\n",
+ (unsigned int)RTE_MAX_ETHPORTS);
+ for (i = 0; i < info->num_ports - 1; i += 2) {
+ uint8_t p1 = info->id[i];
+ uint8_t p2 = info->id[i+1];
+
+ output_ports[p1] = p2;
+ output_ports[p2] = p1;
+
+ configure_tx_buffer(p1, MBQ_CAPACITY);
+ configure_tx_buffer(p2, MBQ_CAPACITY);
+
+ }
+}
+
+/*
+ * Create the hash table that will contain the flows that
+ * the node will handle, which will be used to decide if packet
+ * is transmitted or dropped.
+ */
+static struct rte_hash *
+create_hash_table(const struct shared_info *info)
+{
+ uint32_t num_flows_node = info->num_flows / info->num_nodes;
+ char name[RTE_HASH_NAMESIZE];
+ struct rte_hash *h;
+
+ /* create table */
+ struct rte_hash_parameters hash_params = {
+ .entries = num_flows_node * 2, /* table load = 50% */
+ .key_len = sizeof(uint32_t), /* Store IPv4 dest IP address */
+ .socket_id = rte_socket_id(),
+ .hash_func_init_val = 0,
+ };
+
+ snprintf(name, sizeof(name), "hash_table_%d", node_id);
+ hash_params.name = name;
+ h = rte_hash_create(&hash_params);
+
+ if (h == NULL)
+ rte_exit(EXIT_FAILURE,
+ "Problem creating the hash table for node %d\n",
+ node_id);
+ return h;
+}
+
+static void
+populate_hash_table(const struct rte_hash *h, const struct shared_info *info)
+{
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint32_t num_flows_node = 0;
+ uint64_t target_node;
+
+ /* Add flows in table */
+ for (i = 0; i < info->num_flows; i++) {
+ target_node = i % info->num_nodes;
+ if (target_node != node_id)
+ continue;
+
+ ip_dst = rte_cpu_to_be_32(i);
+
+ ret = rte_hash_add_key(h, (void *) &ip_dst);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u "
+ "in hash table\n", i);
+ else
+ num_flows_node++;
+
+ }
+
+ printf("Hash table: Adding 0x%x keys\n", num_flows_node);
+}
+
+/*
+ * This function performs routing of packets
+ * Just sends each input packet out an output port based solely on the input
+ * port it arrived on.
+ */
+static inline void
+transmit_packet(struct rte_mbuf *buf)
+{
+ int sent;
+ const uint8_t in_port = buf->port;
+ const uint8_t out_port = output_ports[in_port];
+ struct rte_eth_dev_tx_buffer *buffer = tx_buffer[out_port];
+
+ sent = rte_eth_tx_buffer(out_port, node_id, buffer, buf);
+ if (sent)
+ tx_stats->tx[out_port] += sent;
+
+}
+
+static inline void
+handle_packets(struct rte_hash *h, struct rte_mbuf **bufs, uint16_t num_packets)
+{
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[PKT_READ_SIZE];
+ const void *key_ptrs[PKT_READ_SIZE];
+ unsigned int i;
+ int32_t positions[PKT_READ_SIZE] = {0};
+
+ for (i = 0; i < num_packets; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(bufs[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = &ipv4_dst_ip[i];
+ }
+ /* Check if packets belongs to any flows handled by this node */
+ rte_hash_lookup_bulk(h, key_ptrs, num_packets, positions);
+
+ for (i = 0; i < num_packets; i++) {
+ if (likely(positions[i] >= 0)) {
+ filter_stats->passed++;
+ transmit_packet(bufs[i]);
+ } else {
+ filter_stats->drop++;
+ /* Drop packet, as flow is not handled by this node */
+ rte_pktmbuf_free(bufs[i]);
+ }
+ }
+}
+
+/*
+ * Application main function - loops through
+ * receiving and processing packets. Never returns
+ */
+int
+main(int argc, char *argv[])
+{
+ const struct rte_memzone *mz;
+ struct rte_ring *rx_ring;
+ struct rte_hash *h;
+ struct rte_mempool *mp;
+ struct shared_info *info;
+ int need_flush = 0; /* indicates whether we have unsent packets */
+ int retval;
+ void *pkts[PKT_READ_SIZE];
+ uint16_t sent;
+
+ retval = rte_eal_init(argc, argv);
+ if (retval < 0)
+ return -1;
+ argc -= retval;
+ argv += retval;
+
+ if (parse_app_args(argc, argv) < 0)
+ rte_exit(EXIT_FAILURE, "Invalid command-line arguments\n");
+
+ if (rte_eth_dev_count() == 0)
+ rte_exit(EXIT_FAILURE, "No Ethernet ports - bye\n");
+
+ rx_ring = rte_ring_lookup(get_rx_queue_name(node_id));
+ if (rx_ring == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get RX ring - "
+ "is distributor process running?\n");
+
+ mp = rte_mempool_lookup(PKTMBUF_POOL_NAME);
+ if (mp == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get mempool for mbufs\n");
+
+ mz = rte_memzone_lookup(MZ_SHARED_INFO);
+ if (mz == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get port info structure\n");
+ info = mz->addr;
+ tx_stats = &(info->tx_stats[node_id]);
+ filter_stats = &(info->filter_stats[node_id]);
+
+ configure_output_ports(info);
+
+ h = create_hash_table(info);
+
+ populate_hash_table(h, info);
+
+ RTE_LOG(INFO, APP, "Finished Process Init.\n");
+
+ printf("\nNode process %d handling packets\n", node_id);
+ printf("[Press Ctrl-C to quit ...]\n");
+
+ for (;;) {
+ uint16_t rx_pkts = PKT_READ_SIZE;
+ uint8_t port;
+
+ /*
+ * Try dequeuing max possible packets first, if that fails,
+ * get the most we can. Loop body should only execute once,
+ * maximum
+ */
+ while (rx_pkts > 0 &&
+ unlikely(rte_ring_dequeue_bulk(rx_ring, pkts,
+ rx_pkts) != 0))
+ rx_pkts = (uint16_t)RTE_MIN(rte_ring_count(rx_ring),
+ PKT_READ_SIZE);
+
+ if (unlikely(rx_pkts == 0)) {
+ if (need_flush)
+ for (port = 0; port < info->num_ports; port++) {
+ sent = rte_eth_tx_buffer_flush(
+ info->id[port],
+ node_id,
+ tx_buffer[port]);
+ if (unlikely(sent))
+ tx_stats->tx[port] += sent;
+ }
+ need_flush = 0;
+ continue;
+ }
+
+ handle_packets(h, (struct rte_mbuf **)pkts, rx_pkts);
+
+ need_flush = 1;
+ }
+}
diff --git a/examples/flow_distributor/shared/common.h b/examples/flow_distributor/shared/common.h
new file mode 100644
index 0000000..5dcffd6
--- /dev/null
+++ b/examples/flow_distributor/shared/common.h
@@ -0,0 +1,99 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _COMMON_H_
+#define _COMMON_H_
+
+#include <rte_hash_crc.h>
+#include <rte_hash.h>
+
+#define MAX_NODES 16
+/*
+ * Shared port info, including statistics information for display by distributor.
+ * Structure will be put in a memzone.
+ * - All port id values share one cache line as this data will be read-only
+ * during operation.
+ * - All rx statistic values share cache lines, as this data is written only
+ * by the distributor process. (rare reads by stats display)
+ * - The tx statistics have values for all ports per cache line, but the stats
+ * themselves are written by the nodes, so we have a distinct set, on different
+ * cache lines for each node to use.
+ */
+struct rx_stats {
+ uint64_t rx[RTE_MAX_ETHPORTS];
+} __rte_cache_aligned;
+
+struct tx_stats {
+ uint64_t tx[RTE_MAX_ETHPORTS];
+ uint64_t tx_drop[RTE_MAX_ETHPORTS];
+} __rte_cache_aligned;
+
+struct filter_stats {
+ uint64_t drop;
+ uint64_t passed;
+} __rte_cache_aligned;
+
+struct shared_info {
+ uint8_t num_nodes;
+ uint8_t num_ports;
+ uint32_t num_flows;
+ uint8_t id[RTE_MAX_ETHPORTS];
+ struct rx_stats rx_stats;
+ struct tx_stats tx_stats[MAX_NODES];
+ struct filter_stats filter_stats[MAX_NODES];
+};
+
+/* define common names for structures shared between distributor and node */
+#define MP_NODE_RXQ_NAME "MProc_Node_%u_RX"
+#define PKTMBUF_POOL_NAME "MProc_pktmbuf_pool"
+#define MZ_SHARED_INFO "MProc_shared_info"
+
+/*
+ * Given the rx queue name template above, get the queue name
+ */
+static inline const char *
+get_rx_queue_name(unsigned int id)
+{
+ /*
+ * Buffer for return value. Size calculated by %u being replaced
+ * by maximum 3 digits (plus an extra byte for safety)
+ */
+ static char buffer[sizeof(MP_NODE_RXQ_NAME) + 2];
+
+ snprintf(buffer, sizeof(buffer) - 1, MP_NODE_RXQ_NAME, id);
+ return buffer;
+}
+
+#define RTE_LOGTYPE_APP RTE_LOGTYPE_USER1
+
+#endif
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v8 5/6] doc: add EFD library section in Programmers guide
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 " Pablo de Lara
` (3 preceding siblings ...)
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 4/6] examples/flow_distributor: sample app to demonstrate EFD usage Pablo de Lara
@ 2017-01-17 22:23 ` Pablo de Lara
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 6/6] doc: add flow distributor guide Pablo de Lara
2017-01-18 19:57 ` [dpdk-dev] [PATCH v8 0/6] Elastic Flow Distributor Thomas Monjalon
6 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-17 22:23 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Sameh Gobriel
Signed-off-by: Sameh Gobriel <sameh.gobriel@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 1 +
doc/guides/prog_guide/efd_lib.rst | 440 +++++++++++
doc/guides/prog_guide/img/efd_i1.svg | 130 ++++
doc/guides/prog_guide/img/efd_i10.svg | 384 ++++++++++
doc/guides/prog_guide/img/efd_i11.svg | 319 ++++++++
doc/guides/prog_guide/img/efd_i12.svg | 1008 +++++++++++++++++++++++++
doc/guides/prog_guide/img/efd_i2.svg | 280 +++++++
doc/guides/prog_guide/img/efd_i3.svg | 634 ++++++++++++++++
doc/guides/prog_guide/img/efd_i4.svg | 203 ++++++
doc/guides/prog_guide/img/efd_i5.svg | 183 +++++
doc/guides/prog_guide/img/efd_i6.svg | 1254 ++++++++++++++++++++++++++++++++
doc/guides/prog_guide/img/efd_i7.svg | 790 ++++++++++++++++++++
doc/guides/prog_guide/img/efd_i8.svg | 182 +++++
doc/guides/prog_guide/img/efd_i9.svg | 390 ++++++++++
doc/guides/prog_guide/index.rst | 23 +
doc/guides/rel_notes/release_17_02.rst | 3 +
16 files changed, 6224 insertions(+)
create mode 100644 doc/guides/prog_guide/efd_lib.rst
create mode 100644 doc/guides/prog_guide/img/efd_i1.svg
create mode 100644 doc/guides/prog_guide/img/efd_i10.svg
create mode 100644 doc/guides/prog_guide/img/efd_i11.svg
create mode 100644 doc/guides/prog_guide/img/efd_i12.svg
create mode 100644 doc/guides/prog_guide/img/efd_i2.svg
create mode 100644 doc/guides/prog_guide/img/efd_i3.svg
create mode 100644 doc/guides/prog_guide/img/efd_i4.svg
create mode 100644 doc/guides/prog_guide/img/efd_i5.svg
create mode 100644 doc/guides/prog_guide/img/efd_i6.svg
create mode 100644 doc/guides/prog_guide/img/efd_i7.svg
create mode 100644 doc/guides/prog_guide/img/efd_i8.svg
create mode 100644 doc/guides/prog_guide/img/efd_i9.svg
diff --git a/MAINTAINERS b/MAINTAINERS
index b124f6e..66e9466 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -532,6 +532,7 @@ EFD
M: Byron Marohn <byron.marohn@intel.com>
M: Pablo de Lara Guarch <pablo.de.lara.guarch@intel.com>
F: lib/librte_efd/
+F: doc/guides/prog_guide/efd_lib.rst
F: app/test/test_efd*
F: examples/flow_distributor/
diff --git a/doc/guides/prog_guide/efd_lib.rst b/doc/guides/prog_guide/efd_lib.rst
new file mode 100644
index 0000000..5b8e4e3
--- /dev/null
+++ b/doc/guides/prog_guide/efd_lib.rst
@@ -0,0 +1,440 @@
+.. BSD LICENSE
+ Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of Intel Corporation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+.. _Efd_Library:
+
+Elastic Flow Distributor Library
+================================
+
+Introduction
+------------
+
+In Data Centers today, clustering and scheduling of distributed workloads
+is a very common task. Many workloads require a deterministic
+partitioning of a flat key space among a cluster of machines. When a
+packet enters the cluster, the ingress node will direct the packet to
+its handling node. For example, data-centers with disaggregated storage
+use storage metadata tables to forward I/O requests to the correct back end
+storage cluster, stateful packet inspection will use match incoming
+flows to signatures in flow tables to send incoming packets to their
+intended deep packet inspection (DPI) devices, and so on.
+
+EFD is a distributor library that uses perfect hashing to determine a
+target/value for a given incoming flow key. It has the following
+advantages: first, because it uses perfect hashing it does not store the
+key itself and hence lookup performance is not dependent on the key
+size. Second, the target/value can be any arbitrary value hence the
+system designer and/or operator can better optimize service rates and
+inter-cluster network traffic locating. Third, since the storage
+requirement is much smaller than a hash-based flow table (i.e. better
+fit for CPU cache), EFD can scale to millions of flow keys. Finally,
+with the current optimized library implementation, performance is fully
+scalable with any number of CPU cores.
+
+Flow Based Distribution
+-----------------------
+
+Computation Based Schemes
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Flow distribution and/or load balancing can be simply done using a
+stateless computation, for instance using round-robin or a simple
+computation based on the flow key as an input. For example, a hash
+function can be used to direct a certain flow to a target based on
+the flow key (e.g. ``h(key) mod n``) where h(key) is the hash value of the
+flow key and n is the number of possible targets.
+
+.. _figure_efd1:
+
+.. figure:: img/efd_i1.*
+
+ Load Balancing Using Front End Node
+
+In this scheme (:numref:`figure_efd1`), the front end server/distributor/load balancer
+extracts the flow key from the input packet and applies a computation to determine where
+this flow should be directed. Intuitively, this scheme is very simple
+and requires no state to be kept at the front end node, and hence,
+storage requirements are minimum.
+
+.. _figure_efd2:
+
+.. figure:: img/efd_i2.*
+
+ Consistent Hashing
+
+A widely used flow distributor that belongs to the same category of
+computation-based schemes is ``consistent hashing``, shown in :numref:`figure_efd2`.
+Target destinations (shown in red) are hashed into the same space as the flow
+keys (shown in blue), and keys are mapped to the nearest target in a clockwise
+fashion. Dynamically adding and removing targets with consistent hashing
+requires only K/n keys to be remapped on average, where K is the number of
+keys, and n is the number of targets. In contrast, in a traditional hash-based
+scheme, a change in the number of targets causes nearly all keys to be
+remapped.
+
+Although computation-based schemes are simple and need very little
+storage requirement, they suffer from the drawback that the system
+designer/operator can’t fully control the target to assign a specific
+key, as this is dictated by the hash function.
+Deterministically co-locating of keys together (for example, to minimize
+inter-server traffic or to optimize for network traffic conditions,
+target load, etc.) is simply not possible.
+
+Flow-Table Based Schemes
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+When using a Flow-Table based scheme to handle flow distribution/load
+balancing, in contrast with computation-based schemes, the system designer
+has the flexibility of assigning a given flow to any given
+target. The flow table (e.g. DPDK RTE Hash Library) will simply store
+both the flow key and the target value.
+
+.. _figure_efd3:
+
+.. figure:: img/efd_i3.*
+
+ Table Based Flow Distribution
+
+As shown in :numref:`figure_efd3`, when doing a lookup, the flow-table
+is indexed with the hash of the flow key and the keys (more than one is possible,
+because of hash collision) stored in this index and corresponding values
+are retrieved. The retrieved key(s) is matched with the input flow key
+and if there is a match the value (target id) is returned.
+
+The drawback of using a hash table for flow distribution/load balancing
+is the storage requirement, since the flow table need to store keys,
+signatures and target values. This doesn't allow this scheme to scale to
+millions of flow keys. Large tables will usually not fit in
+the CPU cache, and hence, the lookup performance is degraded because of
+the latency to access the main memory.
+
+EFD Based Scheme
+~~~~~~~~~~~~~~~~
+
+EFD combines the advantages of both flow-table based and computation-based
+schemes. It doesn't require the large storage necessary for
+flow-table based schemes (because EFD doesn't store the key as explained
+below), and it supports any arbitrary value for any given key.
+
+.. _figure_efd4:
+
+.. figure:: img/efd_i4.*
+
+ Searching for Perfect Hash Function
+
+The basic idea of EFD is when a given key is to be inserted, a family of
+hash functions is searched until the correct hash function that maps the
+input key to the correct value is found, as shown in :numref:`figure_efd4`.
+However, rather than explicitly storing all keys and their associated values,
+EFD stores only indices of hash functions that map keys to values, and
+thereby consumes much less space than conventional flow-based tables.
+The lookup operation is very simple, similar to a computational-based
+scheme: given an input key the lookup operation is reduced to hashing
+that key with the correct hash function.
+
+.. _figure_efd5:
+
+.. figure:: img/efd_i5.*
+
+ Divide and Conquer for Millions of Keys
+
+Intuitively, finding a hash function that maps each of a large number
+(millions) of input keys to the correct output value is effectively
+impossible, as a result EFD, as shown in :numref:`figure_efd5`,
+breaks the problem into smaller pieces (divide and conquer).
+EFD divides the entire input key set into many small groups.
+Each group consists of approximately 20-28 keys (a configurable parameter
+for the library), then, for each small group, a brute force search to find
+a hash function that produces the correct outputs for each key in the group.
+
+It should be mentioned that, since the online lookup table for EFD
+doesn't store the key itself, the size of the EFD table is independent
+of the key size and hence EFD lookup performance which is almost
+constant irrespective of the length of the key which is a highly
+desirable feature especially for longer keys.
+
+In summary, EFD is a set separation data structure that supports millions of
+keys. It is used to distribute a given key to an intended target. By itself
+EFD is not a FIB data structure with an exact match the input flow key.
+
+.. _Efd_example:
+
+Example of EFD Library Usage
+----------------------------
+
+EFD can be used along the data path of many network functions and middleboxes.
+As previously mentioned, it can used as an index table for
+<key,value> pairs, meta-data for objects, a flow-level load balancer, etc.
+:numref:`figure_efd6` shows an example of using EFD as a flow-level load
+balancer, where flows are received at a front end server before being forwarded
+to the target back end server for processing. The system designer would
+deterministically co-locate flows together in order to minimize cross-server
+interaction.
+(For example, flows requesting certain webpage objects are co-located
+together, to minimize forwarding of common objects across servers).
+
+.. _figure_efd6:
+
+.. figure:: img/efd_i6.*
+
+ EFD as a Flow-Level Load Balancer
+
+As shown in :numref:`figure_efd6`, the front end server will have an EFD table that
+stores for each group what is the perfect hash index that satisfies the
+correct output. Because the table size is small and fits in cache (since
+keys are not stored), it sustains a large number of flows (N*X, where N
+is the maximum number of flows served by each back end server of the X
+possible targets).
+
+With an input flow key, the group id is computed (for example, using
+last few bits of CRC hash) and then the EFD table is indexed with the
+group id to retrieve the corresponding hash index to use. Once the index
+is retrieved the key is hashed using this hash function and the result
+will be the intended correct target where this flow is supposed to be
+processed.
+
+It should be noted that as a result of EFD not matching the exact key but
+rather distributing the flows to a target back end node based on the
+perfect hash index, a key that has not been inserted before
+will be distributed to a valid target. Hence, a local table which stores
+the flows served at each node is used and is
+exact matched with the input key to rule out new never seen before
+flows.
+
+.. _Efd_api:
+
+Library API Overview
+--------------------
+
+The EFD library API is created with a very similar semantics of a
+hash-index or a flow table. The application creates an EFD table for a
+given maximum number of flows, a function is called to insert a flow key
+with a specific target value, and another function is used to retrieve
+target values for a given individual flow key or a bulk of keys.
+
+EFD Table Create
+~~~~~~~~~~~~~~~~
+
+The function ``rte_efd_create()`` is used to create and return a pointer
+to an EFD table that is sized to hold up to num_flows key.
+The online version of the EFD table (the one that does
+not store the keys and is used for lookups) will be allocated and
+created in the last level cache (LLC) of the socket defined by the
+online_socket_bitmask, while the offline EFD table (the one that
+stores the keys and is used for key inserts and for computing the
+perfect hashing) is allocated and created in the LLC of the socket
+defined by offline_socket_bitmask. It should be noted, that for
+highest performance the socket id should match that where the thread is
+running, i.e. the online EFD lookup table should be created on the same
+socket as where the lookup thread is running.
+
+EFD Insert and Update
+~~~~~~~~~~~~~~~~~~~~~
+
+The EFD function to insert a key or update a key to a new value is
+``rte_efd_update()``. This function will update an existing key to
+a new value (target) if the key has already been inserted
+before, or will insert the <key,value> pair if this key has not been inserted
+before. It will return 0 upon success. It will return
+``EFD_UPDATE_WARN_GROUP_FULL (1)`` if the operation is insert, and the
+last available space in the key's group was just used. It will return
+``EFD_UPDATE_FAILED (2)`` when the insertion or update has failed (either it
+failed to find a suitable perfect hash or the group was full). The function
+will return ``EFD_UPDATE_NO_CHANGE (3)`` if there is no change to the EFD
+table (i.e, same value already exists).
+
+EFD Lookup
+~~~~~~~~~~
+
+To lookup a certain key in an EFD table, the function ``rte_efd_lookup()``
+is used to return the value associated with single key.
+As previously mentioned, if the key has been inserted, the correct value
+inserted is returned, if the key has not been inserted before,
+a ‘random’ value (based on hashing of the key) is returned.
+For better performance and to decrease the overhead of
+function calls per key, it is always recommended to use a bulk lookup
+function (simultaneous lookup of multiple keys) instead of a single key
+lookup function. ``rte_efd_lookup_bulk()`` is the bulk lookup function,
+that looks up num_keys simultaneously stored in the key_list and the
+corresponding return values will be returned in the value_list.
+
+EFD Delete
+~~~~~~~~~~
+
+To delete a certain key in an EFD table, the function
+``rte_efd_delete()`` can be used. The function returns zero upon success
+when the key has been found and deleted. Socket_id is the parameter to
+use to lookup the existing value, which is ideally the caller's socket id.
+The previous value associated with this key will be returned
+in the prev_value argument.
+
+.. _Efd_internals:
+
+Library Internals
+-----------------
+
+This section provides the brief high-level idea and an overview
+of the library internals to accompany the RFC. The intent of this
+section is to explain to readers the high-level implementation of
+insert, lookup and group rebalancing in the EFD library.
+
+Insert Function Internals
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As previously mentioned the EFD divides the whole set of keys into
+groups of a manageable size (e.g. 28 keys) and then searches for the
+perfect hash that satisfies the intended target value for each key. EFD
+stores two version of the <key,value> table:
+
+- Offline Version (in memory): Only used for the insertion/update
+ operation, which is less frequent than the lookup operation. In the
+ offline version the exact keys for each group is stored. When a new
+ key is added, the hash function is updated that will satisfy the
+ value for the new key together with the all old keys already inserted
+ in this group.
+
+- Online Version (in cache): Used for the frequent lookup operation. In
+ the online version, as previously mentioned, the keys are not stored
+ but rather only the hash index for each group.
+
+.. _figure_efd7:
+
+.. figure:: img/efd_i7.*
+
+ Group Assignment
+
+:numref:`figure_efd7` depicts the group assignment for 7 flow keys as an example.
+Given a flow key, a hash function (in our implementation CRC hash) is
+used to get the group id. As shown in the figure, the groups can be
+unbalanced. (We highlight group rebalancing further below).
+
+.. _figure_efd8:
+
+.. figure:: img/efd_i8.*
+
+ Perfect Hash Search - Assigned Keys & Target Value
+
+Focusing on one group that has four keys, :numref:`figure_efd8` depicts the search
+algorithm to find the perfect hash function. Assuming that the target
+value bit for the keys is as shown in the figure, then the online EFD
+table will store a 16 bit hash index and 16 bit lookup table per group
+per value bit.
+
+.. _figure_efd9:
+
+.. figure:: img/efd_i9.*
+
+ Perfect Hash Search - Satisfy Target Values
+
+For a given keyX, a hash function ``(h(keyX, seed1) + index * h(keyX, seed2))``
+is used to point to certain bit index in the 16bit lookup_table value,
+as shown in :numref:`figure_efd9`.
+The insert function will brute force search for all possible values for the
+hash index until a non conflicting lookup_table is found.
+
+.. _figure_efd10:
+
+.. figure:: img/efd_i10.*
+
+ Finding Hash Index for Conflict Free lookup_table
+
+For example, since both key3 and key7 have a target bit value of 1, it
+is okay if the hash function of both keys point to the same bit in the
+lookup table. A conflict will occur if a hash index is used that maps
+both Key4 and Key7 to the same index in the lookup_table,
+as shown in :numref:`figure_efd10`, since their target value bit are not the same.
+Once a hash index is found that produces a lookup_table with no
+contradictions, this index is stored for this group. This procedure is
+repeated for each bit of target value.
+
+Lookup Function Internals
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The design principle of EFD is that lookups are much more frequent than
+inserts, and hence, EFD's design optimizes for the lookups which are
+faster and much simpler than the slower insert procedure (inserts are
+slow, because of perfect hash search as previously discussed).
+
+.. _figure_efd11:
+
+.. figure:: img/efd_i11.*
+
+ EFD Lookup Operation
+
+:numref:`figure_efd11` depicts the lookup operation for EFD. Given an input key,
+the group id is computed (using CRC hash) and then the hash index for this
+group is retrieved from the EFD table. Using the retrieved hash index,
+the hash function ``h(key, seed1) + index *h(key, seed2)`` is used which will
+result in an index in the lookup_table, the bit corresponding to this
+index will be the target value bit. This procedure is repeated for each
+bit of the target value.
+
+Group Rebalancing Function Internals
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When discussing EFD inserts and lookups, the discussion is simplified by
+assuming that a group id is simply a result of hash function. However,
+since hashing in general is not perfect and will not always produce a
+uniform output, this simplified assumption will lead to unbalanced
+groups, i.e., some group will have more keys than other groups.
+Typically, and to minimize insert time with an increasing number of keys,
+it is preferable that all groups will have a balanced number of keys, so
+the brute force search for the perfect hash terminates with a valid hash
+index. In order to achieve this target, groups are rebalanced during
+runtime inserts, and keys are moved around from a busy group to a less
+crowded group as the more keys are inserted.
+
+.. _figure_efd12:
+
+.. figure:: img/efd_i12.*
+
+ Runtime Group Rebalancing
+
+:numref:`figure_efd12` depicts the high level idea of group rebalancing, given an
+input key the hash result is split into two parts a chunk id and 8-bit
+bin id. A chunk contains 64 different groups and 256 bins (i.e. for any
+given bin it can map to 4 distinct groups). When a key is inserted, the
+bin id is computed, for example in :numref:`figure_efd12` bin_id=2,
+and since each bin can be mapped to one of four different groups (2 bit storage),
+the four possible mappings are evaluated and the one that will result in a
+balanced key distribution across these four is selected the mapping result
+is stored in these two bits.
+
+
+.. _Efd_references:
+
+References
+-----------
+
+1- EFD is based on collaborative research work between Intel and
+Carnegie Mellon University (CMU), interested readers can refer to the paper
+“Scaling Up Clustered Network Appliances with ScaleBricks;” Dong Zhou et al.
+at SIGCOMM 2015 (`http://conferences.sigcomm.org/sigcomm/2015/pdf/papers/p241.pdf`)
+for more information.
diff --git a/doc/guides/prog_guide/img/efd_i1.svg b/doc/guides/prog_guide/img/efd_i1.svg
new file mode 100644
index 0000000..7f8fcb3
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i1.svg
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i1.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="3.25609in" height="3.375in"
+ viewBox="0 0 234.439 243" xml:space="preserve" color-interpolation-filters="sRGB" class="st10">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st5 {marker-end:url(#mrkr5-12);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:6}
+ .st6 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.70422535211268}
+ .st7 {stroke:#5b9bd5;stroke-dasharray:2.25,4.5;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.25}
+ .st8 {marker-end:url(#mrkr5-39);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st9 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st10 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-12" class="st6" v:arrowType="5" v:arrowSize="2" v:setback="2.485" refX="-2.485" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-1.42,-1.42) "/>
+ </marker>
+ <marker id="mrkr5-39" class="st9" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(77.718,-113.348)">
+ <title>Square</title>
+ <desc>LB</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="18" cy="225" width="36" height="36"/>
+ <g id="shadow2-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="207" width="36" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="207" width="36" height="36" class="st3"/>
+ <text x="13.18" y="228" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>LB</text> </g>
+ <g id="shape3-7" v:mID="3" v:groupContext="shape" transform="translate(37.0513,-131.348)">
+ <title>Sheet.3</title>
+ <path d="M0 243 L25.76 243" class="st5"/>
+ </g>
+ <g id="shape4-13" v:mID="4" v:groupContext="shape" transform="translate(167.718,-178.598)">
+ <title>Square.4</title>
+ <desc>Target 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="220.5" width="45" height="45"/>
+ <g id="shadow4-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="198" width="45" height="45" class="st2"/>
+ </g>
+ <rect x="0" y="198" width="45" height="45" class="st3"/>
+ <text x="5.74" y="223.5" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target 1</text> </g>
+ <g id="shape5-19" v:mID="5" v:groupContext="shape" transform="translate(167.718,-121.005)">
+ <title>Square.5</title>
+ <desc>Target 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="220.5" width="45" height="45"/>
+ <g id="shadow5-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="198" width="45" height="45" class="st2"/>
+ </g>
+ <rect x="0" y="198" width="45" height="45" class="st3"/>
+ <text x="5.74" y="223.5" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target 2</text> </g>
+ <g id="shape7-25" v:mID="7" v:groupContext="shape" transform="translate(167.718,-23.3478)">
+ <title>Square.7</title>
+ <desc>Target N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="220.5" width="45" height="45"/>
+ <g id="shadow7-26" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="198" width="45" height="45" class="st2"/>
+ </g>
+ <rect x="0" y="198" width="45" height="45" class="st3"/>
+ <text x="5.05" y="223.5" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target N</text> </g>
+ <g id="shape8-31" v:mID="8" v:groupContext="shape" transform="translate(433.218,132.402) rotate(90)">
+ <title>Sheet.8</title>
+ <path d="M0 243 L34.59 243" class="st7"/>
+ </g>
+ <g id="shape9-34" v:mID="9" v:groupContext="shape" transform="translate(-78.4279,-37.1059) rotate(-52.2532)">
+ <title>Sheet.9</title>
+ <path d="M0 243 L81.18 243" class="st8"/>
+ </g>
+ <g id="shape11-40" v:mID="11" v:groupContext="shape" transform="translate(60.3469,-125.414) rotate(-12.6875)">
+ <title>Sheet.11</title>
+ <path d="M0 243 L48.32 243" class="st8"/>
+ </g>
+ <g id="shape12-45" v:mID="12" v:groupContext="shape" transform="translate(319.172,-18.1081) rotate(57.7244)">
+ <title>Sheet.12</title>
+ <path d="M0 243 L94.09 243" class="st8"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i10.svg b/doc/guides/prog_guide/img/efd_i10.svg
new file mode 100644
index 0000000..d26ec61
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i10.svg
@@ -0,0 +1,384 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i11.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="9.76715in" height="2.82917in"
+ viewBox="0 0 703.234 203.701" xml:space="preserve" color-interpolation-filters="sRGB" class="st15">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st4 {fill:#000000;font-family:Arial;font-size:0.998566em}
+ .st5 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st6 {fill:#000000;font-family:Arial;font-size:0.918686em;font-style:italic}
+ .st7 {fill:#000000;font-family:Arial;font-size:0.918686em}
+ .st8 {fill:#7e8d96;font-family:Arial;font-size:0.998566em;font-weight:bold}
+ .st9 {fill:#00b050;font-family:Arial;font-size:0.998566em;font-weight:bold}
+ .st10 {fill:#ff0000;font-family:Arial;font-size:0.998566em;font-weight:bold}
+ .st11 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st12 {fill:#ffffff;font-family:Arial;font-size:1.49785em}
+ .st13 {stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st14 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st15 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(19.0195,-93.4328)">
+ <title>Sheet.3</title>
+ <path d="M0 182.93 C0 180.64 1.87 178.78 4.16 178.78 L109.18 178.78 C111.47 178.78 113.33 180.64 113.33 182.93 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.16 203.7 C1.87 203.7 0 201.84 0 199.55 L0 182.93 Z"
+ class="st1"/>
+ </g>
+ <g id="shape4-3" v:mID="4" v:groupContext="shape" transform="translate(19.0195,-93.4328)">
+ <title>Sheet.4</title>
+ <path d="M0 182.93 C0 180.64 1.87 178.78 4.16 178.78 L109.18 178.78 C111.47 178.78 113.33 180.64 113.33 182.93 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.16 203.7 C1.87 203.7 0 201.84 0 199.55 L0 182.93 Z"
+ class="st2"/>
+ </g>
+ <g id="shape5-5" v:mID="5" v:groupContext="shape" transform="translate(19.0195,-96.9057)">
+ <title>Sheet.5</title>
+ <desc>Key1: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.0575" cy="196.509" width="116.12" height="14.3829"/>
+ <path d="M116.11 189.32 L0 189.32 L0 203.7 L116.11 203.7 L116.11 189.32" class="st3"/>
+ <text x="15.59" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1: Value = 0</text> </g>
+ <g id="shape6-9" v:mID="6" v:groupContext="shape" transform="translate(19.0195,-68.6284)">
+ <title>Sheet.6</title>
+ <path d="M0 182.93 C0 180.64 1.87 178.78 4.16 178.78 L109.18 178.78 C111.47 178.78 113.33 180.64 113.33 182.93 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.16 203.7 C1.87 203.7 0 201.84 0 199.55 L0 182.93 Z"
+ class="st1"/>
+ </g>
+ <g id="shape7-11" v:mID="7" v:groupContext="shape" transform="translate(19.0195,-68.6284)">
+ <title>Sheet.7</title>
+ <path d="M0 182.93 C0 180.64 1.87 178.78 4.16 178.78 L109.18 178.78 C111.47 178.78 113.33 180.64 113.33 182.93 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.16 203.7 C1.87 203.7 0 201.84 0 199.55 L0 182.93 Z"
+ class="st2"/>
+ </g>
+ <g id="shape8-13" v:mID="8" v:groupContext="shape" transform="translate(19.0195,-72.0832)">
+ <title>Sheet.8</title>
+ <desc>Key3: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.0575" cy="196.509" width="116.12" height="14.3829"/>
+ <path d="M116.11 189.32 L0 189.32 L0 203.7 L116.11 203.7 L116.11 189.32" class="st3"/>
+ <text x="15.59" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3: Value = 1</text> </g>
+ <g id="shape9-17" v:mID="9" v:groupContext="shape" transform="translate(19.0195,-43.5843)">
+ <title>Sheet.9</title>
+ <path d="M0 182.84 C-0 180.53 1.88 178.66 4.19 178.66 L109.15 178.66 C111.46 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.53 C113.33 201.84 111.46 203.7 109.15 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.53 L0 182.84 Z"
+ class="st1"/>
+ </g>
+ <g id="shape10-19" v:mID="10" v:groupContext="shape" transform="translate(19.0195,-43.5843)">
+ <title>Sheet.10</title>
+ <path d="M0 182.84 C-0 180.53 1.88 178.66 4.19 178.66 L109.15 178.66 C111.46 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.53 C113.33 201.84 111.46 203.7 109.15 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.53 L0 182.84 Z"
+ class="st2"/>
+ </g>
+ <g id="shape11-21" v:mID="11" v:groupContext="shape" transform="translate(19.0195,-47.1109)">
+ <title>Sheet.11</title>
+ <desc>Key4: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.0575" cy="196.509" width="116.12" height="14.3829"/>
+ <path d="M116.11 189.32 L0 189.32 L0 203.7 L116.11 203.7 L116.11 189.32" class="st3"/>
+ <text x="15.59" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4: Value = 0</text> </g>
+ <g id="shape12-25" v:mID="12" v:groupContext="shape" transform="translate(19.0195,-19.0195)">
+ <title>Sheet.12</title>
+ <path d="M0 182.84 C-0 180.53 1.88 178.66 4.19 178.66 L109.15 178.66 C111.46 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.53 C113.33 201.84 111.46 203.7 109.15 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.53 L0 182.84 Z"
+ class="st1"/>
+ </g>
+ <g id="shape13-27" v:mID="13" v:groupContext="shape" transform="translate(19.0195,-19.0195)">
+ <title>Sheet.13</title>
+ <path d="M0 182.84 C-0 180.53 1.88 178.66 4.19 178.66 L109.15 178.66 C111.46 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.53 C113.33 201.84 111.46 203.7 109.15 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.53 L0 182.84 Z"
+ class="st2"/>
+ </g>
+ <g id="shape14-29" v:mID="14" v:groupContext="shape" transform="translate(19.0195,-22.5475)">
+ <title>Sheet.14</title>
+ <desc>Key7: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.0575" cy="196.509" width="116.12" height="14.3829"/>
+ <path d="M116.11 189.32 L0 189.32 L0 203.7 L116.11 203.7 L116.11 189.32" class="st3"/>
+ <text x="15.59" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7: Value = 1</text> </g>
+ <g id="shape15-33" v:mID="15" v:groupContext="shape" transform="translate(141.656,-45.5615)">
+ <title>Sheet.15</title>
+ <path d="M0 169.01 L22.75 169.01 L22.75 157.45 L45.5 180.57 L22.75 203.7 L22.75 192.14 L0 192.14 L0 169.01 Z"
+ class="st5"/>
+ </g>
+ <g id="shape16-35" v:mID="16" v:groupContext="shape" transform="translate(193.22,-56.0464)">
+ <title>Sheet.16</title>
+ <path d="M0 182.84 C0 180.53 1.88 178.66 4.19 178.66 L96.55 178.66 C98.87 178.66 100.73 180.53 100.73 182.84 L100.73
+ 199.54 C100.73 201.84 98.87 203.7 96.55 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.54 L0 182.84 Z"
+ class="st1"/>
+ </g>
+ <g id="shape17-37" v:mID="17" v:groupContext="shape" transform="translate(193.22,-56.0464)">
+ <title>Sheet.17</title>
+ <path d="M0 182.84 C0 180.53 1.88 178.66 4.19 178.66 L96.55 178.66 C98.87 178.66 100.73 180.53 100.73 182.84 L100.73
+ 199.54 C100.73 201.84 98.87 203.7 96.55 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.54 L0 182.84 Z"
+ class="st2"/>
+ </g>
+ <g id="shape18-39" v:mID="18" v:groupContext="shape" transform="translate(228.157,-66.9545)">
+ <title>Sheet.18</title>
+ <desc>F</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.63538" cy="197.084" width="11.28" height="13.2327"/>
+ <path d="M11.27 190.47 L0 190.47 L0 203.7 L11.27 203.7 L11.27 190.47" class="st3"/>
+ <text x="2.27" y="200.39" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>F</text> </g>
+ <g id="shape19-43" v:mID="19" v:groupContext="shape" transform="translate(234.88,-66.9545)">
+ <title>Sheet.19</title>
+ <desc>(key,</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.261" cy="197.084" width="34.53" height="13.2327"/>
+ <path d="M34.52 190.47 L0 190.47 L0 203.7 L34.52 203.7 L34.52 190.47" class="st3"/>
+ <text x="5.32" y="200.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(key, </text> </g>
+ <g id="shape20-47" v:mID="20" v:groupContext="shape" transform="translate(198.215,-53.7734)">
+ <title>Sheet.20</title>
+ <desc>hash_index =</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="41.4128" cy="197.084" width="82.83" height="13.2327"/>
+ <path d="M82.83 190.47 L0 190.47 L0 203.7 L82.83 203.7 L82.83 190.47" class="st3"/>
+ <text x="8.47" y="200.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash_index = </text> </g>
+ <g id="shape21-51" v:mID="21" v:groupContext="shape" transform="translate(274.858,-53.7734)">
+ <title>Sheet.21</title>
+ <desc>i)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.28241" cy="197.084" width="10.57" height="13.2327"/>
+ <path d="M10.56 190.47 L0 190.47 L0 203.7 L10.56 203.7 L10.56 190.47" class="st3"/>
+ <text x="2.22" y="200.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>i)</text> </g>
+ <g id="shape22-55" v:mID="22" v:groupContext="shape" transform="translate(351.453,-93.7923)">
+ <title>Sheet.22</title>
+ <path d="M0 182.84 C0 180.53 1.88 178.66 4.19 178.66 L109.16 178.66 C111.47 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.54 C113.33 201.84 111.47 203.7 109.16 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.54 L0 182.84 Z"
+ class="st1"/>
+ </g>
+ <g id="shape23-57" v:mID="23" v:groupContext="shape" transform="translate(351.453,-93.7923)">
+ <title>Sheet.23</title>
+ <path d="M0 182.84 C0 180.53 1.88 178.66 4.19 178.66 L109.16 178.66 C111.47 178.66 113.33 180.53 113.33 182.84 L113.33
+ 199.54 C113.33 201.84 111.47 203.7 109.16 203.7 L4.19 203.7 C1.88 203.7 0 201.84 0 199.54 L0 182.84 Z"
+ class="st2"/>
+ </g>
+ <g id="shape24-59" v:mID="24" v:groupContext="shape" transform="translate(355.798,-97.3147)">
+ <title>Sheet.24</title>
+ <desc>Key1: Position 4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="51.7083" cy="196.509" width="103.42" height="14.3829"/>
+ <path d="M103.42 189.32 L0 189.32 L0 203.7 L103.42 203.7 L103.42 189.32" class="st3"/>
+ <text x="8.41" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1: Position 4</text> </g>
+ <g id="shape25-63" v:mID="25" v:groupContext="shape" transform="translate(351.453,-68.9879)">
+ <title>Sheet.25</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape26-65" v:mID="26" v:groupContext="shape" transform="translate(351.453,-68.9879)">
+ <title>Sheet.26</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape27-67" v:mID="27" v:groupContext="shape" transform="translate(355.798,-72.4921)">
+ <title>Sheet.27</title>
+ <desc>Key3: Position 6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="51.7083" cy="196.509" width="103.42" height="14.3829"/>
+ <path d="M103.42 189.32 L0 189.32 L0 203.7 L103.42 203.7 L103.42 189.32" class="st3"/>
+ <text x="8.41" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3: Position 6</text> </g>
+ <g id="shape28-71" v:mID="28" v:groupContext="shape" transform="translate(351.453,-44.0636)">
+ <title>Sheet.28</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape29-73" v:mID="29" v:groupContext="shape" transform="translate(351.453,-44.0636)">
+ <title>Sheet.29</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape30-75" v:mID="30" v:groupContext="shape" transform="translate(351.215,-47.5198)">
+ <title>Sheet.30</title>
+ <desc>Key4: Position 14</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="55.5403" cy="196.509" width="111.09" height="14.3829"/>
+ <path d="M111.08 189.32 L0 189.32 L0 203.7 L111.08 203.7 L111.08 189.32" class="st3"/>
+ <text x="8.91" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4: Position 14</text> </g>
+ <g id="shape31-79" v:mID="31" v:groupContext="shape" transform="translate(351.453,-19.4988)">
+ <title>Sheet.31</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape32-81" v:mID="32" v:groupContext="shape" transform="translate(351.453,-19.4988)">
+ <title>Sheet.32</title>
+ <path d="M0 182.94 C0 180.65 1.88 178.78 4.17 178.78 L109.18 178.78 C111.47 178.78 113.33 180.65 113.33 182.94 L113.33
+ 199.55 C113.33 201.84 111.47 203.7 109.18 203.7 L4.17 203.7 C1.88 203.7 0 201.84 0 199.55 L0 182.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape33-83" v:mID="33" v:groupContext="shape" transform="translate(351.215,-22.9565)">
+ <title>Sheet.33</title>
+ <desc>Key7: Position 14</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="55.5403" cy="196.509" width="111.09" height="14.3829"/>
+ <path d="M111.08 189.32 L0 189.32 L0 203.7 L111.08 203.7 L111.08 189.32" class="st3"/>
+ <text x="8.91" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7: Position 14</text> </g>
+ <g id="shape34-87" v:mID="34" v:groupContext="shape" transform="translate(299.89,-46.0408)">
+ <title>Sheet.34</title>
+ <path d="M0 169.01 L22.75 169.01 L22.75 157.45 L45.5 180.57 L22.75 203.7 L22.75 192.14 L0 192.14 L0 169.01 Z"
+ class="st5"/>
+ </g>
+ <g id="shape35-89" v:mID="35" v:groupContext="shape" transform="translate(528.896,-117.518)">
+ <title>Sheet.35</title>
+ <path d="M0 182.94 C0 180.66 1.89 178.78 4.17 178.78 L137.64 178.78 C139.92 178.78 141.79 180.66 141.79 182.94 L141.79
+ 199.57 C141.79 201.84 139.92 203.7 137.64 203.7 L4.17 203.7 C1.89 203.7 0 201.84 0 199.57 L0 182.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape36-91" v:mID="36" v:groupContext="shape" transform="translate(528.896,-117.518)">
+ <title>Sheet.36</title>
+ <path d="M0 182.94 C0 180.66 1.89 178.78 4.17 178.78 L137.64 178.78 C139.92 178.78 141.79 180.66 141.79 182.94 L141.79
+ 199.57 C141.79 201.84 139.92 203.7 137.64 203.7 L4.17 203.7 C1.89 203.7 0 201.84 0 199.57 L0 182.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape37-93" v:mID="37" v:groupContext="shape" transform="translate(530.056,-121.017)">
+ <title>Sheet.37</title>
+ <desc>0000</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="19.1585" cy="196.509" width="38.32" height="14.3829"/>
+ <path d="M38.32 189.32 L0 189.32 L0 203.7 L38.32 203.7 L38.32 189.32" class="st3"/>
+ <text x="5.83" y="200.1" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0000 </text> </g>
+ <g id="shape38-97" v:mID="38" v:groupContext="shape" transform="translate(567.215,-121.017)">
+ <title>Sheet.38</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.7483" cy="196.509" width="11.5" height="14.3829"/>
+ <path d="M11.5 189.32 L0 189.32 L0 203.7 L11.5 203.7 L11.5 189.32" class="st3"/>
+ <text x="2.42" y="200.1" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape39-101" v:mID="39" v:groupContext="shape" transform="translate(576.215,-121.017)">
+ <title>Sheet.39</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.7483" cy="196.509" width="11.5" height="14.3829"/>
+ <path d="M11.5 189.32 L0 189.32 L0 203.7 L11.5 203.7 L11.5 189.32" class="st3"/>
+ <text x="2.42" y="200.1" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape40-105" v:mID="40" v:groupContext="shape" transform="translate(584.486,-121.017)">
+ <title>Sheet.40</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.7483" cy="196.509" width="11.5" height="14.3829"/>
+ <path d="M11.5 189.32 L0 189.32 L0 203.7 L11.5 203.7 L11.5 189.32" class="st3"/>
+ <text x="2.42" y="200.1" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape41-109" v:mID="41" v:groupContext="shape" transform="translate(588.646,-121.017)">
+ <title>Sheet.41</title>
+ <desc>0 0000 00</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="32.5687" cy="196.509" width="65.14" height="14.3829"/>
+ <path d="M65.14 189.32 L0 189.32 L0 203.7 L65.14 203.7 L65.14 189.32" class="st3"/>
+ <text x="5.91" y="200.1" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0 0000 00</text> </g>
+ <g id="shape42-113" v:mID="42" v:groupContext="shape" transform="translate(644.965,-121.017)">
+ <title>Sheet.42</title>
+ <desc>?</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="6.12511" cy="196.509" width="12.26" height="14.3829"/>
+ <path d="M12.25 189.32 L0 189.32 L0 203.7 L12.25 203.7 L12.25 189.32" class="st3"/>
+ <text x="2.47" y="200.1" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>?</text> </g>
+ <g id="shape43-117" v:mID="43" v:groupContext="shape" transform="translate(654.718,-121.017)">
+ <title>Sheet.43</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="5.7483" cy="196.509" width="11.5" height="14.3829"/>
+ <path d="M11.5 189.32 L0 189.32 L0 203.7 L11.5 203.7 L11.5 189.32" class="st3"/>
+ <text x="2.42" y="200.1" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape44-121" v:mID="44" v:groupContext="shape" transform="translate(464.786,-105.296)">
+ <title>Sheet.44</title>
+ <path d="M0 203.7 L108.29 203.7 C108.86 203.7 109.31 203.22 109.31 202.68 L109.31 189.5 L107.27 189.5 L107.27 202.68
+ L108.29 201.66 L0 201.66 L0 203.7 ZM111.35 190.52 L108.29 184.41 L105.23 190.55 L111.35 190.52 Z"
+ class="st11"/>
+ </g>
+ <g id="shape45-123" v:mID="45" v:groupContext="shape" transform="translate(464.786,-80.4315)">
+ <title>Sheet.45</title>
+ <path d="M0 203.7 L123.63 203.7 C124.2 203.7 124.65 203.25 124.65 202.68 L124.65 164.28 L122.61 164.28 L122.61 202.68
+ L123.63 201.66 L0 201.66 L0 203.7 ZM126.69 165.3 L123.6 159.18 L120.57 165.33 L126.69 165.3 Z"
+ class="st11"/>
+ </g>
+ <g id="shape46-125" v:mID="46" v:groupContext="shape" transform="translate(464.786,-55.4772)">
+ <title>Sheet.46</title>
+ <path d="M0 203.7 L186.48 203.7 C186.75 203.7 186.99 203.61 187.2 203.4 C187.38 203.22 187.5 202.95 187.5 202.68 L187.41
+ 139.32 L185.37 139.32 L185.46 202.68 L186.48 201.66 L0 201.66 L0 203.7 ZM189.51 140.07 L185.94 134.23 L183.41
+ 140.61 L189.51 140.07 Z" class="st11"/>
+ </g>
+ <g id="shape47-127" v:mID="47" v:groupContext="shape" transform="translate(464.786,-30.9125)">
+ <title>Sheet.47</title>
+ <path d="M0 203.7 L186.48 203.7 C186.75 203.7 186.99 203.61 187.2 203.4 C187.38 203.22 187.5 202.95 187.5 202.68 L187.41
+ 114.76 L185.37 114.76 L185.46 202.68 L186.48 201.66 L0 201.66 L0 203.7 ZM189.51 115.51 L185.94 109.67 L183.41
+ 116.05 L189.51 115.51 Z" class="st11"/>
+ </g>
+ <g id="shape48-129" v:mID="48" v:groupContext="shape" transform="translate(442.996,-151.106)">
+ <title>Sheet.48</title>
+ <path d="M0 179.56 C0 176.89 2.19 174.7 4.86 174.7 L70.8 174.7 C73.47 174.7 75.64 176.89 75.64 179.56 L75.64 198.88 C75.64
+ 201.54 73.47 203.7 70.8 203.7 L4.86 203.7 C2.19 203.7 0 201.54 0 198.88 L0 179.56 Z" class="st5"/>
+ </g>
+ <g id="shape49-131" v:mID="49" v:groupContext="shape" transform="translate(443.529,-155.018)">
+ <title>Sheet.49</title>
+ <desc>Values</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="37.8175" cy="192.914" width="75.64" height="21.5726"/>
+ <path d="M75.64 182.13 L0 182.13 L0 203.7 L75.64 203.7 L75.64 182.13" class="st3"/>
+ <text x="10.34" y="198.31" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Values</text> </g>
+ <g id="shape50-135" v:mID="50" v:groupContext="shape" transform="translate(102.458,-122.192)">
+ <title>Sheet.50</title>
+ <path d="M0 203.7 C-0 199.21 0.62 195.55 1.37 195.55 L11.67 195.55 C12.42 195.55 13.03 191.9 13.03 187.4 C13.03 191.9
+ 13.64 195.55 14.39 195.55 L24.69 195.55 C25.44 195.55 26.05 199.21 26.05 203.7" class="st13"/>
+ </g>
+ <g id="shape51-138" v:mID="51" v:groupContext="shape" transform="translate(115.454,-137.5)">
+ <title>Sheet.51</title>
+ <path d="M0.2 203.7 L322.66 174.12 L322.48 172.1 L0 201.68 L0.2 203.7 L0.2 203.7 ZM321.84 176.24 L327.66 172.64 L321.28
+ 170.16 L321.84 176.24 L321.84 176.24 Z" class="st14"/>
+ </g>
+ <g id="shape52-140" v:mID="52" v:groupContext="shape" transform="translate(518.211,-142.473)">
+ <title>Sheet.52</title>
+ <path d="M0.99 176.74 L44.78 200.38 L43.82 202.17 L0 178.51 L0.99 176.74 L0.99 176.74 ZM44.87 198.1 L48.8 203.7 L41.96
+ 203.46 L44.87 198.1 L44.87 198.1 Z" class="st11"/>
+ </g>
+ <g id="shape53-142" v:mID="53" v:groupContext="shape" transform="translate(518.331,-141.963)">
+ <title>Sheet.53</title>
+ <path d="M0.75 176.17 L60.09 200.32 L59.34 202.2 L0 178.06 L0.75 176.17 L0.75 176.17 ZM59.91 198.04 L64.44 203.19 L57.6
+ 203.7 L59.91 198.04 L59.91 198.04 Z" class="st11"/>
+ </g>
+ <g id="shape54-144" v:mID="54" v:groupContext="shape" transform="translate(576.558,-153.706)">
+ <title>Sheet.54</title>
+ <path d="M0 177.04 C0 174.1 2.4 171.71 5.34 171.71 L101.51 171.71 C104.48 171.71 106.85 174.1 106.85 177.04 L106.85 198.37
+ C106.85 201.33 104.48 203.7 101.51 203.7 L5.34 203.7 C2.4 203.7 0 201.33 0 198.37 L0 177.04 Z" class="st1"/>
+ </g>
+ <g id="shape55-146" v:mID="55" v:groupContext="shape" transform="translate(577.365,-151.611)">
+ <title>Sheet.55</title>
+ <path d="M0 177.04 C0 174.1 2.4 171.71 5.34 171.71 L101.51 171.71 C104.48 171.71 106.85 174.1 106.85 177.04 L106.85 198.37
+ C106.85 201.33 104.48 203.7 101.51 203.7 L5.34 203.7 C2.4 203.7 0 201.33 0 198.37 L0 177.04 Z" class="st2"/>
+ </g>
+ <g id="shape56-148" v:mID="56" v:groupContext="shape" transform="translate(593.952,-167.894)">
+ <title>Sheet.56</title>
+ <desc>Lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.2942" cy="196.509" width="86.59" height="14.3829"/>
+ <path d="M86.59 189.32 L0 189.32 L0 203.7 L86.59 203.7 L86.59 189.32" class="st3"/>
+ <text x="7.31" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Lookup_table</text> </g>
+ <g id="shape57-152" v:mID="57" v:groupContext="shape" transform="translate(608.239,-153.515)">
+ <title>Sheet.57</title>
+ <desc>(16 bits)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="26.8054" cy="196.509" width="53.62" height="14.3829"/>
+ <path d="M53.61 189.32 L0 189.32 L0 203.7 L53.61 203.7 L53.61 189.32" class="st3"/>
+ <text x="5.16" y="200.1" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(16 bits)</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i11.svg b/doc/guides/prog_guide/img/efd_i11.svg
new file mode 100644
index 0000000..f2cc656
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i11.svg
@@ -0,0 +1,319 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i12.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="10.2783in" height="4.28958in"
+ viewBox="0 0 740.039 308.85" xml:space="preserve" color-interpolation-filters="sRGB" class="st21">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st4 {fill:#000000;font-family:Arial;font-size:0.998566em}
+ .st5 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st6 {stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st7 {stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st8 {fill:#000000;font-family:Arial;font-size:0.918686em;font-weight:bold}
+ .st9 {fill:#00b050;font-size:1em}
+ .st10 {fill:#c00000;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st11 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.00749637}
+ .st12 {fill:#ffffff;font-family:Arial;font-size:1.16833em}
+ .st13 {fill:#2e75b5;stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st14 {fill:#ffffff;font-family:Arial;font-size:1.16666em}
+ .st15 {font-size:1em}
+ .st16 {fill:none;stroke:none;stroke-width:0.25}
+ .st17 {fill:#000000;font-family:Calibri;font-size:1.00001em}
+ .st18 {marker-end:url(#mrkr5-121);stroke:#5b9bd5;stroke-dasharray:1.5,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st19 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st20 {fill:#000000;font-family:Calibri;font-size:1.16666em}
+ .st21 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-121" class="st19" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape5-1" v:mID="5" v:groupContext="shape" transform="translate(36.0674,-256.878)">
+ <title>Sheet.5</title>
+ <path d="M0 291.88 C0 290 1.52 288.48 3.41 288.48 L68.51 288.48 C70.4 288.48 71.91 290 71.91 291.88 L71.91 305.46 C71.91
+ 307.33 70.4 308.85 68.51 308.85 L3.41 308.85 C1.52 308.85 0 307.33 0 305.46 L0 291.88 Z" class="st1"/>
+ </g>
+ <g id="shape6-3" v:mID="6" v:groupContext="shape" transform="translate(36.0674,-256.878)">
+ <title>Sheet.6</title>
+ <path d="M0 291.88 C0 290 1.52 288.48 3.41 288.48 L68.51 288.48 C70.4 288.48 71.91 290 71.91 291.88 L71.91 305.46 C71.91
+ 307.33 70.4 308.85 68.51 308.85 L3.41 308.85 C1.52 308.85 0 307.33 0 305.46 L0 291.88 Z" class="st2"/>
+ </g>
+ <g id="shape7-5" v:mID="7" v:groupContext="shape" transform="translate(61.6502,-258.089)">
+ <title>Sheet.7</title>
+ <desc>Key</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="13.7891" cy="301.658" width="27.58" height="14.3829"/>
+ <path d="M27.58 294.47 L0 294.47 L0 308.85 L27.58 308.85 L27.58 294.47" class="st3"/>
+ <text x="3.46" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key</text> </g>
+ <g id="shape8-9" v:mID="8" v:groupContext="shape" transform="translate(51.9748,-236.328)">
+ <title>Sheet.8</title>
+ <path d="M0 298.54 L9.81 298.54 L9.81 288.24 L29.44 288.24 L29.44 298.54 L39.26 298.54 L19.63 308.85 L0 298.54 Z"
+ class="st5"/>
+ </g>
+ <g id="shape9-11" v:mID="9" v:groupContext="shape" transform="translate(36.0674,-215.298)">
+ <title>Sheet.9</title>
+ <path d="M0 291.77 C0 289.89 1.54 288.36 3.42 288.36 L68.49 288.36 C70.38 288.36 71.91 289.89 71.91 291.77 L71.91 305.43
+ C71.91 307.32 70.38 308.85 68.49 308.85 L3.42 308.85 C1.54 308.85 0 307.32 0 305.43 L0 291.77 Z"
+ class="st1"/>
+ </g>
+ <g id="shape10-13" v:mID="10" v:groupContext="shape" transform="translate(36.0674,-215.298)">
+ <title>Sheet.10</title>
+ <path d="M0 291.77 C0 289.89 1.54 288.36 3.42 288.36 L68.49 288.36 C70.38 288.36 71.91 289.89 71.91 291.77 L71.91 305.43
+ C71.91 307.32 70.38 308.85 68.49 308.85 L3.42 308.85 C1.54 308.85 0 307.32 0 305.43 L0 291.77 Z"
+ class="st2"/>
+ </g>
+ <g id="shape11-15" v:mID="11" v:groupContext="shape" transform="translate(58.8889,-216.57)">
+ <title>Sheet.11</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.8573" cy="301.658" width="33.72" height="14.3829"/>
+ <path d="M33.71 294.47 L0 294.47 L0 308.85 L33.71 308.85 L33.71 294.47" class="st3"/>
+ <text x="3.86" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape12-19" v:mID="12" v:groupContext="shape" transform="translate(27.3033,-174.437)">
+ <title>Sheet.12</title>
+ <path d="M0 292.58 C0 290.78 1.46 289.32 3.26 289.32 L87.15 289.32 C88.95 289.32 90.4 290.78 90.4 292.58 L90.4 305.6
+ C90.4 307.4 88.95 308.85 87.15 308.85 L3.26 308.85 C1.46 308.85 0 307.4 0 305.6 L0 292.58 Z" class="st1"/>
+ </g>
+ <g id="shape13-21" v:mID="13" v:groupContext="shape" transform="translate(27.3033,-174.437)">
+ <title>Sheet.13</title>
+ <path d="M0 292.58 C0 290.78 1.46 289.32 3.26 289.32 L87.15 289.32 C88.95 289.32 90.4 290.78 90.4 292.58 L90.4 305.6
+ C90.4 307.4 88.95 308.85 87.15 308.85 L3.26 308.85 C1.46 308.85 0 307.4 0 305.6 L0 292.58 Z" class="st2"/>
+ </g>
+ <g id="shape14-23" v:mID="14" v:groupContext="shape" transform="translate(36.0515,-175.256)">
+ <title>Sheet.14</title>
+ <desc>0x0102ABCD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.6644" cy="301.658" width="87.33" height="14.3829"/>
+ <path d="M87.33 294.47 L0 294.47 L0 308.85 L87.33 308.85 L87.33 294.47" class="st3"/>
+ <text x="7.36" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102ABCD</text> </g>
+ <g id="shape15-27" v:mID="15" v:groupContext="shape" transform="translate(51.9748,-194.029)">
+ <title>Sheet.15</title>
+ <path d="M0 298.48 L9.81 298.48 L9.81 288.12 L29.44 288.12 L29.44 298.48 L39.26 298.48 L19.63 308.85 L0 298.48 Z"
+ class="st5"/>
+ </g>
+ <g id="shape16-29" v:mID="16" v:groupContext="shape" transform="translate(48.9133,-159.818)">
+ <title>Sheet.16</title>
+ <path d="M26.41 296.87 C26.41 300.18 25.97 302.86 25.41 302.86 L14.21 302.86 C13.66 302.86 13.21 305.55 13.21 308.85
+ C13.21 305.55 12.76 302.86 12.21 302.86 L1.01 302.86 C0.45 302.86 0 300.18 0 296.87" class="st6"/>
+ </g>
+ <g id="shape17-32" v:mID="17" v:groupContext="shape" transform="translate(19.0195,-19.0195)">
+ <title>Sheet.17</title>
+ <path d="M0 196.93 L0 308.85 L145.15 308.85 L145.15 196.93 L0 196.93 L0 196.93 Z" class="st1"/>
+ </g>
+ <g id="shape18-34" v:mID="18" v:groupContext="shape" transform="translate(19.0195,-19.0195)">
+ <title>Sheet.18</title>
+ <path d="M0 196.93 L145.15 196.93 L145.15 308.85 L0 308.85 L0 196.93" class="st7"/>
+ </g>
+ <g id="shape19-37" v:mID="19" v:groupContext="shape" transform="translate(28.2638,-70.6655)">
+ <title>Sheet.19</title>
+ <path d="M0 280.69 C0 277.58 2.53 275.06 5.64 275.06 L124.14 275.06 C127.26 275.06 129.78 277.58 129.78 280.69 L129.78
+ 303.22 C129.78 306.33 127.26 308.85 124.14 308.85 L5.64 308.85 C2.53 308.85 0 306.33 0 303.22 L0 280.69
+ Z" class="st1"/>
+ </g>
+ <g id="shape20-39" v:mID="20" v:groupContext="shape" transform="translate(28.2638,-70.6655)">
+ <title>Sheet.20</title>
+ <path d="M0 280.69 C0 277.58 2.53 275.06 5.64 275.06 L124.14 275.06 C127.26 275.06 129.78 277.58 129.78 280.69 L129.78
+ 303.22 C129.78 306.33 127.26 308.85 124.14 308.85 L5.64 308.85 C2.53 308.85 0 306.33 0 303.22 L0 280.69
+ Z" class="st2"/>
+ </g>
+ <g id="shape21-41" v:mID="21" v:groupContext="shape" transform="translate(57.4514,-85.7513)">
+ <title>Sheet.21</title>
+ <desc>hash_index =</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="45.0133" cy="301.658" width="90.03" height="14.3829"/>
+ <path d="M90.03 294.47 L0 294.47 L0 308.85 L90.03 308.85 L90.03 294.47" class="st3"/>
+ <text x="9.2" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash_index = </text> </g>
+ <g id="shape22-45" v:mID="22" v:groupContext="shape" transform="translate(76.3001,-71.3719)">
+ <title>Sheet.22</title>
+ <desc>38123</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.0762" cy="301.658" width="42.16" height="14.3829"/>
+ <path d="M42.15 294.47 L0 294.47 L0 308.85 L42.15 308.85 L42.15 294.47" class="st3"/>
+ <text x="4.42" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>38123</text> </g>
+ <g id="shape23-49" v:mID="23" v:groupContext="shape" transform="translate(28.2638,-27.048)">
+ <title>Sheet.23</title>
+ <path d="M0 280.69 C0 277.59 2.54 275.06 5.64 275.06 L124.14 275.06 C127.26 275.06 129.78 277.59 129.78 280.69 L129.78
+ 303.22 C129.78 306.33 127.26 308.85 124.14 308.85 L5.64 308.85 C2.54 308.85 0 306.33 0 303.22 L0 280.69
+ Z" class="st1"/>
+ </g>
+ <g id="shape24-51" v:mID="24" v:groupContext="shape" transform="translate(28.2638,-27.048)">
+ <title>Sheet.24</title>
+ <path d="M0 280.69 C0 277.59 2.54 275.06 5.64 275.06 L124.14 275.06 C127.26 275.06 129.78 277.59 129.78 280.69 L129.78
+ 303.22 C129.78 306.33 127.26 308.85 124.14 308.85 L5.64 308.85 C2.54 308.85 0 306.33 0 303.22 L0 280.69
+ Z" class="st2"/>
+ </g>
+ <g id="shape25-53" v:mID="25" v:groupContext="shape" transform="translate(54.0924,-41.564)">
+ <title>Sheet.25</title>
+ <desc>lookup_table =</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="46.931" cy="301.658" width="93.87" height="14.3829"/>
+ <path d="M93.86 294.47 L0 294.47 L0 308.85 L93.86 308.85 L93.86 294.47" class="st3"/>
+ <text x="7.79" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table =</text> </g>
+ <g id="shape26-57" v:mID="26" v:groupContext="shape" transform="translate(28.0195,-28.5506)">
+ <title>Sheet.26</title>
+ <desc>0110 1100 0101 1101</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="64.89" cy="302.233" width="129.79" height="13.2327"/>
+ <path d="M129.78 295.62 L0 295.62 L0 308.85 L129.78 308.85 L129.78 295.62" class="st3"/>
+ <text x="11.25" y="305.54" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0110 11<tspan
+ class="st9">0</tspan>0 0101 1101</text> </g>
+ <g id="shape27-62" v:mID="27" v:groupContext="shape" transform="translate(26.2461,-113.863)">
+ <title>Sheet.27</title>
+ <desc>Group ID: 0x0102</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="48.6286" cy="302.881" width="97.26" height="11.9384"/>
+ <path d="M97.26 296.91 L0 296.91 L0 308.85 L97.26 308.85 L97.26 296.91" class="st3"/>
+ <text x="7.73" y="305.86" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group ID: 0x0102</text> </g>
+ <g id="shape28-66" v:mID="28" v:groupContext="shape" transform="translate(42.3703,-135.313)">
+ <title>Sheet.28</title>
+ <path d="M0 298.48 L9.84 298.48 L9.84 288.12 L29.53 288.12 L29.53 298.48 L39.38 298.48 L19.69 308.85 L0 298.48 Z"
+ class="st5"/>
+ </g>
+ <g id="shape29-68" v:mID="29" v:groupContext="shape" transform="translate(117.645,-244.476)">
+ <title>Sheet.29</title>
+ <path d="M0 274.07 L22.75 274.07 L22.75 262.48 L45.5 285.66 L22.75 308.85 L22.75 297.26 L0 297.26 L0 274.07 Z"
+ class="st5"/>
+ </g>
+ <g id="shape30-70" v:mID="30" v:groupContext="shape" transform="translate(169.209,-251.966)">
+ <title>Sheet.30</title>
+ <path d="M0 283.69 C0 280.91 2.27 278.65 5.04 278.65 L111.77 278.65 C114.56 278.65 116.81 280.91 116.81 283.69 L116.81
+ 303.82 C116.81 306.6 114.56 308.85 111.77 308.85 L5.04 308.85 C2.27 308.85 0 306.6 0 303.82 L0 283.69 Z"
+ class="st1"/>
+ </g>
+ <g id="shape31-72" v:mID="31" v:groupContext="shape" transform="translate(169.209,-251.966)">
+ <title>Sheet.31</title>
+ <path d="M0 283.69 C0 280.91 2.27 278.65 5.04 278.65 L111.77 278.65 C114.56 278.65 116.81 280.91 116.81 283.69 L116.81
+ 303.82 C116.81 306.6 114.56 308.85 111.77 308.85 L5.04 308.85 C2.27 308.85 0 306.6 0 303.82 L0 283.69 Z"
+ class="st2"/>
+ </g>
+ <g id="shape35-74" v:mID="35" v:groupContext="shape" transform="translate(291.966,-244.476)">
+ <title>Sheet.35</title>
+ <path d="M0 274.07 L22.69 274.07 L22.69 262.48 L45.38 285.66 L22.69 308.85 L22.69 297.26 L0 297.26 L0 274.07 Z"
+ class="st5"/>
+ </g>
+ <g id="shape36-76" v:mID="36" v:groupContext="shape" transform="translate(343.17,-254.482)">
+ <title>Sheet.36</title>
+ <path d="M0 288.09 C0 285.8 1.88 283.93 4.17 283.93 L109.18 283.93 C111.47 283.93 113.33 285.8 113.33 288.09 L113.33
+ 304.7 C113.33 306.99 111.47 308.85 109.18 308.85 L4.17 308.85 C1.88 308.85 0 306.99 0 304.7 L0 288.09 Z"
+ class="st1"/>
+ </g>
+ <g id="shape37-78" v:mID="37" v:groupContext="shape" transform="translate(343.17,-254.482)">
+ <title>Sheet.37</title>
+ <path d="M0 288.09 C0 285.8 1.88 283.93 4.17 283.93 L109.18 283.93 C111.47 283.93 113.33 285.8 113.33 288.09 L113.33
+ 304.7 C113.33 306.99 111.47 308.85 109.18 308.85 L4.17 308.85 C1.88 308.85 0 306.99 0 304.7 L0 288.09 Z"
+ class="st2"/>
+ </g>
+ <g id="shape38-80" v:mID="38" v:groupContext="shape" transform="translate(368.337,-257.958)">
+ <title>Sheet.38</title>
+ <desc>Position = 6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="38.1131" cy="301.658" width="76.23" height="14.3829"/>
+ <path d="M76.23 294.47 L0 294.47 L0 308.85 L76.23 308.85 L76.23 294.47" class="st3"/>
+ <text x="6.64" y="305.25" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Position = 6</text> </g>
+ <g id="shape39-84" v:mID="39" v:groupContext="shape" transform="translate(158.044,-86.5202)">
+ <title>Sheet.39</title>
+ <path d="M0 308.85 L69.59 308.85 C70.16 308.85 70.62 308.39 70.62 307.83 L70.62 148.5 L68.57 148.5 L68.57 307.83 L69.59
+ 306.81 L0 306.81 L0 308.85 ZM72.66 149.52 L69.59 143.4 L66.53 149.52 L72.66 149.52 Z" class="st11"/>
+ </g>
+ <g id="shape41-86" v:mID="41" v:groupContext="shape" transform="translate(335.112,-199.647)">
+ <title>Sheet.41</title>
+ <desc>Apply the equation</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="71.2648" cy="300.436" width="142.53" height="16.8275"/>
+ <path d="M142.53 292.02 L0 292.02 L0 308.85 L142.53 308.85 L142.53 292.02" class="st3"/>
+ <text x="13.19" y="304.64" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Apply the equation </text> </g>
+ <g id="shape42-90" v:mID="42" v:groupContext="shape" transform="translate(341.115,-182.871)">
+ <title>Sheet.42</title>
+ <desc>to retrieve the bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="64.5256" cy="300.436" width="129.06" height="16.8275"/>
+ <path d="M129.05 292.02 L0 292.02 L0 308.85 L129.05 308.85 L129.05 292.02" class="st3"/>
+ <text x="12.31" y="304.64" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>to retrieve the bit </text> </g>
+ <g id="shape43-94" v:mID="43" v:groupContext="shape" transform="translate(349.999,-166.095)">
+ <title>Sheet.43</title>
+ <desc>position in the</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="54.2285" cy="300.436" width="108.46" height="16.8275"/>
+ <path d="M108.46 292.02 L0 292.02 L0 308.85 L108.46 308.85 L108.46 292.02" class="st3"/>
+ <text x="10.97" y="304.64" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>position in the </text> </g>
+ <g id="shape44-98" v:mID="44" v:groupContext="shape" transform="translate(353.361,-149.319)">
+ <title>Sheet.44</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="47.9619" cy="300.436" width="95.93" height="16.8275"/>
+ <path d="M95.92 292.02 L0 292.02 L0 308.85 L95.92 308.85 L95.92 292.02" class="st3"/>
+ <text x="8.21" y="304.64" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape47-102" v:mID="47" v:groupContext="shape" transform="translate(115.17,255.2) rotate(-90)">
+ <title>1-D word balloon</title>
+ <desc>Retrieve the value “0' from the specified location in the loo...</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ <v:ud v:nameU="Scale" v:val="VT0(1):26"/>
+ <v:ud v:nameU="AntiScale" v:val="VT0(1):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="29.2016" cy="218.85" width="180" height="58.4032" transform="rotate(90)"/>
+ <path d="M0 308.85 L58.4 308.85 L58.4 128.85 L0 128.85 L0 204.67 L-11.87 38.85 L-7.09 233.03 L0 233.03 L0 308.85 Z"
+ class="st13"/>
+ <text x="136.98" y="-41.8" transform="rotate(90)" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Retrieve the value “0' from <tspan
+ x="134.41" dy="1.2em" class="st15">the specified location in the </tspan><tspan x="181.1" dy="1.2em"
+ class="st15">lookup table</tspan></text> </g>
+ <g id="shape48-107" v:mID="48" v:groupContext="shape" transform="translate(169.209,-251.966)">
+ <title>Sheet.48</title>
+ <desc>F(Key, hash_index = 38123</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54.2285" cy="295.35" width="108.46" height="27"/>
+ <rect x="0" y="281.85" width="108.457" height="27" class="st16"/>
+ <text x="5.86" y="291.75" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>F(Key, hash_index = <tspan
+ x="39.02" dy="1.2em" class="st15">38123</tspan></text> </g>
+ <g id="shape49-111" v:mID="49" v:groupContext="shape" transform="translate(553.962,99) rotate(90)">
+ <title>1-D word balloon.49</title>
+ <desc>Apply the equation to retrieve the bit position in the lookup...</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ <v:ud v:nameU="Scale" v:val="VT0(1):26"/>
+ <v:ud v:nameU="AntiScale" v:val="VT0(1):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="29.2016" cy="218.85" width="180" height="58.4032" transform="rotate(-90)"/>
+ <path d="M0 308.85 L58.4 308.85 L58.4 128.85 L0 128.85 L0 204.67 L-51.13 299.85 L0 233.03 L0 308.85 Z" class="st13"/>
+ <text x="-284.62" y="16.6" transform="rotate(-90)" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Apply the equation to <tspan
+ x="-296.67" dy="1.2em" class="st15">retrieve the bit position in </tspan><tspan x="-270.22" dy="1.2em"
+ class="st15">the lookup</tspan>_table</text> </g>
+ <g id="shape50-116" v:mID="50" v:groupContext="shape" transform="translate(640.132,-104.709) rotate(44.1224)">
+ <title>Sheet.50</title>
+ <path d="M0 308.85 L54.13 308.85" class="st18"/>
+ </g>
+ <g id="shape51-122" v:mID="51" v:groupContext="shape" transform="translate(433.02,-122.267)">
+ <title>Sheet.51</title>
+ <desc>(Hash(key,seed1)+38123*hash(key,seed2))%16</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="144" cy="295.35" width="288" height="27"/>
+ <rect x="0" y="281.85" width="288" height="27" class="st2"/>
+ <text x="9.86" y="299.55" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(Hash(key,seed1)+38123*hash(key,seed2))%16</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i12.svg b/doc/guides/prog_guide/img/efd_i12.svg
new file mode 100644
index 0000000..a309d58
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i12.svg
@@ -0,0 +1,1008 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i13.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="10.2932in" height="5.27505in"
+ viewBox="0 0 741.108 379.804" xml:space="preserve" color-interpolation-filters="sRGB" class="st30">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st2 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st3 {fill:#004280;font-family:Arial;font-size:0.828804em}
+ .st4 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st5 {stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st6 {fill:#7030a0;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st7 {fill:#d0d6d9;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st8 {fill:#006fc5;stroke:#006fc5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.00749637}
+ .st9 {fill:#006fc5;stroke:#006fc5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st10 {fill:#d0d6d9;stroke:#d0d6d9;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st11 {fill:#d0d6d9;stroke:#d0d6d9;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st12 {fill:#004280;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st13 {fill:#00b050;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st14 {fill:#ff0000;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st15 {fill:#00b050;stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st16 {fill:#c00000;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st17 {fill:#000000;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st18 {fill:#7f6d00;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st19 {fill:#ff0000;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st20 {fill:#7e8d96;font-family:Arial;font-size:0.828804em;font-weight:bold}
+ .st21 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st22 {fill:#000000;font-family:Arial;font-size:0.998566em}
+ .st23 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st24 {stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st25 {fill:#ffffff;font-family:Arial;font-size:0.998566em}
+ .st26 {fill:#ff6600;stroke:#ff6600;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.00749637}
+ .st27 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st28 {fill:#ff0000;stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st29 {fill:#ffffff;font-family:Arial;font-size:1.49785em}
+ .st30 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(304.703,-329.32)">
+ <title>Sheet.3</title>
+ <path d="M0 379.8 C-0 375.37 0.6 371.78 1.35 371.78 L205.05 371.78 C205.78 371.78 206.38 368.18 206.38 363.75 C206.38
+ 368.18 206.98 371.78 207.73 371.78 L411.43 371.78 C412.15 371.78 412.75 375.37 412.75 379.8" class="st1"/>
+ </g>
+ <g id="shape4-4" v:mID="4" v:groupContext="shape" transform="translate(219.943,-329.32)">
+ <title>Sheet.4</title>
+ <path d="M0 379.8 C0 375.64 0.57 372.25 1.26 372.25 L29.77 372.25 C30.48 372.25 31.03 368.88 31.03 364.71 C31.03 368.88
+ 31.6 372.25 32.29 372.25 L60.81 372.25 C61.51 372.25 62.07 375.64 62.07 379.8" class="st1"/>
+ </g>
+ <g id="shape5-7" v:mID="5" v:groupContext="shape" transform="translate(241.175,-343.9)">
+ <title>Sheet.5</title>
+ <desc>Bins</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="12.7158" cy="373.835" width="25.44" height="11.9384"/>
+ <path d="M25.43 367.87 L0 367.87 L0 379.8 L25.43 379.8 L25.43 367.87" class="st2"/>
+ <text x="3.04" y="376.82" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Bins</text> </g>
+ <g id="shape6-11" v:mID="6" v:groupContext="shape" transform="translate(496.212,-344.504)">
+ <title>Sheet.6</title>
+ <desc>Groups</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="20.3447" cy="373.835" width="40.69" height="11.9384"/>
+ <path d="M40.69 367.87 L0 367.87 L0 379.8 L40.69 379.8 L40.69 367.87" class="st2"/>
+ <text x="4.04" y="376.82" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Groups</text> </g>
+ <g id="shape7-15" v:mID="7" v:groupContext="shape" transform="translate(131.823,-260.299)">
+ <title>Sheet.7</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape8-17" v:mID="8" v:groupContext="shape" transform="translate(131.823,-260.299)">
+ <title>Sheet.8</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape9-20" v:mID="9" v:groupContext="shape" transform="translate(134.706,-310.738)">
+ <title>Sheet.9</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape10-24" v:mID="10" v:groupContext="shape" transform="translate(122.218,-329.32)">
+ <title>Sheet.10</title>
+ <path d="M0 379.8 C-0 375.64 0.57 372.25 1.26 372.25 L29.77 372.25 C30.47 372.25 31.03 368.88 31.03 364.71 C31.03 368.88
+ 31.6 372.25 32.29 372.25 L60.81 372.25 C61.51 372.25 62.07 375.64 62.07 379.8" class="st1"/>
+ </g>
+ <g id="shape11-27" v:mID="11" v:groupContext="shape" transform="translate(137.598,-343.9)">
+ <title>Sheet.11</title>
+ <desc>Chunks</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="20.9813" cy="373.835" width="41.97" height="11.9384"/>
+ <path d="M41.96 367.87 L0 367.87 L0 379.8 L41.96 379.8 L41.96 367.87" class="st2"/>
+ <text x="4.12" y="376.82" class="st3" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Chunks</text> </g>
+ <g id="shape12-31" v:mID="12" v:groupContext="shape" transform="translate(131.823,-195.232)">
+ <title>Sheet.12</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape13-33" v:mID="13" v:groupContext="shape" transform="translate(131.823,-195.232)">
+ <title>Sheet.13</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape14-36" v:mID="14" v:groupContext="shape" transform="translate(134.706,-245.682)">
+ <title>Sheet.14</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape15-40" v:mID="15" v:groupContext="shape" transform="translate(131.823,-130.525)">
+ <title>Sheet.15</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st7"/>
+ </g>
+ <g id="shape16-42" v:mID="16" v:groupContext="shape" transform="translate(131.823,-130.525)">
+ <title>Sheet.16</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape17-45" v:mID="17" v:groupContext="shape" transform="translate(134.706,-180.952)">
+ <title>Sheet.17</title>
+ <desc>…</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.30974" cy="373.835" width="14.62" height="11.9384"/>
+ <path d="M14.62 367.87 L0 367.87 L0 379.8 L14.62 379.8 L14.62 367.87" class="st2"/>
+ <text x="2.34" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>…</text> </g>
+ <g id="shape18-49" v:mID="18" v:groupContext="shape" transform="translate(131.823,-65.4584)">
+ <title>Sheet.18</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape19-51" v:mID="19" v:groupContext="shape" transform="translate(131.823,-65.4584)">
+ <title>Sheet.19</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape20-54" v:mID="20" v:groupContext="shape" transform="translate(130.403,-115.896)">
+ <title>Sheet.20</title>
+ <desc>variable</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="24.7986" cy="373.835" width="49.6" height="11.9384"/>
+ <path d="M49.6 367.87 L0 367.87 L0 379.8 L49.6 379.8 L49.6 367.87" class="st2"/>
+ <text x="6" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>variable </text> </g>
+ <g id="shape21-58" v:mID="21" v:groupContext="shape" transform="translate(130.403,-103.913)">
+ <title>Sheet.21</title>
+ <desc># of</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="13.347" cy="373.835" width="26.7" height="11.9384"/>
+ <path d="M26.69 367.87 L0 367.87 L0 379.8 L26.69 379.8 L26.69 367.87" class="st2"/>
+ <text x="4.51" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/># of </text> </g>
+ <g id="shape22-62" v:mID="22" v:groupContext="shape" transform="translate(130.403,-91.93)">
+ <title>Sheet.22</title>
+ <desc>chunks</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.6122" cy="373.835" width="43.23" height="11.9384"/>
+ <path d="M43.22 367.87 L0 367.87 L0 379.8 L43.22 379.8 L43.22 367.87" class="st2"/>
+ <text x="4.2" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>chunks</text> </g>
+ <g id="shape23-66" v:mID="23" v:groupContext="shape" transform="translate(130.403,-79.9472)">
+ <title>Sheet.23</title>
+ <desc>(power</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.9251" cy="373.835" width="43.86" height="11.9384"/>
+ <path d="M43.85 367.87 L0 367.87 L0 379.8 L43.85 379.8 L43.85 367.87" class="st2"/>
+ <text x="5.62" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(power </text> </g>
+ <g id="shape24-70" v:mID="24" v:groupContext="shape" transform="translate(130.403,-67.9643)">
+ <title>Sheet.24</title>
+ <desc>of 2)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="13.6626" cy="373.835" width="27.33" height="11.9384"/>
+ <path d="M27.33 367.87 L0 367.87 L0 379.8 L27.33 379.8 L27.33 367.87" class="st2"/>
+ <text x="3.17" y="376.82" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>of 2)</text> </g>
+ <g id="shape25-74" v:mID="25" v:groupContext="shape" transform="translate(172.289,-260.838)">
+ <title>Sheet.25</title>
+ <path d="M1.43 379.8 L3.29 375.51 L1.85 374.9 L0 379.19 L1.43 379.8 L1.43 379.8 ZM3.9 374.08 L5.76 369.79 L4.32 369.18
+ L2.47 373.47 L3.9 374.08 L3.9 374.08 ZM6.37 368.36 L8.22 364.07 L6.79 363.45 L4.94 367.75 L6.37 368.36 L6.37
+ 368.36 ZM8.84 362.64 L10.69 358.35 L9.26 357.73 L7.41 362.02 L8.84 362.64 L8.84 362.64 ZM11.31 356.92 L13.16
+ 352.62 L11.73 352 L9.87 356.3 L11.31 356.92 L11.31 356.92 ZM13.78 351.19 L15.63 346.9 L14.2 346.28 L12.34
+ 350.57 L13.78 351.19 L13.78 351.19 ZM16.25 345.47 L18.1 341.17 L16.67 340.56 L14.81 344.85 L16.25 345.47
+ L16.25 345.47 ZM18.71 339.74 L20.57 335.45 L19.13 334.84 L17.28 339.13 L18.71 339.74 L18.71 339.74 ZM21.18
+ 334.02 L23.04 329.73 L21.6 329.12 L19.75 333.41 L21.18 334.02 L21.18 334.02 ZM23.65 328.3 L25.5 324.01 L24.07
+ 323.39 L22.22 327.68 L23.65 328.3 L23.65 328.3 ZM26.12 322.58 L27.97 318.28 L26.54 317.67 L24.69 321.96
+ L26.12 322.58 L26.12 322.58 ZM28.59 316.85 L29.44 314.87 L28.01 314.25 L27.16 316.24 L28.59 316.85 L28.59
+ 316.85 Z" class="st8"/>
+ </g>
+ <g id="shape26-76" v:mID="26" v:groupContext="shape" transform="translate(172.476,-20.463)">
+ <title>Sheet.26</title>
+ <path d="M1.55 203.84 L2.28 208.45 L0.74 208.7 L0 204.09 L1.55 203.84 L1.55 203.84 ZM2.52 209.99 L3.27 214.61 L1.73 214.86
+ L0.99 210.23 L2.52 209.99 L2.52 209.99 ZM3.51 216.15 L4.25 220.76 L2.7 221 L1.97 216.39 L3.51 216.15 L3.51
+ 216.15 ZM4.49 222.3 L5.24 226.92 L3.69 227.16 L2.96 222.54 L4.49 222.3 L4.49 222.3 ZM5.48 228.45 L6.21 233.07
+ L4.67 233.31 L3.93 228.7 L5.48 228.45 L5.48 228.45 ZM6.47 234.6 L7.2 239.22 L5.66 239.47 L4.92 234.86 L6.47
+ 234.6 L6.47 234.6 ZM7.44 240.76 L8.18 245.37 L6.65 245.63 L5.9 241 L7.44 240.76 L7.44 240.76 ZM8.43 246.91
+ L9.17 251.53 L7.62 251.77 L6.89 247.15 L8.43 246.91 L8.43 246.91 ZM9.41 253.07 L10.14 257.68 L8.61 257.92
+ L7.88 253.31 L9.41 253.07 L9.41 253.07 ZM10.4 259.21 L11.14 263.84 L9.59 264.08 L8.85 259.47 L10.4 259.21
+ L10.4 259.21 ZM11.38 265.37 L12.13 269.98 L10.58 270.24 L9.84 265.62 L11.38 265.37 L11.38 265.37 ZM12.37
+ 271.52 L13.1 276.14 L11.56 276.39 L10.82 271.76 L12.37 271.52 L12.37 271.52 ZM13.34 277.68 L14.09 282.29
+ L12.55 282.53 L11.81 277.92 L13.34 277.68 L13.34 277.68 ZM14.33 283.84 L15.07 288.45 L13.52 288.69 L12.79
+ 284.08 L14.33 283.84 L14.33 283.84 ZM15.32 289.99 L16.06 294.61 L14.51 294.85 L13.78 290.23 L15.32 289.99
+ L15.32 289.99 ZM16.3 296.13 L17.03 300.75 L15.5 301 L14.75 296.39 L16.3 296.13 L16.3 296.13 ZM17.29 302.29
+ L18.02 306.9 L16.48 307.16 L15.74 302.53 L17.29 302.29 L17.29 302.29 ZM18.26 308.45 L19 313.06 L17.47 313.3
+ L16.73 308.69 L18.26 308.45 L18.26 308.45 ZM19.25 314.6 L19.99 319.22 L18.44 319.46 L17.71 314.84 L19.25
+ 314.6 L19.25 314.6 ZM20.23 320.76 L20.96 325.37 L19.43 325.61 L18.7 321 L20.23 320.76 L20.23 320.76 ZM21.22
+ 326.9 L21.96 331.51 L20.41 331.77 L19.67 327.15 L21.22 326.9 L21.22 326.9 ZM22.2 333.06 L22.95 337.67 L21.4
+ 337.92 L20.66 333.31 L22.2 333.06 L22.2 333.06 ZM23.19 339.21 L23.92 343.83 L22.38 344.07 L21.64 339.45
+ L23.19 339.21 L23.19 339.21 ZM24.18 345.37 L24.91 349.98 L23.37 350.22 L22.63 345.61 L24.18 345.37 L24.18
+ 345.37 ZM25.15 351.52 L25.89 356.14 L24.36 356.38 L23.61 351.76 L25.15 351.52 L25.15 351.52 ZM26.14 357.67
+ L26.88 362.28 L25.33 362.53 L24.6 357.92 L26.14 357.67 L26.14 357.67 ZM27.12 363.82 L27.85 368.44 L26.32
+ 368.69 L25.59 364.08 L27.12 363.82 L27.12 363.82 ZM28.11 369.98 L28.84 374.59 L27.3 374.83 L26.56 370.22
+ L28.11 369.98 L28.11 369.98 ZM29.08 376.13 L29.64 379.55 L28.09 379.8 L27.55 376.37 L29.08 376.13 L29.08
+ 376.13 Z" class="st9"/>
+ </g>
+ <g id="shape27-78" v:mID="27" v:groupContext="shape" transform="translate(276.159,-233.368)">
+ <title>Sheet.27</title>
+ <path d="M0.45 294.85 L354.04 376.06 L353.59 378.04 L0 296.85 L0.45 294.85 L0.45 294.85 ZM353.5 373.84 L358.79 378.19
+ L352.12 379.8 L353.5 373.84 L353.5 373.84 Z" class="st10"/>
+ </g>
+ <g id="shape28-80" v:mID="28" v:groupContext="shape" transform="translate(275.859,-178.726)">
+ <title>Sheet.28</title>
+ <path d="M1.05 240.32 L231.44 376.33 L230.39 378.1 L0 242.09 L1.05 240.32 L1.05 240.32 ZM231.59 374.05 L235.31 379.8
+ L228.47 379.32 L231.59 374.05 L231.59 374.05 Z" class="st10"/>
+ </g>
+ <g id="shape29-82" v:mID="29" v:groupContext="shape" transform="translate(275.379,-87.6866)">
+ <title>Sheet.29</title>
+ <path d="M2 149.94 L50.05 374.61 L48.05 375.04 L0 150.38 L2 149.94 L2 149.94 ZM51.83 373.18 L50.12 379.8 L45.85 374.47
+ L51.83 373.18 L51.83 373.18 Z" class="st11"/>
+ </g>
+ <g id="shape30-84" v:mID="30" v:groupContext="shape" transform="translate(276.279,-177.108)">
+ <title>Sheet.30</title>
+ <path d="M0.21 353.74 L229.55 375.85 L229.34 377.89 L0 355.75 L0.21 353.74 L0.21 353.74 ZM228.71 373.72 L234.53 377.35
+ L228.14 379.8 L228.71 373.72 L228.71 373.72 Z" class="st10"/>
+ </g>
+ <g id="shape31-86" v:mID="31" v:groupContext="shape" transform="translate(275.919,-213.926)">
+ <title>Sheet.31</title>
+ <path d="M0.45 308.72 L312.65 376.06 L312.2 378.04 L0 310.72 L0.45 308.72 L0.45 308.72 ZM312.08 373.84 L317.43 378.13
+ L310.79 379.8 L312.08 373.84 L312.08 373.84 Z" class="st10"/>
+ </g>
+ <g id="shape32-88" v:mID="32" v:groupContext="shape" transform="translate(275.439,-143.377)">
+ <title>Sheet.32</title>
+ <path d="M1.4 238.41 L150.34 375.59 L148.96 377.09 L0 239.9 L1.4 238.41 L1.4 238.41 ZM150.98 373.41 L153.4 379.8 L146.83
+ 377.9 L150.98 373.41 L150.98 373.41 Z" class="st11"/>
+ </g>
+ <g id="shape33-90" v:mID="33" v:groupContext="shape" transform="translate(275.274,-108.821)">
+ <title>Sheet.33</title>
+ <path d="M1.73 236.53 L90.79 374.97 L89.08 376.07 L0 237.63 L1.73 236.53 L1.73 236.53 ZM91.96 373 L92.7 379.8 L86.82
+ 376.31 L91.96 373 L91.96 373 Z" class="st11"/>
+ </g>
+ <g id="shape34-92" v:mID="34" v:groupContext="shape" transform="translate(275.364,-124.069)">
+ <title>Sheet.34</title>
+ <path d="M1.55 251.66 L108.22 375.28 L106.67 376.61 L0 253 L1.55 251.66 L1.55 251.66 ZM109.1 373.18 L110.78 379.8 L104.46
+ 377.17 L109.1 373.18 L109.1 373.18 Z" class="st11"/>
+ </g>
+ <g id="shape35-94" v:mID="35" v:groupContext="shape" transform="translate(275.154,-87.7165)">
+ <title>Sheet.35</title>
+ <path d="M1.97 215.68 L49.85 374.64 L47.9 375.22 L0 216.27 L1.97 215.68 L1.97 215.68 ZM51.52 373.08 L50.35 379.8 L45.65
+ 374.83 L51.52 373.08 L51.52 373.08 Z" class="st11"/>
+ </g>
+ <g id="shape36-96" v:mID="36" v:groupContext="shape" transform="translate(276.009,-143.736)">
+ <title>Sheet.36</title>
+ <path d="M0.74 320.41 L147.92 376.36 L147.2 378.26 L0 322.32 L0.74 320.41 L0.74 320.41 ZM147.7 374.08 L152.34 379.11
+ L145.52 379.8 L147.7 374.08 L147.7 374.08 Z" class="st11"/>
+ </g>
+ <g id="shape37-98" v:mID="37" v:groupContext="shape" transform="translate(275.649,-108.821)">
+ <title>Sheet.37</title>
+ <path d="M1.46 285.74 L89.46 375.45 L88 376.87 L0 287.16 L1.46 285.74 L1.46 285.74 ZM90.21 373.29 L92.29 379.8 L85.82
+ 377.57 L90.21 373.29 L90.21 373.29 Z" class="st11"/>
+ </g>
+ <g id="shape38-100" v:mID="38" v:groupContext="shape" transform="translate(275.934,-108.686)">
+ <title>Sheet.38</title>
+ <path d="M0.89 335.24 L87.85 376.57 L86.97 378.41 L0 337.09 L0.89 335.24 L0.89 335.24 ZM87.81 374.29 L92.01 379.67 L85.16
+ 379.8 L87.81 374.29 L87.81 374.29 Z" class="st11"/>
+ </g>
+ <g id="shape39-102" v:mID="39" v:groupContext="shape" transform="translate(275.574,-89.454)">
+ <title>Sheet.39</title>
+ <path d="M1.61 316.29 L48.49 375.18 L46.88 376.45 L0 317.57 L1.61 316.29 L1.61 316.29 ZM49.45 373.11 L50.86 379.8 L44.65
+ 376.91 L49.45 373.11 L49.45 373.11 Z" class="st11"/>
+ </g>
+ <g id="shape40-104" v:mID="40" v:groupContext="shape" transform="translate(276.324,-141.744)">
+ <title>Sheet.40</title>
+ <path d="M0.11 368.21 L146.74 375.79 L146.62 377.83 L0 370.23 L0.11 368.21 L0.11 368.21 ZM145.82 373.71 L151.78 377.08
+ L145.51 379.8 L145.82 373.71 L145.82 373.71 Z" class="st11"/>
+ </g>
+ <g id="shape41-106" v:mID="41" v:groupContext="shape" transform="translate(230.508,-309.069)">
+ <title>Sheet.41</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st4"/>
+ </g>
+ <g id="shape42-108" v:mID="42" v:groupContext="shape" transform="translate(230.508,-309.069)">
+ <title>Sheet.42</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape43-111" v:mID="43" v:groupContext="shape" transform="translate(233.39,-309.868)">
+ <title>Sheet.43</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape44-115" v:mID="44" v:groupContext="shape" transform="translate(263.764,-309.869)">
+ <title>Sheet.44</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape45-119" v:mID="45" v:groupContext="shape" transform="translate(230.508,-292.413)">
+ <title>Sheet.45</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape46-121" v:mID="46" v:groupContext="shape" transform="translate(230.508,-292.413)">
+ <title>Sheet.46</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape47-124" v:mID="47" v:groupContext="shape" transform="translate(233.39,-293.221)">
+ <title>Sheet.47</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape48-128" v:mID="48" v:groupContext="shape" transform="translate(230.508,-275.757)">
+ <title>Sheet.48</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st4"/>
+ </g>
+ <g id="shape49-130" v:mID="49" v:groupContext="shape" transform="translate(230.508,-275.757)">
+ <title>Sheet.49</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape50-133" v:mID="50" v:groupContext="shape" transform="translate(233.39,-276.574)">
+ <title>Sheet.50</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape51-137" v:mID="51" v:groupContext="shape" transform="translate(252.478,-276.574)">
+ <title>Sheet.51</title>
+ <desc>3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>3</text> </g>
+ <g id="shape52-141" v:mID="52" v:groupContext="shape" transform="translate(258.001,-276.574)">
+ <title>Sheet.52</title>
+ <desc>+1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="8.11122" cy="373.835" width="16.23" height="11.9384"/>
+ <path d="M16.22 367.87 L0 367.87 L0 379.8 L16.22 379.8 L16.22 367.87" class="st2"/>
+ <text x="2.44" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>+1</text> </g>
+ <g id="shape53-145" v:mID="53" v:groupContext="shape" transform="translate(230.508,-259.7)">
+ <title>Sheet.53</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape54-147" v:mID="54" v:groupContext="shape" transform="translate(230.508,-259.7)">
+ <title>Sheet.54</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape55-150" v:mID="55" v:groupContext="shape" transform="translate(233.39,-260.497)">
+ <title>Sheet.55</title>
+ <desc>3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>3</text> </g>
+ <g id="shape56-154" v:mID="56" v:groupContext="shape" transform="translate(230.508,-243.164)">
+ <title>Sheet.56</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st4"/>
+ </g>
+ <g id="shape57-156" v:mID="57" v:groupContext="shape" transform="translate(230.508,-243.164)">
+ <title>Sheet.57</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape58-159" v:mID="58" v:groupContext="shape" transform="translate(233.39,-244.053)">
+ <title>Sheet.58</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape59-163" v:mID="59" v:groupContext="shape" transform="translate(263.764,-244.053)">
+ <title>Sheet.59</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape60-167" v:mID="60" v:groupContext="shape" transform="translate(230.508,-227.107)">
+ <title>Sheet.60</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape61-169" v:mID="61" v:groupContext="shape" transform="translate(230.508,-227.107)">
+ <title>Sheet.61</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape62-172" v:mID="62" v:groupContext="shape" transform="translate(233.39,-227.976)">
+ <title>Sheet.62</title>
+ <desc>5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>5</text> </g>
+ <g id="shape63-176" v:mID="63" v:groupContext="shape" transform="translate(230.508,-210.211)">
+ <title>Sheet.63</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape64-178" v:mID="64" v:groupContext="shape" transform="translate(230.508,-210.211)">
+ <title>Sheet.64</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape65-181" v:mID="65" v:groupContext="shape" transform="translate(233.39,-211.085)">
+ <title>Sheet.65</title>
+ <desc>6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>6</text> </g>
+ <g id="shape66-185" v:mID="66" v:groupContext="shape" transform="translate(230.508,-193.794)">
+ <title>Sheet.66</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st4"/>
+ </g>
+ <g id="shape67-187" v:mID="67" v:groupContext="shape" transform="translate(230.508,-193.794)">
+ <title>Sheet.67</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape68-190" v:mID="68" v:groupContext="shape" transform="translate(233.39,-194.681)">
+ <title>Sheet.68</title>
+ <desc>7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>7</text> </g>
+ <g id="shape69-194" v:mID="69" v:groupContext="shape" transform="translate(263.764,-194.681)">
+ <title>Sheet.69</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape70-198" v:mID="70" v:groupContext="shape" transform="translate(230.508,-177.258)">
+ <title>Sheet.70</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape71-200" v:mID="71" v:groupContext="shape" transform="translate(230.508,-177.258)">
+ <title>Sheet.71</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape72-203" v:mID="72" v:groupContext="shape" transform="translate(233.39,-178.117)">
+ <title>Sheet.72</title>
+ <desc>8</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>8</text> </g>
+ <g id="shape73-207" v:mID="73" v:groupContext="shape" transform="translate(230.508,-160.602)">
+ <title>Sheet.73</title>
+ <path d="M0 363.15 L0 379.8 L41.18 379.8 L41.18 363.15 L0 363.15 L0 363.15 Z" class="st7"/>
+ </g>
+ <g id="shape74-209" v:mID="74" v:groupContext="shape" transform="translate(230.508,-160.602)">
+ <title>Sheet.74</title>
+ <path d="M0 363.15 L41.18 363.15 L41.18 379.8 L0 379.8 L0 363.15" class="st5"/>
+ </g>
+ <g id="shape75-212" v:mID="75" v:groupContext="shape" transform="translate(233.39,-161.505)">
+ <title>Sheet.75</title>
+ <desc>9</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>9</text> </g>
+ <g id="shape76-216" v:mID="76" v:groupContext="shape" transform="translate(230.508,-143.946)">
+ <title>Sheet.76</title>
+ <path d="M0 363.15 L0 379.8 L41.18 379.8 L41.18 363.15 L0 363.15 L0 363.15 Z" class="st4"/>
+ </g>
+ <g id="shape77-218" v:mID="77" v:groupContext="shape" transform="translate(230.508,-143.946)">
+ <title>Sheet.77</title>
+ <path d="M0 363.15 L41.18 363.15 L41.18 379.8 L0 379.8 L0 363.15" class="st5"/>
+ </g>
+ <g id="shape78-221" v:mID="78" v:groupContext="shape" transform="translate(233.39,-144.841)">
+ <title>Sheet.78</title>
+ <desc>10</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>10</text> </g>
+ <g id="shape79-225" v:mID="79" v:groupContext="shape" transform="translate(263.764,-144.841)">
+ <title>Sheet.79</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape80-229" v:mID="80" v:groupContext="shape" transform="translate(230.508,-127.529)">
+ <title>Sheet.80</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape81-231" v:mID="81" v:groupContext="shape" transform="translate(230.508,-127.529)">
+ <title>Sheet.81</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape82-234" v:mID="82" v:groupContext="shape" transform="translate(233.39,-128.329)">
+ <title>Sheet.82</title>
+ <desc>11</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>11</text> </g>
+ <g id="shape83-238" v:mID="83" v:groupContext="shape" transform="translate(230.508,-110.754)">
+ <title>Sheet.83</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape84-240" v:mID="84" v:groupContext="shape" transform="translate(230.508,-110.754)">
+ <title>Sheet.84</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape85-243" v:mID="85" v:groupContext="shape" transform="translate(233.39,-111.64)">
+ <title>Sheet.85</title>
+ <desc>12</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>12</text> </g>
+ <g id="shape86-247" v:mID="86" v:groupContext="shape" transform="translate(230.508,-94.9362)">
+ <title>Sheet.86</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape87-249" v:mID="87" v:groupContext="shape" transform="translate(230.508,-94.9362)">
+ <title>Sheet.87</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape88-252" v:mID="88" v:groupContext="shape" transform="translate(233.39,-95.7375)">
+ <title>Sheet.88</title>
+ <desc>…</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.30974" cy="373.835" width="14.62" height="11.9384"/>
+ <path d="M14.62 367.87 L0 367.87 L0 379.8 L14.62 379.8 L14.62 367.87" class="st2"/>
+ <text x="2.34" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>…</text> </g>
+ <g id="shape89-256" v:mID="89" v:groupContext="shape" transform="translate(230.508,-78.999)">
+ <title>Sheet.89</title>
+ <path d="M0 363.27 L0 379.8 L41.18 379.8 L41.18 363.27 L0 363.27 L0 363.27 Z" class="st7"/>
+ </g>
+ <g id="shape90-258" v:mID="90" v:groupContext="shape" transform="translate(230.508,-78.999)">
+ <title>Sheet.90</title>
+ <path d="M0 363.27 L41.18 363.27 L41.18 379.8 L0 379.8 L0 363.27" class="st5"/>
+ </g>
+ <g id="shape91-261" v:mID="91" v:groupContext="shape" transform="translate(233.39,-79.8525)">
+ <title>Sheet.91</title>
+ <desc>255</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1326" cy="373.835" width="22.27" height="11.9384"/>
+ <path d="M22.27 367.87 L0 367.87 L0 379.8 L22.27 379.8 L22.27 367.87" class="st2"/>
+ <text x="2.84" y="376.82" class="st12" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>255</text> </g>
+ <g id="shape92-265" v:mID="92" v:groupContext="shape" transform="translate(276.219,-250.503)">
+ <title>Sheet.92</title>
+ <path d="M0.33 311.98 L396.81 375.94 L396.48 377.95 L0 313.99 L0.33 311.98 L0.33 311.98 ZM396.12 373.75 L401.68 377.74
+ L395.16 379.8 L396.12 373.75 L396.12 373.75 Z" class="st15"/>
+ </g>
+ <g id="shape93-267" v:mID="93" v:groupContext="shape" transform="translate(275.859,-178.426)">
+ <title>Sheet.93</title>
+ <path d="M0.57 305.72 L230.93 376.21 L230.33 378.16 L0 307.67 L0.57 305.72 L0.57 305.72 ZM230.57 373.96 L235.52 378.67
+ L228.77 379.8 L230.57 373.96 L230.57 373.96 Z" class="st15"/>
+ </g>
+ <g id="shape94-269" v:mID="94" v:groupContext="shape" transform="translate(276.279,-151.285)">
+ <title>Sheet.94</title>
+ <path d="M0.21 379.8 L230.12 353.17 L229.88 351.14 L0 377.8 L0.21 379.8 L0.21 379.8 ZM229.34 355.3 L235.07 351.55 L228.65
+ 349.25 L229.34 355.3 L229.34 355.3 Z" class="st15"/>
+ </g>
+ <g id="shape95-271" v:mID="95" v:groupContext="shape" transform="translate(276.009,-232.679)">
+ <title>Sheet.95</title>
+ <path d="M0.27 327.47 L354.22 375.91 L353.95 377.92 L0 329.48 L0.27 327.47 L0.27 327.47 ZM353.5 373.75 L359.15 377.62
+ L352.66 379.8 L353.5 373.75 L353.5 373.75 Z" class="st10"/>
+ </g>
+ <g id="shape96-273" v:mID="96" v:groupContext="shape" transform="translate(276.279,-201.134)">
+ <title>Sheet.96</title>
+ <path d="M0.21 379.8 L353.86 348.14 L353.68 346.1 L0 377.77 L0.21 379.8 L0.21 379.8 ZM353.05 350.24 L358.88 346.64 L352.48
+ 344.16 L353.05 350.24 L353.05 350.24 Z" class="st15"/>
+ </g>
+ <g id="shape97-275" v:mID="97" v:groupContext="shape" transform="translate(346.482,-41.2531)">
+ <title>Sheet.97</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st7"/>
+ </g>
+ <g id="shape98-277" v:mID="98" v:groupContext="shape" transform="translate(346.482,-41.2531)">
+ <title>Sheet.98</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape99-280" v:mID="99" v:groupContext="shape" transform="translate(349.371,-91.6514)">
+ <title>Sheet.99</title>
+ <desc>…</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.30974" cy="373.835" width="14.62" height="11.9384"/>
+ <path d="M14.62 367.87 L0 367.87 L0 379.8 L14.62 379.8 L14.62 367.87" class="st2"/>
+ <text x="2.34" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>…</text> </g>
+ <g id="shape100-284" v:mID="100" v:groupContext="shape" transform="translate(470.019,-94.337)">
+ <title>Sheet.100</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st7"/>
+ </g>
+ <g id="shape101-286" v:mID="101" v:groupContext="shape" transform="translate(470.019,-94.337)">
+ <title>Sheet.101</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape102-289" v:mID="102" v:groupContext="shape" transform="translate(472.925,-144.778)">
+ <title>Sheet.102</title>
+ <desc>5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>5</text> </g>
+ <g id="shape103-293" v:mID="103" v:groupContext="shape" transform="translate(511.558,-113.749)">
+ <title>Sheet.103</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape104-295" v:mID="104" v:groupContext="shape" transform="translate(511.558,-113.749)">
+ <title>Sheet.104</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape105-298" v:mID="105" v:groupContext="shape" transform="translate(514.441,-164.138)">
+ <title>Sheet.105</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape106-302" v:mID="106" v:groupContext="shape" transform="translate(542.148,-164.138)">
+ <title>Sheet.106</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape107-306" v:mID="107" v:groupContext="shape" transform="translate(542.148,-152.155)">
+ <title>Sheet.107</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape108-310" v:mID="108" v:groupContext="shape" transform="translate(536.626,-140.172)">
+ <title>Sheet.108</title>
+ <desc>10</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>10</text> </g>
+ <g id="shape109-314" v:mID="109" v:groupContext="shape" transform="translate(514.201,-114.441)">
+ <title>Sheet.109</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape110-318" v:mID="110" v:groupContext="shape" transform="translate(519.723,-114.441)">
+ <title>Sheet.110</title>
+ <desc>+4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="8.11122" cy="373.835" width="16.23" height="11.9384"/>
+ <path d="M16.22 367.87 L0 367.87 L0 379.8 L16.22 379.8 L16.22 367.87" class="st2"/>
+ <text x="2.44" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>+4</text> </g>
+ <g id="shape111-322" v:mID="111" v:groupContext="shape" transform="translate(552.257,-130.525)">
+ <title>Sheet.111</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st7"/>
+ </g>
+ <g id="shape112-324" v:mID="112" v:groupContext="shape" transform="translate(552.257,-130.525)">
+ <title>Sheet.112</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape113-327" v:mID="113" v:groupContext="shape" transform="translate(555.203,-180.952)">
+ <title>Sheet.113</title>
+ <desc>3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>3</text> </g>
+ <g id="shape114-331" v:mID="114" v:groupContext="shape" transform="translate(634.615,-169.11)">
+ <title>Sheet.114</title>
+ <path d="M0 313.9 L0 379.8 L41.18 379.8 L41.18 313.9 L0 313.9 L0 313.9 Z" class="st4"/>
+ </g>
+ <g id="shape115-333" v:mID="115" v:groupContext="shape" transform="translate(634.615,-169.11)">
+ <title>Sheet.115</title>
+ <path d="M0 313.9 L41.18 313.9 L41.18 379.8 L0 379.8 L0 313.9" class="st5"/>
+ </g>
+ <g id="shape116-336" v:mID="116" v:groupContext="shape" transform="translate(637.526,-219.595)">
+ <title>Sheet.116</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape117-340" v:mID="117" v:groupContext="shape" transform="translate(665.234,-219.595)">
+ <title>Sheet.117</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape118-344" v:mID="118" v:groupContext="shape" transform="translate(665.2,-225.489)">
+ <title>Sheet.118</title>
+ <path d="M0 379.32 L0 379.8 L5.52 379.8 L5.52 379.32 L0 379.32 L0 379.32 Z" class="st19"/>
+ </g>
+ <g id="shape119-346" v:mID="119" v:groupContext="shape" transform="translate(665.234,-207.612)">
+ <title>Sheet.119</title>
+ <desc>7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>7</text> </g>
+ <g id="shape120-350" v:mID="120" v:groupContext="shape" transform="translate(637.286,-169.898)">
+ <title>Sheet.120</title>
+ <desc>5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>5</text> </g>
+ <g id="shape121-354" v:mID="121" v:groupContext="shape" transform="translate(642.809,-169.898)">
+ <title>Sheet.121</title>
+ <desc>-</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="3.49545" cy="373.835" width="7" height="11.9384"/>
+ <path d="M6.99 367.87 L0 367.87 L0 379.8 L6.99 379.8 L6.99 367.87" class="st2"/>
+ <text x="1.84" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>-</text> </g>
+ <g id="shape122-358" v:mID="122" v:groupContext="shape" transform="translate(646.17,-169.898)">
+ <title>Sheet.122</title>
+ <desc>3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>3</text> </g>
+ <g id="shape123-362" v:mID="123" v:groupContext="shape" transform="translate(676.275,-186.725)">
+ <title>Sheet.123</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape124-364" v:mID="124" v:groupContext="shape" transform="translate(676.275,-186.725)">
+ <title>Sheet.124</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape125-367" v:mID="125" v:groupContext="shape" transform="translate(679.141,-237.17)">
+ <title>Sheet.125</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape126-371" v:mID="126" v:groupContext="shape" transform="translate(706.849,-237.17)">
+ <title>Sheet.126</title>
+ <desc>0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0</text> </g>
+ <g id="shape127-375" v:mID="127" v:groupContext="shape" transform="translate(678.901,-187.474)">
+ <title>Sheet.127</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape128-379" v:mID="128" v:groupContext="shape" transform="translate(304.943,-21.841)">
+ <title>Sheet.128</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape129-381" v:mID="129" v:groupContext="shape" transform="translate(304.943,-21.841)">
+ <title>Sheet.129</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape130-384" v:mID="130" v:groupContext="shape" transform="translate(307.855,-72.2917)">
+ <title>Sheet.130</title>
+ <desc>64</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>64</text> </g>
+ <g id="shape131-388" v:mID="131" v:groupContext="shape" transform="translate(330.041,-72.2917)">
+ <title>Sheet.131</title>
+ <desc>96</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>96</text> </g>
+ <g id="shape132-392" v:mID="132" v:groupContext="shape" transform="translate(307.616,-22.5952)">
+ <title>Sheet.132</title>
+ <desc>7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>7</text> </g>
+ <g id="shape133-396" v:mID="133" v:groupContext="shape" transform="translate(428.72,-77.4413)">
+ <title>Sheet.133</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape134-398" v:mID="134" v:groupContext="shape" transform="translate(428.72,-77.4413)">
+ <title>Sheet.134</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape135-401" v:mID="135" v:groupContext="shape" transform="translate(431.648,-127.825)">
+ <title>Sheet.135</title>
+ <desc>6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>6</text> </g>
+ <g id="shape136-405" v:mID="136" v:groupContext="shape" transform="translate(453.834,-127.825)">
+ <title>Sheet.136</title>
+ <desc>98</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>98</text> </g>
+ <g id="shape137-409" v:mID="137" v:groupContext="shape" transform="translate(431.409,-78.1289)">
+ <title>Sheet.137</title>
+ <desc>5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>5</text> </g>
+ <g id="shape138-413" v:mID="138" v:groupContext="shape" transform="translate(593.796,-149.818)">
+ <title>Sheet.138</title>
+ <path d="M0 313.9 L0 379.8 L41.18 379.8 L41.18 313.9 L0 313.9 L0 313.9 Z" class="st4"/>
+ </g>
+ <g id="shape139-415" v:mID="139" v:groupContext="shape" transform="translate(593.796,-149.818)">
+ <title>Sheet.139</title>
+ <path d="M0 313.9 L41.18 313.9 L41.18 379.8 L0 379.8 L0 313.9" class="st5"/>
+ </g>
+ <g id="shape140-418" v:mID="140" v:groupContext="shape" transform="translate(596.718,-200.312)">
+ <title>Sheet.140</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape141-422" v:mID="141" v:groupContext="shape" transform="translate(618.904,-200.312)">
+ <title>Sheet.141</title>
+ <desc>99</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>99</text> </g>
+ <g id="shape142-426" v:mID="142" v:groupContext="shape" transform="translate(596.478,-150.615)">
+ <title>Sheet.142</title>
+ <desc>9</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>9</text> </g>
+ <g id="shape143-430" v:mID="143" v:groupContext="shape" transform="translate(387.181,-58.0291)">
+ <title>Sheet.143</title>
+ <path d="M0 314.02 L0 379.8 L41.18 379.8 L41.18 314.02 L0 314.02 L0 314.02 Z" class="st4"/>
+ </g>
+ <g id="shape144-432" v:mID="144" v:groupContext="shape" transform="translate(387.181,-58.0291)">
+ <title>Sheet.144</title>
+ <path d="M0 314.02 L41.18 314.02 L41.18 379.8 L0 379.8 L0 314.02" class="st5"/>
+ </g>
+ <g id="shape145-435" v:mID="145" v:groupContext="shape" transform="translate(390.133,-108.466)">
+ <title>Sheet.145</title>
+ <desc>7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>7</text> </g>
+ <g id="shape146-439" v:mID="146" v:groupContext="shape" transform="translate(412.318,-108.466)">
+ <title>Sheet.146</title>
+ <desc>97</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="7.95203" cy="373.835" width="15.91" height="11.9384"/>
+ <path d="M15.9 367.87 L0 367.87 L0 379.8 L15.9 379.8 L15.9 367.87" class="st2"/>
+ <text x="2.42" y="376.82" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>97</text> </g>
+ <g id="shape147-443" v:mID="147" v:groupContext="shape" transform="translate(389.893,-58.7692)">
+ <title>Sheet.147</title>
+ <desc>6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.77151" cy="373.835" width="9.55" height="11.9384"/>
+ <path d="M9.54 367.87 L0 367.87 L0 379.8 L9.54 379.8 L9.54 367.87" class="st2"/>
+ <text x="2.01" y="376.82" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>6</text> </g>
+ <g id="shape148-447" v:mID="148" v:groupContext="shape" transform="translate(31.8163,-277.674)">
+ <title>Sheet.148</title>
+ <path d="M0 362.83 C0 360.95 1.52 359.43 3.41 359.43 L68.51 359.43 C70.4 359.43 71.91 360.95 71.91 362.83 L71.91 376.41
+ C71.91 378.28 70.4 379.8 68.51 379.8 L3.41 379.8 C1.52 379.8 0 378.28 0 376.41 L0 362.83 Z" class="st4"/>
+ </g>
+ <g id="shape149-449" v:mID="149" v:groupContext="shape" transform="translate(31.8163,-277.674)">
+ <title>Sheet.149</title>
+ <path d="M0 362.83 C0 360.95 1.52 359.43 3.41 359.43 L68.51 359.43 C70.4 359.43 71.91 360.95 71.91 362.83 L71.91 376.41
+ C71.91 378.28 70.4 379.8 68.51 379.8 L3.41 379.8 C1.52 379.8 0 378.28 0 376.41 L0 362.83 Z" class="st21"/>
+ </g>
+ <g id="shape150-451" v:mID="150" v:groupContext="shape" transform="translate(36,-278.851)">
+ <title>Sheet.150</title>
+ <desc>Insert key</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="35.613" cy="372.612" width="71.23" height="14.3829"/>
+ <path d="M71.23 365.42 L0 365.42 L0 379.8 L71.23 379.8 L71.23 365.42" class="st2"/>
+ <text x="9.64" y="376.21" class="st22" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Insert key </text> </g>
+ <g id="shape151-455" v:mID="151" v:groupContext="shape" transform="translate(47.7236,-257.004)">
+ <title>Sheet.151</title>
+ <path d="M0 369.44 L9.81 369.44 L9.81 359.07 L29.44 359.07 L29.44 369.44 L39.26 369.44 L19.63 379.8 L0 369.44 Z"
+ class="st23"/>
+ </g>
+ <g id="shape152-457" v:mID="152" v:groupContext="shape" transform="translate(31.8163,-236.094)">
+ <title>Sheet.152</title>
+ <path d="M0 362.73 C0 360.85 1.54 359.31 3.42 359.31 L68.49 359.31 C70.38 359.31 71.91 360.85 71.91 362.73 L71.91 376.39
+ C71.91 378.28 70.38 379.8 68.49 379.8 L3.42 379.8 C1.54 379.8 0 378.28 0 376.39 L0 362.73 Z" class="st4"/>
+ </g>
+ <g id="shape153-459" v:mID="153" v:groupContext="shape" transform="translate(31.8163,-236.094)">
+ <title>Sheet.153</title>
+ <path d="M0 362.73 C0 360.85 1.54 359.31 3.42 359.31 L68.49 359.31 C70.38 359.31 71.91 360.85 71.91 362.73 L71.91 376.39
+ C71.91 378.28 70.38 379.8 68.49 379.8 L3.42 379.8 C1.54 379.8 0 378.28 0 376.39 L0 362.73 Z" class="st21"/>
+ </g>
+ <g id="shape154-461" v:mID="154" v:groupContext="shape" transform="translate(54.6845,-237.332)">
+ <title>Sheet.154</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.8573" cy="372.612" width="33.72" height="14.3829"/>
+ <path d="M33.71 365.42 L0 365.42 L0 379.8 L33.71 379.8 L33.71 365.42" class="st2"/>
+ <text x="3.86" y="376.21" class="st22" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape155-465" v:mID="155" v:groupContext="shape" transform="translate(23.0522,-195.232)">
+ <title>Sheet.155</title>
+ <path d="M0 363.53 C0 361.73 1.46 360.27 3.26 360.27 L87.15 360.27 C88.95 360.27 90.4 361.73 90.4 363.53 L90.4 376.55
+ C90.4 378.35 88.95 379.8 87.15 379.8 L3.26 379.8 C1.46 379.8 0 378.35 0 376.55 L0 363.53 Z" class="st4"/>
+ </g>
+ <g id="shape156-467" v:mID="156" v:groupContext="shape" transform="translate(23.0522,-195.232)">
+ <title>Sheet.156</title>
+ <path d="M0 363.53 C0 361.73 1.46 360.27 3.26 360.27 L87.15 360.27 C88.95 360.27 90.4 361.73 90.4 363.53 L90.4 376.55
+ C90.4 378.35 88.95 379.8 87.15 379.8 L3.26 379.8 C1.46 379.8 0 378.35 0 376.55 L0 363.53 Z" class="st21"/>
+ </g>
+ <g id="shape157-469" v:mID="157" v:groupContext="shape" transform="translate(27,-196.017)">
+ <title>Sheet.157</title>
+ <desc>0x0102ABCD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.6644" cy="372.612" width="87.33" height="14.3829"/>
+ <path d="M87.33 365.42 L0 365.42 L0 379.8 L87.33 379.8 L87.33 365.42" class="st2"/>
+ <text x="7.36" y="376.21" class="st22" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102ABCD</text> </g>
+ <g id="shape158-473" v:mID="158" v:groupContext="shape" transform="translate(47.7236,-214.824)">
+ <title>Sheet.158</title>
+ <path d="M0 369.5 L9.81 369.5 L9.81 359.19 L29.44 359.19 L29.44 369.5 L39.26 369.5 L19.63 379.8 L0 369.5 Z"
+ class="st23"/>
+ </g>
+ <g id="shape159-475" v:mID="159" v:groupContext="shape" transform="translate(49.8539,-181.212)">
+ <title>Sheet.159</title>
+ <path d="M11.89 368.42 C11.89 371.57 11.47 374.11 10.94 374.11 L6.9 374.11 C6.37 374.11 5.94 376.67 5.94 379.8 C5.94
+ 376.67 5.52 374.11 5 374.11 L0.95 374.11 C0.43 374.11 0 371.57 0 368.42" class="st24"/>
+ </g>
+ <g id="shape160-478" v:mID="160" v:groupContext="shape" transform="translate(64.2606,-180.973)">
+ <title>Sheet.160</title>
+ <path d="M9.54 368.54 C9.54 371.66 9.21 374.17 8.79 374.17 L5.53 374.17 C5.11 374.17 4.77 376.7 4.77 379.8 C4.77 376.7
+ 4.43 374.17 4.02 374.17 L0.76 374.17 C0.34 374.17 0 371.66 0 368.54" class="st24"/>
+ </g>
+ <g id="shape161-481" v:mID="161" v:groupContext="shape" transform="translate(18.19,-60.9649)">
+ <title>Sheet.161</title>
+ <path d="M0 354.74 C0 351.97 2.25 349.73 5.03 349.73 L10.77 349.73 L30.27 267.14 L26.92 349.73 L59.58 349.73 C62.35 349.73
+ 64.59 351.97 64.59 354.74 L64.59 354.74 L64.59 362.26 L64.59 374.8 C64.59 377.57 62.35 379.8 59.58 379.8
+ L26.92 379.8 L10.77 379.8 L10.77 379.8 L5.03 379.8 C2.25 379.8 0 377.57 0 374.8 L0 362.26 L0 354.74 L0 354.74
+ Z" class="st23"/>
+ </g>
+ <g id="shape162-483" v:mID="162" v:groupContext="shape" transform="translate(28.141,-66.9569)">
+ <title>Sheet.162</title>
+ <desc>chunk id</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="27.5794" cy="372.612" width="55.16" height="14.3829"/>
+ <path d="M55.16 365.42 L0 365.42 L0 379.8 L55.16 379.8 L55.16 365.42" class="st2"/>
+ <text x="5.26" y="376.21" class="st25" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>chunk id</text> </g>
+ <g id="shape163-487" v:mID="163" v:groupContext="shape" transform="translate(50.8451,-112.132)">
+ <title>Sheet.163</title>
+ <path d="M0 354.64 C0 351.87 2.27 349.61 5.04 349.61 L10.74 349.61 L16.27 313.66 L26.86 349.61 L59.43 349.61 C62.22 349.61
+ 64.47 351.87 64.47 354.64 L64.47 354.64 L64.47 362.19 L64.47 374.77 C64.47 377.56 62.22 379.8 59.43 379.8
+ L26.86 379.8 L10.74 379.8 L10.74 379.8 L5.04 379.8 C2.27 379.8 0 377.56 0 374.77 L0 362.19 L0 354.64 L0
+ 354.64 Z" class="st23"/>
+ </g>
+ <g id="shape164-489" v:mID="164" v:groupContext="shape" transform="translate(68.8168,-118.181)">
+ <title>Sheet.164</title>
+ <desc>bin id</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="18.3881" cy="372.612" width="36.78" height="14.3829"/>
+ <path d="M36.78 365.42 L0 365.42 L0 379.8 L36.78 379.8 L36.78 365.42" class="st2"/>
+ <text x="4.06" y="376.21" class="st25" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bin id</text> </g>
+ <g id="shape165-493" v:mID="165" v:groupContext="shape" transform="translate(113.454,-225.085)">
+ <title>Sheet.165</title>
+ <path d="M0.01 375.68 L13.23 375.73 L13.22 377.77 L0 377.72 L0.01 375.68 L0.01 375.68 ZM12.22 373.69 L18.33 376.76 L12.2
+ 379.8 L12.22 373.69 L12.22 373.69 Z" class="st26"/>
+ </g>
+ <g id="shape166-495" v:mID="166" v:groupContext="shape" transform="translate(200.975,-280.969)">
+ <title>Sheet.166</title>
+ <path d="M0 375.73 L20.11 375.73 L20.11 377.77 L0 377.77 L0 375.73 L0 375.73 ZM19.09 373.69 L25.21 376.75 L19.09 379.8
+ L19.09 373.69 L19.09 373.69 Z" class="st26"/>
+ </g>
+ <g id="shape167-497" v:mID="167" v:groupContext="shape" transform="translate(275.739,-179.745)">
+ <title>Sheet.167</title>
+ <path d="M0.81 274.59 L231.38 376.48 L230.54 378.37 L0 276.48 L0.81 274.59 L0.81 274.59 ZM231.26 374.2 L235.64 379.47
+ L228.8 379.8 L231.26 374.2 L231.26 374.2 Z" class="st27"/>
+ </g>
+ <g id="shape168-499" v:mID="168" v:groupContext="shape" transform="translate(521.823,-96.8834)">
+ <title>Sheet.168</title>
+ <path d="M127.17 309.02 L127.17 378.79 C127.17 379.35 126.72 379.8 126.15 379.8 L3.06 379.8 C2.52 379.8 2.04 379.35 2.04
+ 378.79 L2.04 369.59 L4.08 369.59 L4.08 378.79 L3.06 377.77 L126.15 377.77 L125.13 378.79 L125.13 309.02
+ L127.17 309.02 ZM0 370.61 L3.06 364.5 L6.12 370.61 L0 370.61 Z" class="st28"/>
+ </g>
+ <g id="shape169-501" v:mID="169" v:groupContext="shape" transform="translate(478.603,-39.7553)">
+ <title>Sheet.169</title>
+ <path d="M0 347.57 C0 344.01 2.91 341.1 6.48 341.1 L237.86 341.1 C241.43 341.1 244.31 344.01 244.31 347.57 L244.31 373.36
+ C244.31 376.93 241.43 379.8 237.86 379.8 L6.48 379.8 C2.91 379.8 0 376.93 0 373.36 L0 347.57 Z"
+ class="st23"/>
+ </g>
+ <g id="shape170-503" v:mID="170" v:groupContext="shape" transform="translate(487.717,-45.5378)">
+ <title>Sheet.170</title>
+ <desc>Move bin from group 1 to 4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="126.387" cy="369.018" width="252.78" height="21.5726"/>
+ <path d="M252.77 358.23 L0 358.23 L0 379.8 L252.77 379.8 L252.77 358.23" class="st2"/>
+ <text x="18.98" y="374.41" class="st29" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Move bin from group 1 to 4</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i2.svg b/doc/guides/prog_guide/img/efd_i2.svg
new file mode 100644
index 0000000..a5f43f9
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i2.svg
@@ -0,0 +1,280 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i2.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="2.85156in" height="2.98777in"
+ viewBox="0 0 205.313 215.12" xml:space="preserve" color-interpolation-filters="sRGB" class="st18">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#deebf6;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st5 {fill:#ff0000;stroke:#c7c8c8;stroke-width:0.25}
+ .st6 {fill:none;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st7 {fill:none;stroke:#0070c0;stroke-width:1.5}
+ .st8 {marker-end:url(#mrkr5-91);stroke:#0070c0;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st9 {fill:#0070c0;fill-opacity:1;stroke:#0070c0;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st10 {fill:none;stroke:none;stroke-width:0.25}
+ .st11 {fill:#ff0000;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st12 {font-size:1em}
+ .st13 {marker-end:url(#mrkr5-101);stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st14 {fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st15 {fill:#5b9bd5;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st16 {marker-end:url(#mrkr5-110);stroke:#41719c;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st17 {fill:#41719c;fill-opacity:1;stroke:#41719c;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st18 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-91" class="st9" v:arrowType="5" v:arrowSize="2" v:setback="4.45" refX="-4.45" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ <marker id="mrkr5-101" class="st14" v:arrowType="5" v:arrowSize="2" v:setback="6.16" refX="-6.16" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ <marker id="mrkr5-110" class="st17" v:arrowType="5" v:arrowSize="2" v:setback="6.16" refX="-6.16" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(24.4044,-42.7174)">
+ <title>Circle</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow2-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 138.62 A76.5 76.5 0 0 1 153 138.62 A76.5 76.5 0 1 1 0 138.62 Z" class="st2"/>
+ </g>
+ <path d="M0 138.62 A76.5 76.5 0 0 1 153 138.62 A76.5 76.5 0 1 1 0 138.62 Z" class="st3"/>
+ </g>
+ <g id="shape3-6" v:mID="3" v:groupContext="shape" transform="translate(24.4044,-144.53)">
+ <title>Circle.3</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow3-7" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape4-11" v:mID="4" v:groupContext="shape" transform="translate(21.0294,-102.342)">
+ <title>Circle.4</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow4-12" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape5-16" v:mID="5" v:groupContext="shape" transform="translate(69.4044,-183.342)">
+ <title>Circle.5</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow5-17" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape6-21" v:mID="6" v:groupContext="shape" transform="translate(117.217,-183.342)">
+ <title>Circle.6</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow6-22" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st5"/>
+ </g>
+ <g id="shape7-26" v:mID="7" v:groupContext="shape" transform="translate(171.217,-104.03)">
+ <title>Circle.7</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow7-27" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st5"/>
+ </g>
+ <g id="shape8-31" v:mID="8" v:groupContext="shape" transform="translate(109.904,-38.2174)">
+ <title>Circle.8</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow8-32" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st5"/>
+ </g>
+ <g id="shape9-36" v:mID="9" v:groupContext="shape" transform="translate(21.0294,-124.842)">
+ <title>Circle.9</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow9-37" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st5"/>
+ </g>
+ <g id="shape10-41" v:mID="10" v:groupContext="shape" transform="translate(147.029,-168.717)">
+ <title>Circle.10</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow10-42" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape11-46" v:mID="11" v:groupContext="shape" transform="translate(138.029,-48.3424)">
+ <title>Circle.11</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow11-47" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape12-51" v:mID="12" v:groupContext="shape" transform="translate(160.529,-74.2174)">
+ <title>Circle.12</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow12-52" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape13-56" v:mID="13" v:groupContext="shape" transform="translate(40.7169,-57.3424)">
+ <title>Circle.13</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow13-57" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape14-61" v:mID="14" v:groupContext="shape" transform="translate(42.4044,-168.717)">
+ <title>Circle.14</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow14-62" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape15-66" v:mID="15" v:groupContext="shape" transform="translate(66.0294,-42.7174)">
+ <title>Circle.15</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow15-67" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape16-71" v:mID="16" v:groupContext="shape" transform="translate(25.5294,-79.8424)">
+ <title>Circle.16</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow16-72" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape17-76" v:mID="17" v:groupContext="shape" transform="translate(165.029,-143.405)">
+ <title>Circle.17</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow17-77" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st2"/>
+ </g>
+ <path d="M0 208.93 A6.1875 6.1875 0 1 1 12.37 208.93 A6.1875 6.1875 0 0 1 0 208.93 Z" class="st4"/>
+ </g>
+ <g id="shape18-81" v:mID="18" v:groupContext="shape" transform="translate(276.618,4.50201) rotate(45)">
+ <title>Ellipse</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow18-82" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,1.63935,1.1506)" class="st1">
+ <path d="M0 187.01 A14.7383 28.1086 0 1 1 29.48 187.01 A14.7383 28.1086 0 1 1 0 187.01 Z" class="st6"/>
+ </g>
+ <path d="M0 187.01 A14.7383 28.1086 0 1 1 29.48 187.01 A14.7383 28.1086 0 1 1 0 187.01 Z" class="st7"/>
+ </g>
+ <g id="shape19-86" v:mID="19" v:groupContext="shape" transform="translate(251.273,355.436) rotate(156.038)">
+ <title>Sheet.19</title>
+ <path d="M-0 215.12 A73.4538 31.2572 85.43 0 1 40.92 208.96 L41.1 209.27" class="st8"/>
+ </g>
+ <g id="shape20-92" v:mID="20" v:groupContext="shape" transform="translate(62.705,-78.7174)">
+ <title>Sheet.20</title>
+ <desc>Target Hashed Value</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="42.6994" cy="203.87" width="85.4" height="22.5"/>
+ <rect x="0" y="192.62" width="85.3987" height="22.5" class="st10"/>
+ <text x="6.73" y="200.27" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target Hashed <tspan
+ x="28.48" dy="1.2em" class="st12">Value</tspan></text> </g>
+ <g id="shape21-96" v:mID="21" v:groupContext="shape" transform="translate(314.101,88.728) rotate(75.9638)">
+ <title>Sheet.21</title>
+ <path d="M0 215.12 L16.92 215.12" class="st13"/>
+ </g>
+ <g id="shape23-102" v:mID="23" v:groupContext="shape" transform="translate(60.4044,-138.342)">
+ <title>Sheet.23</title>
+ <desc>Keys</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="24.75" cy="203.87" width="49.5" height="22.5"/>
+ <rect x="0" y="192.62" width="49.5" height="22.5" class="st10"/>
+ <text x="13.21" y="207.47" class="st15" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Keys</text> </g>
+ <g id="shape24-105" v:mID="24" v:groupContext="shape" transform="translate(-125.293,114.034) rotate(-104.574)">
+ <title>Sheet.24</title>
+ <path d="M0 215.12 L22.9 215.12" class="st16"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i3.svg b/doc/guides/prog_guide/img/efd_i3.svg
new file mode 100644
index 0000000..ae22903
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i3.svg
@@ -0,0 +1,634 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i3.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="6.56036in" height="5.44284in"
+ viewBox="0 0 472.346 391.884" xml:space="preserve" color-interpolation-filters="sRGB" class="st22">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false"/>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st5 {marker-end:url(#mrkr5-24);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st6 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st7 {fill:none;stroke:#2e75b5;stroke-width:1}
+ .st8 {fill:#5b9bd5;font-family:Calibri;font-size:1.00001em}
+ .st9 {font-size:1em}
+ .st10 {fill:none;stroke:none;stroke-width:1}
+ .st11 {fill:#feffff;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st12 {fill:#5b9bd5;fill-opacity:0.25;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.25}
+ .st13 {fill:#4f87bb;stroke:#40709c;stroke-width:0.75}
+ .st14 {fill:#feffff;font-family:Calibri;font-size:0.75em}
+ .st15 {fill:none;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st16 {fill:none;stroke:#2e75b5;stroke-width:2.25}
+ .st17 {stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st18 {fill:#305497;stroke:#2e75b5;stroke-width:1}
+ .st19 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:none}
+ .st20 {fill:#92d050;fill-opacity:0.3;stroke:none;stroke-width:0.25}
+ .st21 {fill:#feffff;font-family:Calibri;font-size:1.16666em}
+ .st22 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-24" class="st6" v:arrowType="5" v:arrowSize="2" v:setback="6.16" refX="-6.16" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <v:layer v:name="Connector" v:index="0"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(111.25,-354.482)">
+ <title>Rectangle</title>
+ <desc>Packet Header</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="42.75" cy="382.884" width="85.5" height="18"/>
+ <g id="shadow2-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="85.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="85.5" height="18" class="st3"/>
+ <text x="13.24" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Packet Header</text> </g>
+ <g id="shape3-7" v:mID="3" v:groupContext="shape" transform="translate(192.25,-354.482)">
+ <title>Rectangle.3</title>
+ <desc>Payload</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="382.884" width="108" height="18"/>
+ <g id="shadow3-8" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="108" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="108" height="18" class="st3"/>
+ <text x="37.95" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Payload</text> </g>
+ <g id="shape4-13" v:mID="4" v:groupContext="shape" transform="translate(136,-311.232)">
+ <title>Rectangle.4</title>
+ <desc>Flow Key</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="382.884" width="54" height="18"/>
+ <g id="shadow4-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="54" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="54" height="18" class="st3"/>
+ <text x="8.87" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Flow Key</text> </g>
+ <g id="shape5-19" v:mID="5" v:groupContext="shape" transform="translate(465.501,-160.057) rotate(59.7436)">
+ <title>Sheet.5</title>
+ <path d="M0 391.88 L25.1 391.88" class="st5"/>
+ </g>
+ <g id="shape8-25" v:mID="8" v:groupContext="shape" transform="translate(219.25,-320.169)">
+ <title>Sheet.8</title>
+ <desc>Fields of the packet are used to form a flow Key</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="377.822" width="135" height="28.125"/>
+ <rect x="0" y="363.759" width="135" height="28.125" class="st7"/>
+ <text x="10.7" y="374.22" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Fields of the packet are <tspan
+ x="9.67" dy="1.2em" class="st9">used to form a flow Key</tspan></text> </g>
+ <g id="group13-29" transform="translate(120.25,-266.897)" v:mID="13" v:groupContext="group">
+ <title>Sheet.13</title>
+ <g id="shape11-30" v:mID="11" v:groupContext="shape" transform="translate(85.5,751.143) rotate(180)">
+ <title>Trapezoid</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow11-31" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,-0.345598,-1.97279)" class="st1">
+ <path d="M0 391.88 L85.5 391.88 L60.19 359.26 L25.31 359.26 L0 391.88 Z" class="st2"/>
+ </g>
+ <path d="M0 391.88 L85.5 391.88 L60.19 359.26 L25.31 359.26 L0 391.88 Z" class="st3"/>
+ </g>
+ <g id="shape12-35" v:mID="12" v:groupContext="shape" transform="translate(13.5,-6.525)">
+ <title>Sheet.12</title>
+ <desc>H(..)</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="381.689" width="54" height="20.3906"/>
+ <rect x="0" y="371.494" width="54" height="20.3906" class="st10"/>
+ <text x="16.27" y="385.29" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H(..)</text> </g>
+ </g>
+ <g id="shape14-38" v:mID="14" v:groupContext="shape" transform="translate(-229.872,96.3648) rotate(-90.0429)">
+ <title>Simple Arrow</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ArrowType" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <g id="shadow14-39" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,-1.97305,0.344122)" class="st1">
+ <path d="M0 391.88 L10.18 387.38 L10.18 389.63 L16.71 389.63 L16.71 391.88 L16.71 394.13 L10.18 394.13 L10.18 396.38
+ L0 391.88 Z" class="st12"/>
+ </g>
+ <path d="M0 391.88 L10.18 387.38 L10.18 389.63 L16.71 389.63 L16.71 391.88 L16.71 394.13 L10.18 394.13 L10.18 396.38
+ L0 391.88 Z" class="st13"/>
+ </g>
+ <g id="shape15-43" v:mID="15" v:groupContext="shape" transform="translate(212.5,-271.46)">
+ <title>Sheet.15</title>
+ <desc>Hash function is used to create a flow table index</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="377.822" width="135" height="28.125"/>
+ <rect x="0" y="363.759" width="135" height="28.125" class="st7"/>
+ <text x="9.05" y="374.22" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Hash function is used to <tspan
+ x="7.39" dy="1.2em" class="st9">create a flow table index</tspan></text> </g>
+ <g id="shape58-47" v:mID="58" v:groupContext="shape" transform="translate(199,-221.397)">
+ <title>Rectangle.58</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow58-48" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape59-53" v:mID="59" v:groupContext="shape" transform="translate(232.75,-221.397)">
+ <title>Rectangle.59</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow59-54" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape60-59" v:mID="60" v:groupContext="shape" transform="translate(280,-221.397)">
+ <title>Rectangle.60</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow60-60" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape61-65" v:mID="61" v:groupContext="shape" transform="translate(313.75,-221.397)">
+ <title>Rectangle.61</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow61-66" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape62-71" v:mID="62" v:groupContext="shape" transform="translate(361,-221.397)">
+ <title>Rectangle.62</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow62-72" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape63-76" v:mID="63" v:groupContext="shape" transform="translate(394.75,-221.397)">
+ <title>Rectangle.63</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow63-77" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape64-81" v:mID="64" v:groupContext="shape" transform="translate(199,-198.897)">
+ <title>Rectangle.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow64-82" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape65-86" v:mID="65" v:groupContext="shape" transform="translate(232.75,-198.897)">
+ <title>Rectangle.65</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow65-87" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape66-91" v:mID="66" v:groupContext="shape" transform="translate(280,-198.897)">
+ <title>Rectangle.66</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow66-92" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape67-96" v:mID="67" v:groupContext="shape" transform="translate(313.75,-198.897)">
+ <title>Rectangle.67</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow67-97" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape68-101" v:mID="68" v:groupContext="shape" transform="translate(361,-198.897)">
+ <title>Rectangle.68</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow68-102" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape69-106" v:mID="69" v:groupContext="shape" transform="translate(394.75,-198.897)">
+ <title>Rectangle.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow69-107" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape70-111" v:mID="70" v:groupContext="shape" transform="translate(199,-162.897)">
+ <title>Rectangle.70</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow70-112" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape71-117" v:mID="71" v:groupContext="shape" transform="translate(232.75,-162.897)">
+ <title>Rectangle.71</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow71-118" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape72-123" v:mID="72" v:groupContext="shape" transform="translate(280,-162.897)">
+ <title>Rectangle.72</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow72-124" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape73-129" v:mID="73" v:groupContext="shape" transform="translate(313.75,-162.897)">
+ <title>Rectangle.73</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow73-130" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape74-135" v:mID="74" v:groupContext="shape" transform="translate(361,-162.897)">
+ <title>Rectangle.74</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow74-136" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape75-141" v:mID="75" v:groupContext="shape" transform="translate(394.75,-162.897)">
+ <title>Rectangle.75</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow75-142" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape76-147" v:mID="76" v:groupContext="shape" transform="translate(199,-126.397)">
+ <title>Rectangle.76</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow76-148" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape77-152" v:mID="77" v:groupContext="shape" transform="translate(232.75,-126.397)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow77-153" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape78-157" v:mID="78" v:groupContext="shape" transform="translate(280,-126.397)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow78-158" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape79-162" v:mID="79" v:groupContext="shape" transform="translate(313.75,-126.397)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow79-163" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape80-167" v:mID="80" v:groupContext="shape" transform="translate(361,-126.397)">
+ <title>Rectangle.80</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow80-168" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="385.58" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape81-173" v:mID="81" v:groupContext="shape" transform="translate(394.75,-126.397)">
+ <title>Rectangle.81</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="382.884" width="42.75" height="18"/>
+ <g id="shadow81-174" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="385.58" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape82-179" v:mID="82" v:groupContext="shape" transform="translate(196.75,-117.397)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow82-180" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="256.384" width="245.25" height="135.5" class="st15"/>
+ </g>
+ <rect x="0" y="256.384" width="245.25" height="135.5" class="st16"/>
+ </g>
+ <g id="shape83-184" v:mID="83" v:groupContext="shape" transform="translate(554.884,123.862) rotate(90)">
+ <title>Sheet.83</title>
+ <path d="M0 391.88 L99 391.88" class="st17"/>
+ </g>
+ <g id="shape84-187" v:mID="84" v:groupContext="shape" transform="translate(208,-248.397)">
+ <title>Sheet.84</title>
+ <desc>Load Balancing Flow Table</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="91.75" cy="386.259" width="183.5" height="11.25"/>
+ <rect x="0" y="380.634" width="183.5" height="11.25" class="st18"/>
+ <text x="26.14" y="389.86" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Load Balancing Flow Table</text> </g>
+ <g id="shape85-190" v:mID="85" v:groupContext="shape" transform="translate(190,-157.835)">
+ <title>Rectangle.85</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow85-191" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="363.759" width="261" height="28.125" class="st19"/>
+ </g>
+ <rect x="0" y="363.759" width="261" height="28.125" class="st20"/>
+ </g>
+ <g id="shape86-195" v:mID="86" v:groupContext="shape" transform="translate(163,-169.022)">
+ <title>Sheet.86</title>
+ <path d="M0 391.88 L18.76 391.88" class="st5"/>
+ </g>
+ <g id="shape87-200" v:mID="87" v:groupContext="shape" transform="translate(19,-198.107)">
+ <title>Sheet.87</title>
+ <desc>Hash value used to index Flow table</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="377.822" width="135" height="28.125"/>
+ <rect x="0" y="363.759" width="135" height="28.125" class="st7"/>
+ <text x="6.79" y="374.22" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Hash value used to index <tspan
+ x="42.16" dy="1.2em" class="st9">Flow table</tspan></text> </g>
+ <g id="shape88-204" v:mID="88" v:groupContext="shape" transform="translate(551.381,21.2928) rotate(87.9001)">
+ <title>Sheet.88</title>
+ <path d="M0 391.88 L20.86 391.88" class="st5"/>
+ </g>
+ <g id="shape89-209" v:mID="89" v:groupContext="shape" transform="translate(494.785,297.309) rotate(131.987)">
+ <title>Sheet.89</title>
+ <path d="M0 391.88 L30.84 391.88" class="st5"/>
+ </g>
+ <g id="shape90-214" v:mID="90" v:groupContext="shape" transform="translate(228.25,-92.5847)">
+ <title>Rectangle.90</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow90-215" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape91-220" v:mID="91" v:groupContext="shape" transform="translate(340.75,-92.5847)">
+ <title>Rectangle.91</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow91-221" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="group96-226" transform="translate(253,-51.4597)" v:mID="96" v:groupContext="group">
+ <title>Sheet.96</title>
+ <g id="shape97-227" v:mID="97" v:groupContext="shape" transform="translate(85.5,751.143) rotate(180)">
+ <title>Trapezoid</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow97-228" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,-0.345598,-1.97279)" class="st1">
+ <path d="M0 391.88 L85.5 391.88 L60.19 359.26 L25.31 359.26 L0 391.88 Z" class="st2"/>
+ </g>
+ <path d="M0 391.88 L85.5 391.88 L60.19 359.26 L25.31 359.26 L0 391.88 Z" class="st3"/>
+ </g>
+ <g id="shape98-232" v:mID="98" v:groupContext="shape" transform="translate(13.5,-6.525)">
+ <title>Sheet.98</title>
+ <desc>Match</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="381.689" width="54" height="20.3906"/>
+ <rect x="0" y="371.494" width="54" height="20.3906" class="st10"/>
+ <text x="10.98" y="385.29" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Match</text> </g>
+ </g>
+ <g id="shape99-235" v:mID="99" v:groupContext="shape" transform="translate(532.137,0.00916548) rotate(54.6508)">
+ <title>Sheet.99</title>
+ <path d="M0 391.88 L93.23 391.88" class="st5"/>
+ </g>
+ <g id="shape100-240" v:mID="100" v:groupContext="shape" transform="translate(683.134,224.487) rotate(90)">
+ <title>Sheet.100</title>
+ <path d="M0 391.88 L77.15 391.88" class="st5"/>
+ </g>
+ <g id="shape101-245" v:mID="101" v:groupContext="shape" transform="translate(692.213,476.024) rotate(129.078)">
+ <title>Sheet.101</title>
+ <path d="M0 391.88 L95.37 391.88" class="st5"/>
+ </g>
+ <g id="shape102-250" v:mID="102" v:groupContext="shape" transform="translate(293.5,-97.0847)">
+ <title>Rectangle.102</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="382.884" width="31.5" height="18"/>
+ <g id="shadow102-251" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape103-256" v:mID="103" v:groupContext="shape" transform="translate(169.75,-55.9597)">
+ <title>Rectangle.103</title>
+ <desc>Flow Key</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="382.884" width="54" height="18"/>
+ <g id="shadow103-257" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="54" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="54" height="18" class="st3"/>
+ <text x="8.87" y="385.88" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Flow Key</text> </g>
+ <g id="shape104-262" v:mID="104" v:groupContext="shape" transform="translate(226,-64.9597)">
+ <title>Sheet.104</title>
+ <path d="M0 391.88 L34.34 391.88" class="st5"/>
+ </g>
+ <g id="shape105-267" v:mID="105" v:groupContext="shape" transform="translate(54,-82.4597)">
+ <title>Sheet.105</title>
+ <desc>Retrieved keys are matched with input key</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="377.822" width="135" height="28.125"/>
+ <rect x="0" y="363.759" width="135" height="28.125" class="st7"/>
+ <text x="22.51" y="374.22" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Retrieved keys are <tspan
+ x="9.83" dy="1.2em" class="st9">matched with input key</tspan></text> </g>
+ <g id="shape106-271" v:mID="106" v:groupContext="shape" transform="translate(271,-23.9597)">
+ <title>Rectangle.106</title>
+ <desc>Action</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="27" cy="382.884" width="54" height="18"/>
+ <g id="shadow106-272" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="373.884" width="54" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="373.884" width="54" height="18" class="st3"/>
+ <text x="8.67" y="387.08" class="st21" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action</text> </g>
+ <g id="shape111-277" v:mID="111" v:groupContext="shape" transform="translate(-94.8716,350.902) rotate(-90.0429)">
+ <title>Simple Arrow.111</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ArrowType" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <g id="shadow111-278" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,-1.97305,0.344122)" class="st1">
+ <path d="M0 391.88 L10.18 387.38 L10.18 389.63 L16.71 389.63 L16.71 391.88 L16.71 394.13 L10.18 394.13 L10.18 396.38
+ L0 391.88 Z" class="st12"/>
+ </g>
+ <path d="M0 391.88 L10.18 387.38 L10.18 389.63 L16.71 389.63 L16.71 391.88 L16.71 394.13 L10.18 394.13 L10.18 396.38
+ L0 391.88 Z" class="st13"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i4.svg b/doc/guides/prog_guide/img/efd_i4.svg
new file mode 100644
index 0000000..5be5ccd
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i4.svg
@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i4.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="2.78993in" height="1.78151in"
+ viewBox="0 0 200.875 128.269" xml:space="preserve" color-interpolation-filters="sRGB" class="st19">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:none;stroke:none;stroke-width:0.25}
+ .st2 {fill:#5b9bd5;font-family:Calibri;font-size:0.75em}
+ .st3 {font-size:1em}
+ .st4 {fill:#5b9bd5;font-family:Calibri;font-size:0.75em;font-weight:bold}
+ .st5 {fill:#deebf6;stroke:none;stroke-width:0.25}
+ .st6 {stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st7 {stroke:#5b9bd5;stroke-dasharray:0.75,1.5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st8 {fill:#ff0000;font-size:1em}
+ .st9 {baseline-shift:-28.8834%;font-size:0.577667em}
+ .st10 {fill:#ff0000;font-family:Calibri;font-size:0.75em}
+ .st11 {fill:#5b9bd5;font-size:1em}
+ .st12 {visibility:visible}
+ .st13 {fill:#5b9bd5;fill-opacity:0.25;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.25}
+ .st14 {fill:url(#grad0-73);stroke:#40709c;stroke-width:0.75}
+ .st15 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st16 {fill:#00fefe;font-size:1em}
+ .st17 {fill:#00b050}
+ .st18 {stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st19 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Patterns_And_Gradients">
+ <linearGradient id="grad0-73" x1="0" y1="0" x2="1" y2="0" gradientTransform="rotate(250 0.5 0.5)">
+ <stop offset="0" stop-color="#4f87bb" stop-opacity="1"/>
+ <stop offset="0.48" stop-color="#4f87bb" stop-opacity="1"/>
+ <stop offset="0.82" stop-color="#5b9bd5" stop-opacity="1"/>
+ </linearGradient>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(18.25,-59.3478)">
+ <title>Sheet.2</title>
+ <desc>Key 1 Key 2 ... Key 28</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="18" cy="121.519" width="36" height="13.5"/>
+ <rect x="0" y="114.769" width="36" height="13.5" class="st1"/>
+ <text x="8.09" y="108.02" class="st2" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1<v:newlineChar/><tspan
+ x="8.09" dy="1.2em" class="st3">Key </tspan>2<v:newlineChar/><tspan x="14.59" dy="1.2em" class="st3">...<v:newlineChar/></tspan><tspan
+ x="5.81" dy="1.2em" class="st3">Key </tspan>28</text> </g>
+ <g id="shape9-7" v:mID="9" v:groupContext="shape" transform="translate(52,-91.9728)">
+ <title>Sheet.9</title>
+ <desc>Target Value</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="17.4375" cy="122.644" width="34.88" height="11.25"/>
+ <rect x="0" y="117.019" width="34.875" height="11.25" class="st1"/>
+ <text x="5.43" y="119.94" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Target <tspan x="6.77"
+ dy="1.2em" class="st3">Value</tspan></text> </g>
+ <g id="shape11-11" v:mID="11" v:groupContext="shape" transform="translate(52,-42.4728)">
+ <title>Sheet.11</title>
+ <desc>0 1 0</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="17.4375" cy="105.769" width="34.88" height="45"/>
+ <rect x="0" y="83.2689" width="34.875" height="45" class="st5"/>
+ <text x="15.16" y="92.27" class="st2" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0<v:newlineChar/><tspan
+ x="15.16" dy="1.2em" class="st3">1<v:newlineChar/><v:newlineChar/></tspan><tspan x="15.16" dy="2.4em"
+ class="st3">0</tspan></text> </g>
+ <g id="shape8-16" v:mID="8" v:groupContext="shape" transform="translate(180.269,21.6711) rotate(90)">
+ <title>Sheet.8</title>
+ <path d="M0 128.27 L69.75 128.27" class="st6"/>
+ </g>
+ <g id="shape10-19" v:mID="10" v:groupContext="shape" transform="translate(215.144,21.6711) rotate(90)">
+ <title>Sheet.10</title>
+ <path d="M0 128.27 L69.75 128.27" class="st6"/>
+ </g>
+ <g id="shape4-22" v:mID="4" v:groupContext="shape" transform="translate(22.75,-77.3478)">
+ <title>Sheet.4</title>
+ <path d="M0 128.27 L157.5 128.27" class="st7"/>
+ </g>
+ <g id="shape5-25" v:mID="5" v:groupContext="shape" transform="translate(23.875,-66.0978)">
+ <title>Sheet.5</title>
+ <path d="M0 128.27 L158.62 128.27" class="st7"/>
+ </g>
+ <g id="shape6-28" v:mID="6" v:groupContext="shape" transform="translate(22.75,-54.8478)">
+ <title>Sheet.6</title>
+ <path d="M0 128.27 L159.75 128.27" class="st7"/>
+ </g>
+ <g id="shape7-31" v:mID="7" v:groupContext="shape" transform="translate(22.75,-87.4728)">
+ <title>Sheet.7</title>
+ <path d="M0 128.27 L155.25 128.27" class="st6"/>
+ </g>
+ <g id="shape12-34" v:mID="12" v:groupContext="shape" transform="translate(91.9375,-42.4728)">
+ <title>Sheet.12</title>
+ <desc>0 0 0</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="8.4375" cy="105.769" width="16.88" height="45"/>
+ <rect x="0" y="83.2689" width="16.875" height="45" class="st1"/>
+ <text x="6.16" y="92.27" class="st2" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0<v:newlineChar/><tspan
+ x="6.16" dy="1.2em" class="st8">0<v:newlineChar/><v:newlineChar/></tspan><tspan x="6.16" dy="2.4em"
+ class="st3">0</tspan></text> </g>
+ <g id="shape26-39" v:mID="26" v:groupContext="shape" transform="translate(86.875,-88.5978)">
+ <title>Sheet.26</title>
+ <desc>H1(x)</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="14.0625" cy="122.644" width="28.13" height="11.25"/>
+ <rect x="0" y="117.019" width="28.125" height="11.25" class="st1"/>
+ <text x="5.03" y="125.34" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st9" v:baseFontSize="8">1</tspan><tspan dy="0.164em" class="st3">(</tspan>x)</text> </g>
+ <g id="shape27-44" v:mID="27" v:groupContext="shape" transform="translate(115,-42.4728)">
+ <title>Sheet.27</title>
+ <desc>1 1 0</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="8.4375" cy="105.769" width="16.88" height="45"/>
+ <rect x="0" y="83.2689" width="16.875" height="45" class="st1"/>
+ <text x="6.16" y="92.27" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1<v:newlineChar/><tspan
+ x="6.16" dy="1.2em" class="st11">1<v:newlineChar/><v:newlineChar/></tspan><tspan x="6.16" dy="2.4em"
+ class="st11">0</tspan></text> </g>
+ <g id="shape28-49" v:mID="28" v:groupContext="shape" transform="translate(109.938,-88.5978)">
+ <title>Sheet.28</title>
+ <desc>H2(x)</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="14.0625" cy="122.644" width="28.13" height="11.25"/>
+ <rect x="0" y="117.019" width="28.125" height="11.25" class="st1"/>
+ <text x="5.03" y="125.34" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st9" v:baseFontSize="8">2</tspan><tspan dy="0.164em" class="st3">(</tspan>x)</text> </g>
+ <g id="shape29-54" v:mID="29" v:groupContext="shape" transform="translate(155.5,-42.4728)">
+ <title>Sheet.29</title>
+ <desc>0 1 0</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="8.4375" cy="105.769" width="16.88" height="45"/>
+ <rect x="0" y="83.2689" width="16.875" height="45" class="st1"/>
+ <text x="6.16" y="92.27" class="st2" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0<v:newlineChar/><tspan
+ x="6.16" dy="1.2em" class="st3">1<v:newlineChar/><v:newlineChar/></tspan><tspan x="6.16" dy="2.4em"
+ class="st3">0</tspan></text> </g>
+ <g id="shape30-59" v:mID="30" v:groupContext="shape" transform="translate(150.438,-88.5978)">
+ <title>Sheet.30</title>
+ <desc>Hm(x)</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="14.0625" cy="122.644" width="28.13" height="11.25"/>
+ <rect x="0" y="117.019" width="28.125" height="11.25" class="st1"/>
+ <text x="4.24" y="125.34" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st9" v:baseFontSize="8">m</tspan><tspan dy="0.164em" class="st3">(</tspan>x)</text> </g>
+ <g id="shape31-64" v:mID="31" v:groupContext="shape" transform="translate(130.188,-89.7228)">
+ <title>Sheet.31</title>
+ <desc>…..</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="14.0625" cy="122.644" width="28.13" height="11.25"/>
+ <rect x="0" y="117.019" width="28.125" height="11.25" class="st1"/>
+ <text x="8.46" y="125.34" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>…..</text> </g>
+ <g id="shape32-67" v:mID="32" v:groupContext="shape" transform="translate(34,-23.3478)">
+ <title>Sheet.32</title>
+ <desc>Store m for this group of keys</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="66.375" cy="122.644" width="132.75" height="11.25"/>
+ <g id="shadow32-68" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st12">
+ <rect x="0" y="117.019" width="132.75" height="11.25" class="st13"/>
+ </g>
+ <rect x="0" y="117.019" width="132.75" height="11.25" class="st14"/>
+ <text x="6.32" y="125.64" class="st15" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Store <tspan
+ class="st16">m</tspan> for this group of keys</text> </g>
+ <g id="shape36-76" v:mID="36" v:groupContext="shape" transform="translate(159.381,-100.964)">
+ <title>Sheet.36</title>
+ <path d="M3.45 125.81 L6.87 119.34 L7.99 120.16 L3.87 128.27 L0 124.35 L0.86 123.13 L3.45 125.81 Z" class="st17"/>
+ </g>
+ <g id="group44-79" transform="translate(97.5625,-100.086)" v:mID="44" v:groupContext="group">
+ <title>Sheet.44</title>
+ <g id="shape42-80" v:mID="42" v:groupContext="shape" transform="translate(85.4972,28.6255) rotate(41.8011)">
+ <title>Sheet.42</title>
+ <path d="M0 128.27 L6.04 128.27" class="st18"/>
+ </g>
+ <g id="shape43-83" v:mID="43" v:groupContext="shape" transform="translate(-87.9035,34.8564) rotate(-43.2597)">
+ <title>Sheet.43</title>
+ <path d="M0 128.27 L5.87 128.27" class="st18"/>
+ </g>
+ </g>
+ <g id="group45-86" transform="translate(120.625,-100.086)" v:mID="45" v:groupContext="group">
+ <title>Sheet.45</title>
+ <g id="shape46-87" v:mID="46" v:groupContext="shape" transform="translate(85.4972,28.6255) rotate(41.8011)">
+ <title>Sheet.46</title>
+ <path d="M0 128.27 L6.04 128.27" class="st18"/>
+ </g>
+ <g id="shape47-90" v:mID="47" v:groupContext="shape" transform="translate(-87.9035,34.8564) rotate(-43.2597)">
+ <title>Sheet.47</title>
+ <path d="M0 128.27 L5.87 128.27" class="st18"/>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i5.svg b/doc/guides/prog_guide/img/efd_i5.svg
new file mode 100644
index 0000000..b6540ba
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i5.svg
@@ -0,0 +1,183 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i5.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="8.34375in" height="2.86443in"
+ viewBox="0 0 600.75 206.239" xml:space="preserve" color-interpolation-filters="sRGB" class="st14">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:1.5em}
+ .st5 {fill:#feffff;font-family:Calibri;font-size:1.16666em}
+ .st6 {marker-end:url(#mrkr5-36);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st7 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st8 {stroke:#5b9bd5;stroke-dasharray:1.5,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st9 {fill:none;stroke:none;stroke-width:0.25}
+ .st10 {fill:#5b9bd5;font-family:Calibri;font-size:1.5em;font-weight:bold}
+ .st11 {baseline-shift:-32.4951%;font-size:0.649902em}
+ .st12 {fill:#deebf6;stroke:#0070c0;stroke-width:1}
+ .st13 {fill:#5b9bd5;font-family:Calibri;font-size:1.5em}
+ .st14 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-36" class="st7" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape2-1" v:mID="2" v:groupContext="shape" transform="translate(93.0294,-158.5)">
+ <title>Rectangle</title>
+ <desc>All Keys</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="216" cy="192.739" width="432" height="27"/>
+ <g id="shadow2-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="179.239" width="432" height="27" class="st2"/>
+ </g>
+ <rect x="0" y="179.239" width="432" height="27" class="st3"/>
+ <text x="187.88" y="198.14" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>All Keys</text> </g>
+ <g id="shape3-7" v:mID="3" v:groupContext="shape" transform="translate(21.0294,-77.5)">
+ <title>Rectangle.3</title>
+ <desc>Group 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="188.239" width="108" height="36"/>
+ <g id="shadow3-8" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="170.239" width="108" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="170.239" width="108" height="36" class="st3"/>
+ <text x="30.97" y="192.44" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group 1</text> </g>
+ <g id="shape4-13" v:mID="4" v:groupContext="shape" transform="translate(156.029,-77.5)">
+ <title>Rectangle.4</title>
+ <desc>Group 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="188.239" width="108" height="36"/>
+ <g id="shadow4-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="170.239" width="108" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="170.239" width="108" height="36" class="st3"/>
+ <text x="30.97" y="192.44" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group 2</text> </g>
+ <g id="shape5-19" v:mID="5" v:groupContext="shape" transform="translate(291.029,-77.5)">
+ <title>Rectangle.5</title>
+ <desc>Group 3</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="188.239" width="108" height="36"/>
+ <g id="shadow5-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="170.239" width="108" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="170.239" width="108" height="36" class="st3"/>
+ <text x="30.97" y="192.44" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group 3</text> </g>
+ <g id="shape6-25" v:mID="6" v:groupContext="shape" transform="translate(471.029,-77.5)">
+ <title>Rectangle.6</title>
+ <desc>Group X</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="188.239" width="108" height="36"/>
+ <g id="shadow6-26" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="170.239" width="108" height="36" class="st2"/>
+ </g>
+ <rect x="0" y="170.239" width="108" height="36" class="st3"/>
+ <text x="30.88" y="192.44" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group X</text> </g>
+ <g id="shape7-31" v:mID="7" v:groupContext="shape" transform="translate(359.05,247.819) rotate(165.964)">
+ <title>Sheet.7</title>
+ <path d="M0 206.24 L178.5 206.24" class="st6"/>
+ </g>
+ <g id="shape8-37" v:mID="8" v:groupContext="shape" transform="translate(428.903,215.562) rotate(144.462)">
+ <title>Sheet.8</title>
+ <path d="M0 206.24 L70.39 206.24" class="st6"/>
+ </g>
+ <g id="shape9-42" v:mID="9" v:groupContext="shape" transform="translate(470.075,-81.0976) rotate(51.3402)">
+ <title>Sheet.9</title>
+ <path d="M0 206.24 L50.59 206.24" class="st6"/>
+ </g>
+ <g id="shape10-47" v:mID="10" v:groupContext="shape" transform="translate(364.228,-150.976) rotate(15.5241)">
+ <title>Sheet.10</title>
+ <path d="M0 206.24 L161.1 206.24" class="st6"/>
+ </g>
+ <g id="shape11-52" v:mID="11" v:groupContext="shape" transform="translate(408.029,-95.5)">
+ <title>Sheet.11</title>
+ <path d="M0 206.24 L45 206.24" class="st8"/>
+ </g>
+ <g id="shape12-55" v:mID="12" v:groupContext="shape" transform="translate(48.0294,-50.5)">
+ <title>Sheet.12</title>
+ <desc>H7</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="192.739" width="45" height="27"/>
+ <rect x="0" y="179.239" width="45" height="27" class="st9"/>
+ <text x="13.86" y="198.14" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan
+ dy="-0.284em" class="st11" v:baseFontSize="18">7</tspan></text> </g>
+ <g id="shape13-59" v:mID="13" v:groupContext="shape" transform="translate(192.029,-50.5)">
+ <title>Sheet.13</title>
+ <desc>H267</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="192.739" width="45" height="27"/>
+ <rect x="0" y="179.239" width="45" height="27" class="st9"/>
+ <text x="7.93" y="198.14" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st11" v:baseFontSize="18">267</tspan></text> </g>
+ <g id="shape14-63" v:mID="14" v:groupContext="shape" transform="translate(318.029,-50.5)">
+ <title>Sheet.14</title>
+ <desc>H46</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="192.739" width="45" height="27"/>
+ <rect x="0" y="179.239" width="45" height="27" class="st9"/>
+ <text x="10.89" y="198.14" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan
+ dy="-0.284em" class="st11" v:baseFontSize="18">46</tspan></text> </g>
+ <g id="shape15-67" v:mID="15" v:groupContext="shape" transform="translate(502.529,-50.5)">
+ <title>Sheet.15</title>
+ <desc>H132</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="22.5" cy="192.739" width="45" height="27"/>
+ <rect x="0" y="179.239" width="45" height="27" class="st9"/>
+ <text x="7.93" y="198.14" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>H<tspan dy="-0.284em"
+ class="st11" v:baseFontSize="18">132</tspan></text> </g>
+ <g id="shape16-71" v:mID="16" v:groupContext="shape" transform="translate(111.029,-19)">
+ <title>Sheet.16</title>
+ <desc>Store hash function index for each group of keys</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="189" cy="192.739" width="378" height="27"/>
+ <rect x="0" y="179.239" width="378" height="27" class="st12"/>
+ <text x="12.27" y="198.14" class="st13" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Store hash function index for each group of keys</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i6.svg b/doc/guides/prog_guide/img/efd_i6.svg
new file mode 100644
index 0000000..9aee30b
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i6.svg
@@ -0,0 +1,1254 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i6.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="8.2496in" height="5.89673in"
+ viewBox="0 0 593.971 424.565" xml:space="preserve" color-interpolation-filters="sRGB" class="st27">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st5 {fill:#feffff;font-family:Calibri;font-size:0.75em}
+ .st6 {fill:none;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st7 {fill:none;stroke:#2e75b5;stroke-width:2.25}
+ .st8 {fill:#305497;stroke:#2e75b5;stroke-width:1}
+ .st9 {fill:#feffff;font-family:Calibri;font-size:0.833336em;font-weight:bold}
+ .st10 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2)}
+ .st11 {fill:#5b9bd5}
+ .st12 {stroke:#c7c8c8;stroke-width:0.25}
+ .st13 {fill:#acccea;stroke:#c7c8c8;stroke-width:0.25}
+ .st14 {fill:#feffff;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st15 {fill:#ed7d31;stroke:#c7c8c8;stroke-width:0.25}
+ .st16 {fill:#deebf6;stroke:#c7c8c8;stroke-width:0.25}
+ .st17 {marker-end:url(#mrkr5-212);stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st18 {fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st19 {fill:none;stroke:#2e75b5;stroke-width:1}
+ .st20 {fill:#5b9bd5;font-family:Calibri;font-size:1.00001em}
+ .st21 {fill:none;stroke:none;stroke-width:0.25}
+ .st22 {font-size:1em}
+ .st23 {fill:#ffffff}
+ .st24 {stroke:#5b9bd5;stroke-dasharray:1.5,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st25 {marker-end:url(#mrkr5-444);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st26 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st27 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-212" class="st18" v:arrowType="5" v:arrowSize="2" v:setback="5.8" refX="-5.8" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ <marker id="mrkr5-444" class="st26" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(319.501,-335.688)">
+ <title>Rectangle.58</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow3-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape4-7" v:mID="4" v:groupContext="shape" transform="translate(353.251,-335.688)">
+ <title>Rectangle.59</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow4-8" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape5-13" v:mID="5" v:groupContext="shape" transform="translate(400.501,-335.688)">
+ <title>Rectangle.60</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow5-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape6-19" v:mID="6" v:groupContext="shape" transform="translate(434.251,-335.688)">
+ <title>Rectangle.61</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow6-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape7-25" v:mID="7" v:groupContext="shape" transform="translate(481.501,-335.688)">
+ <title>Rectangle.62</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow7-26" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape8-30" v:mID="8" v:groupContext="shape" transform="translate(515.251,-335.688)">
+ <title>Rectangle.63</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow8-31" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape9-35" v:mID="9" v:groupContext="shape" transform="translate(319.501,-313.188)">
+ <title>Rectangle.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow9-36" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape10-40" v:mID="10" v:groupContext="shape" transform="translate(353.251,-313.188)">
+ <title>Rectangle.65</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow10-41" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape11-45" v:mID="11" v:groupContext="shape" transform="translate(400.501,-313.188)">
+ <title>Rectangle.66</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow11-46" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape12-50" v:mID="12" v:groupContext="shape" transform="translate(434.251,-313.188)">
+ <title>Rectangle.67</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow12-51" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape13-55" v:mID="13" v:groupContext="shape" transform="translate(481.501,-313.188)">
+ <title>Rectangle.68</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow13-56" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape14-60" v:mID="14" v:groupContext="shape" transform="translate(515.251,-313.188)">
+ <title>Rectangle.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow14-61" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape15-65" v:mID="15" v:groupContext="shape" transform="translate(319.501,-277.188)">
+ <title>Rectangle.70</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow15-66" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape16-71" v:mID="16" v:groupContext="shape" transform="translate(353.251,-277.188)">
+ <title>Rectangle.71</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow16-72" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape17-77" v:mID="17" v:groupContext="shape" transform="translate(400.501,-277.188)">
+ <title>Rectangle.72</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow17-78" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape18-83" v:mID="18" v:groupContext="shape" transform="translate(434.251,-277.188)">
+ <title>Rectangle.73</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow18-84" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape19-89" v:mID="19" v:groupContext="shape" transform="translate(481.501,-277.188)">
+ <title>Rectangle.74</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow19-90" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape20-95" v:mID="20" v:groupContext="shape" transform="translate(515.251,-277.188)">
+ <title>Rectangle.75</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow20-96" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape21-101" v:mID="21" v:groupContext="shape" transform="translate(319.501,-240.687)">
+ <title>Rectangle.76</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow21-102" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape22-106" v:mID="22" v:groupContext="shape" transform="translate(353.251,-240.687)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow22-107" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape23-111" v:mID="23" v:groupContext="shape" transform="translate(400.501,-240.687)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow23-112" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape24-116" v:mID="24" v:groupContext="shape" transform="translate(434.251,-240.687)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow24-117" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape25-121" v:mID="25" v:groupContext="shape" transform="translate(481.501,-240.687)">
+ <title>Rectangle.80</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow25-122" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape26-127" v:mID="26" v:groupContext="shape" transform="translate(515.251,-240.687)">
+ <title>Rectangle.81</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow26-128" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape27-133" v:mID="27" v:groupContext="shape" transform="translate(317.251,-231.687)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow27-134" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st6"/>
+ </g>
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st7"/>
+ </g>
+ <g id="shape28-138" v:mID="28" v:groupContext="shape" transform="translate(328.501,-362.688)">
+ <title>Sheet.28</title>
+ <desc>Local Table for N Specific Flows Serviced at Node 1</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="110.423" cy="418.94" width="220.85" height="11.25"/>
+ <rect x="0" y="413.315" width="220.846" height="11.25" class="st8"/>
+ <text x="5.77" y="421.94" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Local Table for N Specific Flows Serviced at Node 1</text> </g>
+ <g id="group34-141" transform="translate(66.0294,-165.569)" v:mID="34" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Device)"/>
+ <v:cp v:nameU="SubShapeType" v:lbl="SubShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Load balancer)"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Load balancer</title>
+ <g id="shape35-142" v:mID="35" v:groupContext="shape" transform="translate(0,-7.33146)">
+ <title>Sheet.35</title>
+ <g id="shadow35-143" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23 Z" class="st10"/>
+ <path d="M0 377.86 L72 377.86" class="st6"/>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23" class="st6"/>
+ </g>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23 Z" class="st11"/>
+ <path d="M0 377.86 L72 377.86" class="st12"/>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23" class="st12"/>
+ </g>
+ <g id="shape36-152" v:mID="36" v:groupContext="shape" transform="translate(8.03054,-12.9324)">
+ <title>Sheet.36</title>
+ <g id="shadow36-153" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M45.34 421.81 L41.2 422.66 L44.12 424.56 L49.68 423.16 L48.75 417.51 L45.8 415.59 L46.69 419.68 L36.97
+ 413.34 L35.6 415.45 L45.34 421.81 ZM50.83 405.36 L39.2 405.36 L39.2 407.88 L50.8 407.88 L47.82 410.83
+ L51.34 410.83 L55.21 406.61 L51.32 402.39 L47.83 402.39 L50.83 405.36 ZM46.49 392.01 L36.75 398.37
+ L38.13 400.48 L47.84 394.14 L46.96 398.23 L49.91 396.31 L50.84 390.66 L45.28 389.26 L42.36 391.16
+ L46.49 392.01 ZM27.71 397.16 C22.66 397.16 18.58 401.25 18.58 406.29 C18.58 411.33 22.66 415.42
+ 27.71 415.42 C32.75 415.42 36.84 411.33 36.84 406.29 C36.84 401.25 32.75 397.16 27.71 397.16 ZM27.71
+ 400.04 C31.15 400.04 33.96 402.84 33.96 406.29 C33.96 409.74 31.15 412.54 27.71 412.54 C24.26 412.54
+ 21.46 409.74 21.46 406.29 C21.46 402.84 24.26 400.04 27.71 400.04 ZM11.64 405.04 L0 405.04 L0 407.56
+ L11.6 407.56 L8.62 410.51 L12.14 410.51 L16.01 406.29 L12.12 402.07 L8.64 402.07 L11.64 405.04 Z"
+ class="st2"/>
+ </g>
+ <path d="M45.34 421.81 L41.2 422.66 L44.12 424.56 L49.68 423.16 L48.75 417.51 L45.8 415.59 L46.69 419.68 L36.97 413.34
+ L35.6 415.45 L45.34 421.81 ZM50.83 405.36 L39.2 405.36 L39.2 407.88 L50.8 407.88 L47.82 410.83 L51.34
+ 410.83 L55.21 406.61 L51.32 402.39 L47.83 402.39 L50.83 405.36 ZM46.49 392.01 L36.75 398.37 L38.13 400.48
+ L47.84 394.14 L46.96 398.23 L49.91 396.31 L50.84 390.66 L45.28 389.26 L42.36 391.16 L46.49 392.01 ZM27.71
+ 397.16 C22.66 397.16 18.58 401.25 18.58 406.29 C18.58 411.33 22.66 415.42 27.71 415.42 C32.75 415.42
+ 36.84 411.33 36.84 406.29 C36.84 401.25 32.75 397.16 27.71 397.16 ZM27.71 400.04 C31.15 400.04 33.96
+ 402.84 33.96 406.29 C33.96 409.74 31.15 412.54 27.71 412.54 C24.26 412.54 21.46 409.74 21.46 406.29
+ C21.46 402.84 24.26 400.04 27.71 400.04 ZM11.64 405.04 L0 405.04 L0 407.56 L11.6 407.56 L8.62 410.51
+ L12.14 410.51 L16.01 406.29 L12.12 402.07 L8.64 402.07 L11.64 405.04 Z" class="st13"/>
+ </g>
+ </g>
+ <g id="shape37-157" v:mID="37" v:groupContext="shape" transform="translate(21.0294,-45.4375)">
+ <title>Rectangle</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow37-158" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="336.433" width="135" height="88.1315" class="st2"/>
+ </g>
+ <rect x="0" y="336.433" width="135" height="88.1315" class="st3"/>
+ </g>
+ <g id="shape38-162" v:mID="38" v:groupContext="shape" transform="translate(34.693,-126.438)">
+ <title>Sheet.38</title>
+ <desc>EFD Table</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="49.3364" cy="415.565" width="98.68" height="18"/>
+ <rect x="0" y="406.565" width="98.6728" height="18" class="st8"/>
+ <text x="24.87" y="419.17" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>EFD Table</text> </g>
+ <g id="shape39-165" v:mID="39" v:groupContext="shape" transform="translate(30.0294,-99.4375)">
+ <title>Rectangle.39</title>
+ <desc>Group_id</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="26.9182" cy="415.565" width="53.84" height="18"/>
+ <g id="shadow39-166" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st15"/>
+ <text x="7.87" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group_id</text> </g>
+ <g id="shape40-171" v:mID="40" v:groupContext="shape" transform="translate(93.193,-99.4375)">
+ <title>Rectangle.40</title>
+ <desc>Hash index</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="26.9182" cy="415.565" width="53.84" height="18"/>
+ <g id="shadow40-172" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st15"/>
+ <text x="4.64" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Hash index</text> </g>
+ <g id="shape41-177" v:mID="41" v:groupContext="shape" transform="translate(30.193,-82.4275)">
+ <title>Rectangle.41</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow41-178" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape42-182" v:mID="42" v:groupContext="shape" transform="translate(30.193,-66.8125)">
+ <title>Rectangle.42</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow42-183" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape43-187" v:mID="43" v:groupContext="shape" transform="translate(30.1112,-52.1875)">
+ <title>Rectangle.43</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow43-188" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape44-192" v:mID="44" v:groupContext="shape" transform="translate(93.0294,-81.4375)">
+ <title>Rectangle.44</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow44-193" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape45-197" v:mID="45" v:groupContext="shape" transform="translate(93.193,-66.8125)">
+ <title>Rectangle.45</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow45-198" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape46-202" v:mID="46" v:groupContext="shape" transform="translate(93.193,-52.1875)">
+ <title>Rectangle.46</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow46-203" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape47-207" v:mID="47" v:groupContext="shape" transform="translate(374.924,544.022) rotate(135)">
+ <title>Sheet.47</title>
+ <path d="M-0 417.75 A40.674 18.0151 -156.2 0 0 40.24 422.15 L40.49 421.89" class="st17"/>
+ </g>
+ <g id="shape48-213" v:mID="48" v:groupContext="shape" transform="translate(21.0294,-19)">
+ <title>Sheet.48</title>
+ <desc>Supports X*N Flows</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="415.565" width="135" height="18"/>
+ <rect x="0" y="406.565" width="135" height="18" class="st19"/>
+ <text x="19.05" y="419.17" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Supports X*N Flows</text> </g>
+ <g id="shape49-216" v:mID="49" v:groupContext="shape" transform="translate(48.0294,-229.938)">
+ <title>Sheet.49</title>
+ <desc>Frontend Server or Load Balancer</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="400.94" width="108" height="47.25"/>
+ <rect x="0" y="377.315" width="108" height="47.25" class="st21"/>
+ <text x="14.56" y="397.34" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Frontend Server<v:newlineChar/><tspan
+ x="13.16" dy="1.2em" class="st22">or Load Balancer </tspan> </text> </g>
+ <g id="group51-220" transform="translate(223.876,-310.938)" v:mID="51" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server</title>
+ <g id="shape52-221" v:mID="52" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.52</title>
+ <g id="shadow52-222" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape53-226" v:mID="53" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.53</title>
+ <g id="shadow53-227" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape54-231" v:mID="54" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.54</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow54-232" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape59-239" v:mID="59" v:groupContext="shape" transform="translate(277.876,-373.938)">
+ <title>Sheet.59</title>
+ <path d="M-0 424.56 A111.108 53.2538 42.31 0 1 93.83 421.21 L94.14 421.41" class="st17"/>
+ </g>
+ <g id="shape60-244" v:mID="60" v:groupContext="shape" transform="translate(205.876,-283.938)">
+ <title>Sheet.60</title>
+ <desc>Backend Server 1</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.93" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server 1</text> </g>
+ <g id="group61-247" transform="translate(223.876,-207.438)" v:mID="61" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server.61</title>
+ <g id="shape62-248" v:mID="62" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.62</title>
+ <g id="shadow62-249" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape63-253" v:mID="63" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.63</title>
+ <g id="shadow63-254" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape64-258" v:mID="64" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow64-259" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape65-266" v:mID="65" v:groupContext="shape" transform="translate(205.876,-180.437)">
+ <title>Sheet.65</title>
+ <desc>Backend Server 2</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.93" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server 2</text> </g>
+ <g id="group66-269" transform="translate(219.029,-58.9375)" v:mID="66" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server.66</title>
+ <g id="shape67-270" v:mID="67" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.67</title>
+ <g id="shadow67-271" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape68-275" v:mID="68" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.68</title>
+ <g id="shadow68-276" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape69-280" v:mID="69" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow69-281" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape70-288" v:mID="70" v:groupContext="shape" transform="translate(201.029,-26.056)">
+ <title>Sheet.70</title>
+ <desc>Backend Server X</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.86" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server X</text> </g>
+ <g id="shape71-291" v:mID="71" v:groupContext="shape" transform="translate(684.44,239.627) rotate(90)">
+ <title>Sheet.71</title>
+ <path d="M0 424.56 L45 424.56" class="st24"/>
+ </g>
+ <g id="shape72-294" v:mID="72" v:groupContext="shape" transform="translate(6.85967,-22.443) rotate(-38.1076)">
+ <title>Sheet.72</title>
+ <path d="M-0 424.56 A96.1331 44.4001 55.03 0 1 68.24 420.56 L68.51 420.79" class="st17"/>
+ </g>
+ <g id="shape73-299" v:mID="73" v:groupContext="shape" transform="translate(328.501,-135.937)">
+ <title>Rectangle.73</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow73-300" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape74-305" v:mID="74" v:groupContext="shape" transform="translate(362.251,-135.937)">
+ <title>Rectangle.74</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow74-306" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape75-311" v:mID="75" v:groupContext="shape" transform="translate(409.501,-135.937)">
+ <title>Rectangle.75</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow75-312" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape76-317" v:mID="76" v:groupContext="shape" transform="translate(443.251,-135.937)">
+ <title>Rectangle.76</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow76-318" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape77-323" v:mID="77" v:groupContext="shape" transform="translate(490.501,-135.937)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow77-324" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape78-328" v:mID="78" v:groupContext="shape" transform="translate(524.251,-135.937)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow78-329" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape79-333" v:mID="79" v:groupContext="shape" transform="translate(328.501,-113.437)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow79-334" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape80-338" v:mID="80" v:groupContext="shape" transform="translate(362.251,-113.437)">
+ <title>Rectangle.80</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow80-339" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape81-343" v:mID="81" v:groupContext="shape" transform="translate(409.501,-113.437)">
+ <title>Rectangle.81</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow81-344" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape82-348" v:mID="82" v:groupContext="shape" transform="translate(443.251,-113.437)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow82-349" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape83-353" v:mID="83" v:groupContext="shape" transform="translate(490.501,-113.437)">
+ <title>Rectangle.83</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow83-354" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape84-358" v:mID="84" v:groupContext="shape" transform="translate(524.251,-113.437)">
+ <title>Rectangle.84</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow84-359" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape85-363" v:mID="85" v:groupContext="shape" transform="translate(328.501,-77.4375)">
+ <title>Rectangle.85</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow85-364" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape86-369" v:mID="86" v:groupContext="shape" transform="translate(362.251,-77.4375)">
+ <title>Rectangle.86</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow86-370" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape87-375" v:mID="87" v:groupContext="shape" transform="translate(409.501,-77.4375)">
+ <title>Rectangle.87</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow87-376" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape88-381" v:mID="88" v:groupContext="shape" transform="translate(443.251,-77.4375)">
+ <title>Rectangle.88</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow88-382" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape89-387" v:mID="89" v:groupContext="shape" transform="translate(490.501,-77.4375)">
+ <title>Rectangle.89</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow89-388" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape90-393" v:mID="90" v:groupContext="shape" transform="translate(524.251,-77.4375)">
+ <title>Rectangle.90</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow90-394" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape91-399" v:mID="91" v:groupContext="shape" transform="translate(328.501,-40.9375)">
+ <title>Rectangle.91</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow91-400" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape92-404" v:mID="92" v:groupContext="shape" transform="translate(362.251,-40.9375)">
+ <title>Rectangle.92</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow92-405" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape93-409" v:mID="93" v:groupContext="shape" transform="translate(409.501,-40.9375)">
+ <title>Rectangle.93</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow93-410" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape94-414" v:mID="94" v:groupContext="shape" transform="translate(443.251,-40.9375)">
+ <title>Rectangle.94</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow94-415" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape95-419" v:mID="95" v:groupContext="shape" transform="translate(490.501,-40.9375)">
+ <title>Rectangle.95</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow95-420" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape96-425" v:mID="96" v:groupContext="shape" transform="translate(524.251,-40.9375)">
+ <title>Rectangle.96</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow96-426" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape97-431" v:mID="97" v:groupContext="shape" transform="translate(326.251,-31.9375)">
+ <title>Rectangle.97</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow97-432" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st6"/>
+ </g>
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st7"/>
+ </g>
+ <g id="shape98-436" v:mID="98" v:groupContext="shape" transform="translate(337.501,-162.938)">
+ <title>Sheet.98</title>
+ <desc>Local Table for N Specific Flows Serviced at Node X</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="110.423" cy="418.94" width="220.85" height="11.25"/>
+ <rect x="0" y="413.315" width="220.846" height="11.25" class="st8"/>
+ <text x="5.55" y="421.94" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Local Table for N Specific Flows Serviced at Node X</text> </g>
+ <g id="shape99-439" v:mID="99" v:groupContext="shape" transform="translate(-204.342,-29.4449) rotate(-53.7462)">
+ <title>Sheet.99</title>
+ <path d="M0 424.56 L160.37 424.56" class="st25"/>
+ </g>
+ <g id="shape100-445" v:mID="100" v:groupContext="shape" transform="translate(-37.6568,-164.882) rotate(-24.444)">
+ <title>Sheet.100</title>
+ <path d="M0 424.56 L101.71 424.56" class="st25"/>
+ </g>
+ <g id="shape101-450" v:mID="101" v:groupContext="shape" transform="translate(464.049,-50.8578) rotate(50.099)">
+ <title>Sheet.101</title>
+ <path d="M0 424.56 L139.8 424.56" class="st25"/>
+ </g>
+ <g id="shape102-455" v:mID="102" v:groupContext="shape" transform="translate(372.376,-207.438)">
+ <title>Sheet.102</title>
+ <desc>Supports N Flows</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="415.565" width="135" height="18"/>
+ <rect x="0" y="406.565" width="135" height="18" class="st19"/>
+ <text x="25.15" y="419.17" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Supports N Flows</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i7.svg b/doc/guides/prog_guide/img/efd_i7.svg
new file mode 100644
index 0000000..98f8000
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i7.svg
@@ -0,0 +1,790 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i8.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="10.6168in" height="4.81965in"
+ viewBox="0 0 764.409 347.015" xml:space="preserve" color-interpolation-filters="sRGB" class="st27">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st4 {fill:#000000;font-family:Intel Clear;font-size:0.998566em}
+ .st5 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st6 {stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st7 {stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st8 {stroke:#ca8f02;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st9 {stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st10 {fill:#c00000;font-family:Intel Clear;font-size:0.828804em;font-weight:bold}
+ .st11 {fill:#7f6d00;font-family:Intel Clear;font-size:0.828804em;font-weight:bold}
+ .st12 {fill:#00b050;stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st13 {fill:#004280;stroke:#004280;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0149927}
+ .st14 {fill:#00b050;stroke:#00b050;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st15 {fill:#ca8f02;stroke:#ca8f02;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.0299855}
+ .st16 {fill:#004280;font-family:Intel Clear;font-size:0.828804em}
+ .st17 {fill:#ffffff;font-family:Intel Clear;font-size:0.998566em}
+ .st18 {fill:#ffffff;font-family:Intel Clear;font-size:1.49785em}
+ .st19 {visibility:visible}
+ .st20 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st21 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st22 {fill:#feffff;font-family:Symbol;font-size:1.16666em}
+ .st23 {font-size:1em}
+ .st24 {font-family:Calibri;font-size:1em}
+ .st25 {fill:none;stroke:none;stroke-width:0.25}
+ .st26 {fill:#ffffff;font-family:Calibri;font-size:1.00001em}
+ .st27 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(27.7836,-307.505)">
+ <title>Sheet.3</title>
+ <path d="M0 329.94 C-0 328.06 1.54 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.06 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.54 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape4-3" v:mID="4" v:groupContext="shape" transform="translate(27.7836,-307.505)">
+ <title>Sheet.4</title>
+ <path d="M0 329.94 C-0 328.06 1.54 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.06 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.54 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape5-5" v:mID="5" v:groupContext="shape" transform="translate(50.1544,-309.121)">
+ <title>Sheet.5</title>
+ <desc>Key1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1</text> </g>
+ <g id="shape6-9" v:mID="6" v:groupContext="shape" transform="translate(43.6909,-286.954)">
+ <title>Sheet.6</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape7-11" v:mID="7" v:groupContext="shape" transform="translate(27.7836,-266.044)">
+ <title>Sheet.7</title>
+ <path d="M0 330.04 C0 328.16 1.52 326.64 3.41 326.64 L68.51 326.64 C70.4 326.64 71.91 328.16 71.91 330.04 L71.91 343.62
+ C71.91 345.49 70.4 347.02 68.51 347.02 L3.41 347.02 C1.52 347.02 0 345.49 0 343.62 L0 330.04 Z"
+ class="st1"/>
+ </g>
+ <g id="shape8-13" v:mID="8" v:groupContext="shape" transform="translate(27.7836,-266.044)">
+ <title>Sheet.8</title>
+ <path d="M0 330.04 C0 328.16 1.52 326.64 3.41 326.64 L68.51 326.64 C70.4 326.64 71.91 328.16 71.91 330.04 L71.91 343.62
+ C71.91 345.49 70.4 347.02 68.51 347.02 L3.41 347.02 C1.52 347.02 0 345.49 0 343.62 L0 330.04 Z"
+ class="st2"/>
+ </g>
+ <g id="shape9-15" v:mID="9" v:groupContext="shape" transform="translate(50.7572,-267.602)">
+ <title>Sheet.9</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape10-19" v:mID="10" v:groupContext="shape" transform="translate(19.0195,-225.183)">
+ <title>Sheet.10</title>
+ <path d="M0 330.74 C0 328.94 1.46 327.48 3.26 327.48 L87.15 327.48 C88.95 327.48 90.4 328.94 90.4 330.74 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.26 347.02 C1.46 347.02 0 345.56 0 343.76 L0 330.74 Z"
+ class="st1"/>
+ </g>
+ <g id="shape11-21" v:mID="11" v:groupContext="shape" transform="translate(19.0195,-225.183)">
+ <title>Sheet.11</title>
+ <path d="M0 330.74 C0 328.94 1.46 327.48 3.26 327.48 L87.15 327.48 C88.95 327.48 90.4 328.94 90.4 330.74 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.26 347.02 C1.46 347.02 0 345.56 0 343.76 L0 330.74 Z"
+ class="st2"/>
+ </g>
+ <g id="shape12-23" v:mID="12" v:groupContext="shape" transform="translate(28.0373,-226.287)">
+ <title>Sheet.12</title>
+ <desc>0x0102ABCD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.3615" cy="339.824" width="86.73" height="14.3829"/>
+ <path d="M86.72 332.63 L0 332.63 L0 347.02 L86.72 347.02 L86.72 332.63" class="st3"/>
+ <text x="7.12" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102ABCD</text> </g>
+ <g id="shape13-27" v:mID="13" v:groupContext="shape" transform="translate(43.6909,-244.775)">
+ <title>Sheet.13</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape14-29" v:mID="14" v:groupContext="shape" transform="translate(40.7496,-210.444)">
+ <title>Sheet.14</title>
+ <path d="M26.29 334.91 C26.29 338.26 25.84 340.96 25.29 340.96 L14.16 340.96 C13.6 340.96 13.15 343.67 13.15 347.02 C13.15
+ 343.67 12.7 340.96 12.14 340.96 L1.01 340.96 C0.46 340.96 0 338.26 0 334.91" class="st6"/>
+ </g>
+ <g id="shape15-32" v:mID="15" v:groupContext="shape" transform="translate(125.629,-307.625)">
+ <title>Sheet.15</title>
+ <path d="M0 330.04 C0 328.16 1.52 326.64 3.41 326.64 L68.63 326.64 C70.51 326.64 72.03 328.16 72.03 330.04 L72.03 343.62
+ C72.03 345.49 70.51 347.02 68.63 347.02 L3.41 347.02 C1.52 347.02 0 345.49 0 343.62 L0 330.04 Z"
+ class="st1"/>
+ </g>
+ <g id="shape16-34" v:mID="16" v:groupContext="shape" transform="translate(125.629,-307.625)">
+ <title>Sheet.16</title>
+ <path d="M0 330.04 C0 328.16 1.52 326.64 3.41 326.64 L68.63 326.64 C70.51 326.64 72.03 328.16 72.03 330.04 L72.03 343.62
+ C72.03 345.49 70.51 347.02 68.63 347.02 L3.41 347.02 C1.52 347.02 0 345.49 0 343.62 L0 330.04 Z"
+ class="st2"/>
+ </g>
+ <g id="shape17-36" v:mID="17" v:groupContext="shape" transform="translate(148.034,-309.155)">
+ <title>Sheet.17</title>
+ <desc>Key2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key2</text> </g>
+ <g id="shape18-40" v:mID="18" v:groupContext="shape" transform="translate(141.536,-286.954)">
+ <title>Sheet.18</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape19-42" v:mID="19" v:groupContext="shape" transform="translate(125.629,-266.044)">
+ <title>Sheet.19</title>
+ <path d="M0 329.94 C0 328.06 1.54 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.06 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.54 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st1"/>
+ </g>
+ <g id="shape20-44" v:mID="20" v:groupContext="shape" transform="translate(125.629,-266.044)">
+ <title>Sheet.20</title>
+ <path d="M0 329.94 C0 328.06 1.54 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.06 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.54 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st2"/>
+ </g>
+ <g id="shape21-46" v:mID="21" v:groupContext="shape" transform="translate(148.636,-267.636)">
+ <title>Sheet.21</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape22-50" v:mID="22" v:groupContext="shape" transform="translate(116.865,-225.183)">
+ <title>Sheet.22</title>
+ <path d="M0 330.74 C0 328.94 1.46 327.48 3.26 327.48 L87.15 327.48 C88.95 327.48 90.4 328.94 90.4 330.74 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.26 347.02 C1.46 347.02 0 345.56 0 343.76 L0 330.74 Z"
+ class="st1"/>
+ </g>
+ <g id="shape23-52" v:mID="23" v:groupContext="shape" transform="translate(116.865,-225.183)">
+ <title>Sheet.23</title>
+ <path d="M0 330.74 C0 328.94 1.46 327.48 3.26 327.48 L87.15 327.48 C88.95 327.48 90.4 328.94 90.4 330.74 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.26 347.02 C1.46 347.02 0 345.56 0 343.76 L0 330.74 Z"
+ class="st2"/>
+ </g>
+ <g id="shape24-54" v:mID="24" v:groupContext="shape" transform="translate(125.917,-226.322)">
+ <title>Sheet.24</title>
+ <desc>0x0103CDAB</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.3615" cy="339.824" width="86.73" height="14.3829"/>
+ <path d="M86.72 332.63 L0 332.63 L0 347.02 L86.72 347.02 L86.72 332.63" class="st3"/>
+ <text x="7.12" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0103CDAB</text> </g>
+ <g id="shape25-58" v:mID="25" v:groupContext="shape" transform="translate(141.536,-244.775)">
+ <title>Sheet.25</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape26-60" v:mID="26" v:groupContext="shape" transform="translate(138.595,-210.444)">
+ <title>Sheet.26</title>
+ <path d="M26.29 334.91 C26.29 338.26 25.84 340.96 25.29 340.96 L14.16 340.96 C13.6 340.96 13.15 343.67 13.15 347.02 C13.15
+ 343.67 12.7 340.96 12.14 340.96 L1.01 340.96 C0.46 340.96 0 338.26 0 334.91" class="st7"/>
+ </g>
+ <g id="shape27-63" v:mID="27" v:groupContext="shape" transform="translate(221.793,-307.625)">
+ <title>Sheet.27</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.64 326.64 C70.52 326.64 72.03 328.17 72.03 330.04 L72.03 343.63
+ C72.03 345.5 70.52 347.02 68.64 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st1"/>
+ </g>
+ <g id="shape28-65" v:mID="28" v:groupContext="shape" transform="translate(221.793,-307.625)">
+ <title>Sheet.28</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.64 326.64 C70.52 326.64 72.03 328.17 72.03 330.04 L72.03 343.63
+ C72.03 345.5 70.52 347.02 68.64 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st2"/>
+ </g>
+ <g id="shape29-67" v:mID="29" v:groupContext="shape" transform="translate(244.237,-309.155)">
+ <title>Sheet.29</title>
+ <desc>Key3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3</text> </g>
+ <g id="shape30-71" v:mID="30" v:groupContext="shape" transform="translate(237.701,-286.954)">
+ <title>Sheet.30</title>
+ <path d="M0 336.65 L9.84 336.65 L9.84 326.28 L29.53 326.28 L29.53 336.65 L39.38 336.65 L19.69 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape31-73" v:mID="31" v:groupContext="shape" transform="translate(221.793,-266.044)">
+ <title>Sheet.31</title>
+ <path d="M0 329.94 C-0 328.07 1.55 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.07 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st1"/>
+ </g>
+ <g id="shape32-75" v:mID="32" v:groupContext="shape" transform="translate(221.793,-266.044)">
+ <title>Sheet.32</title>
+ <path d="M0 329.94 C-0 328.07 1.55 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.07 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st2"/>
+ </g>
+ <g id="shape33-77" v:mID="33" v:groupContext="shape" transform="translate(244.84,-267.636)">
+ <title>Sheet.33</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape34-81" v:mID="34" v:groupContext="shape" transform="translate(213.029,-225.183)">
+ <title>Sheet.34</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.27 327.48 C89.07 327.48 90.52 328.95 90.52 330.75 L90.52 343.76
+ C90.52 345.56 89.07 347.02 87.27 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape35-83" v:mID="35" v:groupContext="shape" transform="translate(213.029,-225.183)">
+ <title>Sheet.35</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.27 327.48 C89.07 327.48 90.52 328.95 90.52 330.75 L90.52 343.76
+ C90.52 345.56 89.07 347.02 87.27 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape36-85" v:mID="36" v:groupContext="shape" transform="translate(222.002,-226.322)">
+ <title>Sheet.36</title>
+ <desc>0x0102BAAD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.4787" cy="339.824" width="86.96" height="14.3829"/>
+ <path d="M86.96 332.63 L0 332.63 L0 347.02 L86.96 347.02 L86.96 332.63" class="st3"/>
+ <text x="7.13" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102BAAD</text> </g>
+ <g id="shape37-89" v:mID="37" v:groupContext="shape" transform="translate(237.701,-244.775)">
+ <title>Sheet.37</title>
+ <path d="M0 336.71 L9.84 336.71 L9.84 326.4 L29.53 326.4 L29.53 336.71 L39.38 336.71 L19.69 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape38-91" v:mID="38" v:groupContext="shape" transform="translate(234.759,-210.444)">
+ <title>Sheet.38</title>
+ <path d="M26.41 334.91 C26.41 338.26 25.96 340.96 25.41 340.96 L14.22 340.96 C13.66 340.96 13.21 343.67 13.21 347.02
+ C13.21 343.67 12.76 340.96 12.2 340.96 L1.01 340.96 C0.46 340.96 0 338.26 0 334.91" class="st6"/>
+ </g>
+ <g id="shape39-94" v:mID="39" v:groupContext="shape" transform="translate(319.759,-307.625)">
+ <title>Sheet.39</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.52 326.64 C70.4 326.64 71.91 328.17 71.91 330.04 L71.91 343.63
+ C71.91 345.5 70.4 347.02 68.52 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st1"/>
+ </g>
+ <g id="shape40-96" v:mID="40" v:groupContext="shape" transform="translate(319.759,-307.625)">
+ <title>Sheet.40</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.52 326.64 C70.4 326.64 71.91 328.17 71.91 330.04 L71.91 343.63
+ C71.91 345.5 70.4 347.02 68.52 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st2"/>
+ </g>
+ <g id="shape41-98" v:mID="41" v:groupContext="shape" transform="translate(342.125,-309.155)">
+ <title>Sheet.41</title>
+ <desc>Key4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4</text> </g>
+ <g id="shape42-102" v:mID="42" v:groupContext="shape" transform="translate(335.666,-286.954)">
+ <title>Sheet.42</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape43-104" v:mID="43" v:groupContext="shape" transform="translate(319.759,-266.044)">
+ <title>Sheet.43</title>
+ <path d="M0 329.94 C0 328.07 1.55 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.07 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape44-106" v:mID="44" v:groupContext="shape" transform="translate(319.759,-266.044)">
+ <title>Sheet.44</title>
+ <path d="M0 329.94 C0 328.07 1.55 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.07 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape45-108" v:mID="45" v:groupContext="shape" transform="translate(342.728,-267.636)">
+ <title>Sheet.45</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape46-112" v:mID="46" v:groupContext="shape" transform="translate(310.995,-225.183)">
+ <title>Sheet.46</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.15 327.48 C88.95 327.48 90.4 328.95 90.4 330.75 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape47-114" v:mID="47" v:groupContext="shape" transform="translate(310.995,-225.183)">
+ <title>Sheet.47</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.15 327.48 C88.95 327.48 90.4 328.95 90.4 330.75 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape48-116" v:mID="48" v:groupContext="shape" transform="translate(321.689,-226.322)">
+ <title>Sheet.48</title>
+ <desc>0x0104BEEF</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="41.4183" cy="339.824" width="82.84" height="14.3829"/>
+ <path d="M82.84 332.63 L0 332.63 L0 347.02 L82.84 347.02 L82.84 332.63" class="st3"/>
+ <text x="6.87" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0104BEEF</text> </g>
+ <g id="shape49-120" v:mID="49" v:groupContext="shape" transform="translate(335.666,-244.775)">
+ <title>Sheet.49</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape50-122" v:mID="50" v:groupContext="shape" transform="translate(332.725,-210.444)">
+ <title>Sheet.50</title>
+ <path d="M26.29 334.91 C26.29 338.27 25.84 340.96 25.29 340.96 L14.17 340.96 C13.61 340.96 13.15 343.67 13.15 347.02
+ C13.15 343.67 12.7 340.96 12.14 340.96 L1.02 340.96 C0.47 340.96 0 338.27 0 334.91" class="st6"/>
+ </g>
+ <g id="shape51-125" v:mID="51" v:groupContext="shape" transform="translate(416.884,-307.625)">
+ <title>Sheet.51</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.52 326.64 C70.4 326.64 71.91 328.17 71.91 330.04 L71.91 343.63
+ C71.91 345.5 70.4 347.02 68.52 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st1"/>
+ </g>
+ <g id="shape52-127" v:mID="52" v:groupContext="shape" transform="translate(416.884,-307.625)">
+ <title>Sheet.52</title>
+ <path d="M0 330.04 C0 328.17 1.53 326.64 3.41 326.64 L68.52 326.64 C70.4 326.64 71.91 328.17 71.91 330.04 L71.91 343.63
+ C71.91 345.5 70.4 347.02 68.52 347.02 L3.41 347.02 C1.53 347.02 0 345.5 0 343.63 L0 330.04 Z" class="st2"/>
+ </g>
+ <g id="shape53-129" v:mID="53" v:groupContext="shape" transform="translate(439.255,-309.155)">
+ <title>Sheet.53</title>
+ <desc>Key5</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key5</text> </g>
+ <g id="shape54-133" v:mID="54" v:groupContext="shape" transform="translate(432.791,-286.954)">
+ <title>Sheet.54</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape55-135" v:mID="55" v:groupContext="shape" transform="translate(416.884,-266.044)">
+ <title>Sheet.55</title>
+ <path d="M0 329.94 C0 328.07 1.55 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.07 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st1"/>
+ </g>
+ <g id="shape56-137" v:mID="56" v:groupContext="shape" transform="translate(416.884,-266.044)">
+ <title>Sheet.56</title>
+ <path d="M0 329.94 C0 328.07 1.55 326.52 3.42 326.52 L68.49 326.52 C70.38 326.52 71.91 328.07 71.91 329.94 L71.91 343.6
+ C71.91 345.49 70.38 347.02 68.49 347.02 L3.42 347.02 C1.55 347.02 0 345.49 0 343.6 L0 329.94 Z"
+ class="st2"/>
+ </g>
+ <g id="shape57-139" v:mID="57" v:groupContext="shape" transform="translate(439.858,-267.636)">
+ <title>Sheet.57</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape58-143" v:mID="58" v:groupContext="shape" transform="translate(408.12,-225.183)">
+ <title>Sheet.58</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.15 327.48 C88.95 327.48 90.4 328.95 90.4 330.75 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape59-145" v:mID="59" v:groupContext="shape" transform="translate(408.12,-225.183)">
+ <title>Sheet.59</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.15 327.48 C88.95 327.48 90.4 328.95 90.4 330.75 L90.4 343.76
+ C90.4 345.56 88.95 347.02 87.15 347.02 L3.27 347.02 C1.47 347.02 0 345.56 0 343.76 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape60-147" v:mID="60" v:groupContext="shape" transform="translate(416.778,-226.322)">
+ <title>Sheet.60</title>
+ <desc>0x0103DABD</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.7817" cy="339.824" width="87.57" height="14.3829"/>
+ <path d="M87.56 332.63 L0 332.63 L0 347.02 L87.56 347.02 L87.56 332.63" class="st3"/>
+ <text x="7.17" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0103DABD</text> </g>
+ <g id="shape61-151" v:mID="61" v:groupContext="shape" transform="translate(432.791,-244.775)">
+ <title>Sheet.61</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape62-153" v:mID="62" v:groupContext="shape" transform="translate(429.85,-210.444)">
+ <title>Sheet.62</title>
+ <path d="M26.29 334.91 C26.29 338.27 25.84 340.96 25.29 340.96 L14.17 340.96 C13.61 340.96 13.15 343.67 13.15 347.02
+ C13.15 343.67 12.7 340.96 12.14 340.96 L1.02 340.96 C0.47 340.96 0 338.27 0 334.91" class="st7"/>
+ </g>
+ <g id="shape63-156" v:mID="63" v:groupContext="shape" transform="translate(514.489,-307.625)">
+ <title>Sheet.63</title>
+ <path d="M0 330.06 C-0 328.17 1.53 326.64 3.42 326.64 L68.64 326.64 C70.53 326.64 72.03 328.17 72.03 330.06 L72.03 343.63
+ C72.03 345.52 70.53 347.02 68.64 347.02 L3.42 347.02 C1.53 347.02 0 345.52 0 343.63 L0 330.06 Z"
+ class="st1"/>
+ </g>
+ <g id="shape64-158" v:mID="64" v:groupContext="shape" transform="translate(514.489,-307.625)">
+ <title>Sheet.64</title>
+ <path d="M0 330.06 C-0 328.17 1.53 326.64 3.42 326.64 L68.64 326.64 C70.53 326.64 72.03 328.17 72.03 330.06 L72.03 343.63
+ C72.03 345.52 70.53 347.02 68.64 347.02 L3.42 347.02 C1.53 347.02 0 345.52 0 343.63 L0 330.06 Z"
+ class="st2"/>
+ </g>
+ <g id="shape65-160" v:mID="65" v:groupContext="shape" transform="translate(536.883,-309.19)">
+ <title>Sheet.65</title>
+ <desc>Key6</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key6</text> </g>
+ <g id="shape66-164" v:mID="66" v:groupContext="shape" transform="translate(530.396,-287.074)">
+ <title>Sheet.66</title>
+ <path d="M0 336.71 L9.81 336.71 L9.81 326.4 L29.44 326.4 L29.44 336.71 L39.26 336.71 L19.63 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape67-166" v:mID="67" v:groupContext="shape" transform="translate(514.489,-266.044)">
+ <title>Sheet.67</title>
+ <path d="M0 329.94 C0 328.08 1.56 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.08 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.56 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st1"/>
+ </g>
+ <g id="shape68-168" v:mID="68" v:groupContext="shape" transform="translate(514.489,-266.044)">
+ <title>Sheet.68</title>
+ <path d="M0 329.94 C0 328.08 1.56 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.08 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.56 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st2"/>
+ </g>
+ <g id="shape69-170" v:mID="69" v:groupContext="shape" transform="translate(537.486,-267.671)">
+ <title>Sheet.69</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape70-174" v:mID="70" v:groupContext="shape" transform="translate(505.725,-225.183)">
+ <title>Sheet.70</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.16 327.48 C88.96 327.48 90.4 328.95 90.4 330.75 L90.4 343.78
+ C90.4 345.58 88.96 347.02 87.16 347.02 L3.27 347.02 C1.47 347.02 0 345.58 0 343.78 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape71-176" v:mID="71" v:groupContext="shape" transform="translate(505.725,-225.183)">
+ <title>Sheet.71</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.16 327.48 C88.96 327.48 90.4 328.95 90.4 330.75 L90.4 343.78
+ C90.4 345.58 88.96 347.02 87.16 347.02 L3.27 347.02 C1.47 347.02 0 345.58 0 343.78 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape72-178" v:mID="72" v:groupContext="shape" transform="translate(514.766,-226.356)">
+ <title>Sheet.72</title>
+ <desc>0x0102ADCB</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.3615" cy="339.824" width="86.73" height="14.3829"/>
+ <path d="M86.72 332.63 L0 332.63 L0 347.02 L86.72 347.02 L86.72 332.63" class="st3"/>
+ <text x="7.12" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102ADCB</text> </g>
+ <g id="shape73-182" v:mID="73" v:groupContext="shape" transform="translate(530.396,-244.775)">
+ <title>Sheet.73</title>
+ <path d="M0 336.65 L9.81 336.65 L9.81 326.28 L29.44 326.28 L29.44 336.65 L39.26 336.65 L19.63 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape74-184" v:mID="74" v:groupContext="shape" transform="translate(527.455,-210.564)">
+ <title>Sheet.74</title>
+ <path d="M26.29 335.03 C26.29 338.36 25.87 341.02 25.3 341.02 L14.17 341.02 C13.6 341.02 13.15 343.72 13.15 347.02 C13.15
+ 343.72 12.73 341.02 12.16 341.02 L1.02 341.02 C0.45 341.02 0 338.36 0 335.03" class="st6"/>
+ </g>
+ <g id="shape75-187" v:mID="75" v:groupContext="shape" transform="translate(610.653,-307.505)">
+ <title>Sheet.75</title>
+ <path d="M0 329.94 C0 328.08 1.56 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.08 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.56 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st1"/>
+ </g>
+ <g id="shape76-189" v:mID="76" v:groupContext="shape" transform="translate(610.653,-307.505)">
+ <title>Sheet.76</title>
+ <path d="M0 329.94 C0 328.08 1.56 326.52 3.42 326.52 L68.61 326.52 C70.5 326.52 72.03 328.08 72.03 329.94 L72.03 343.6
+ C72.03 345.49 70.5 347.02 68.61 347.02 L3.42 347.02 C1.56 347.02 0 345.49 0 343.6 L0 329.94 Z" class="st2"/>
+ </g>
+ <g id="shape77-191" v:mID="77" v:groupContext="shape" transform="translate(633.086,-309.121)">
+ <title>Sheet.77</title>
+ <desc>Key7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="17.3237" cy="339.824" width="34.65" height="14.3829"/>
+ <path d="M34.65 332.63 L0 332.63 L0 347.02 L34.65 347.02 L34.65 332.63" class="st3"/>
+ <text x="3.72" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7</text> </g>
+ <g id="shape78-195" v:mID="78" v:groupContext="shape" transform="translate(626.561,-286.954)">
+ <title>Sheet.78</title>
+ <path d="M0 336.65 L9.84 336.65 L9.84 326.28 L29.53 326.28 L29.53 336.65 L39.38 336.65 L19.69 347.02 L0 336.65 Z"
+ class="st5"/>
+ </g>
+ <g id="shape79-197" v:mID="79" v:groupContext="shape" transform="translate(610.653,-266.044)">
+ <title>Sheet.79</title>
+ <path d="M0 330.06 C-0 328.17 1.53 326.64 3.42 326.64 L68.64 326.64 C70.53 326.64 72.03 328.17 72.03 330.06 L72.03 343.63
+ C72.03 345.52 70.53 347.02 68.64 347.02 L3.42 347.02 C1.53 347.02 0 345.52 0 343.63 L0 330.06 Z"
+ class="st1"/>
+ </g>
+ <g id="shape80-199" v:mID="80" v:groupContext="shape" transform="translate(610.653,-266.044)">
+ <title>Sheet.80</title>
+ <path d="M0 330.06 C-0 328.17 1.53 326.64 3.42 326.64 L68.64 326.64 C70.53 326.64 72.03 328.17 72.03 330.06 L72.03 343.63
+ C72.03 345.52 70.53 347.02 68.64 347.02 L3.42 347.02 C1.53 347.02 0 345.52 0 343.63 L0 330.06 Z"
+ class="st2"/>
+ </g>
+ <g id="shape81-201" v:mID="81" v:groupContext="shape" transform="translate(633.689,-267.602)">
+ <title>Sheet.81</title>
+ <desc>hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="16.5866" cy="339.824" width="33.18" height="14.3829"/>
+ <path d="M33.17 332.63 L0 332.63 L0 347.02 L33.17 347.02 L33.17 332.63" class="st3"/>
+ <text x="3.63" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash</text> </g>
+ <g id="shape82-205" v:mID="82" v:groupContext="shape" transform="translate(601.889,-225.183)">
+ <title>Sheet.82</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.28 327.48 C89.08 327.48 90.52 328.95 90.52 330.75 L90.52 343.78
+ C90.52 345.58 89.08 347.02 87.28 347.02 L3.27 347.02 C1.47 347.02 0 345.58 0 343.78 L0 330.75 Z"
+ class="st1"/>
+ </g>
+ <g id="shape83-207" v:mID="83" v:groupContext="shape" transform="translate(601.889,-225.183)">
+ <title>Sheet.83</title>
+ <path d="M0 330.75 C0 328.95 1.47 327.48 3.27 327.48 L87.28 327.48 C89.08 327.48 90.52 328.95 90.52 330.75 L90.52 343.78
+ C90.52 345.58 89.08 347.02 87.28 347.02 L3.27 347.02 C1.47 347.02 0 345.58 0 343.78 L0 330.75 Z"
+ class="st2"/>
+ </g>
+ <g id="shape84-209" v:mID="84" v:groupContext="shape" transform="translate(610.969,-226.287)">
+ <title>Sheet.84</title>
+ <desc>0x0104DBCA</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="43.3615" cy="339.824" width="86.73" height="14.3829"/>
+ <path d="M86.72 332.63 L0 332.63 L0 347.02 L86.72 347.02 L86.72 332.63" class="st3"/>
+ <text x="7.12" y="343.42" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0104DBCA</text> </g>
+ <g id="shape85-213" v:mID="85" v:groupContext="shape" transform="translate(626.561,-244.775)">
+ <title>Sheet.85</title>
+ <path d="M0 336.71 L9.84 336.71 L9.84 326.4 L29.53 326.4 L29.53 336.71 L39.38 336.71 L19.69 347.02 L0 336.71 Z"
+ class="st5"/>
+ </g>
+ <g id="shape86-215" v:mID="86" v:groupContext="shape" transform="translate(623.619,-210.444)">
+ <title>Sheet.86</title>
+ <path d="M26.41 334.91 C26.41 338.27 25.96 340.96 25.42 340.96 L14.23 340.96 C13.69 340.96 13.21 343.69 13.21 347.02
+ C13.21 343.69 12.76 340.96 12.22 340.96 L1.02 340.96 C0.48 340.96 0 338.27 0 334.91" class="st8"/>
+ </g>
+ <g id="shape87-218" v:mID="87" v:groupContext="shape" transform="translate(242.323,-81.6288)">
+ <title>Sheet.87</title>
+ <path d="M0 281.23 L0 347.02 L41.18 347.02 L41.18 281.23 L0 281.23 L0 281.23 Z" class="st1"/>
+ </g>
+ <g id="shape88-220" v:mID="88" v:groupContext="shape" transform="translate(247.009,-81.6288)">
+ <title>Sheet.88</title>
+ <path d="M0 281.23 L41.18 281.23 L41.18 347.02 L0 347.02 L0 281.23" class="st9"/>
+ </g>
+ <g id="shape89-223" v:mID="89" v:groupContext="shape" transform="translate(245.254,-132.398)">
+ <title>Sheet.89</title>
+ <desc>0x0102</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.3211" cy="341.046" width="42.65" height="11.9384"/>
+ <path d="M42.64 335.08 L0 335.08 L0 347.02 L42.64 347.02 L42.64 335.08" class="st3"/>
+ <text x="4" y="344.03" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0102</text> </g>
+ <g id="shape90-227" v:mID="90" v:groupContext="shape" transform="translate(245.015,-82.7016)">
+ <title>Sheet.90</title>
+ <desc>4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.79425" cy="341.046" width="9.59" height="11.9384"/>
+ <path d="M9.59 335.08 L0 335.08 L0 347.02 L9.59 347.02 L9.59 335.08" class="st3"/>
+ <text x="1.84" y="344.03" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>4</text> </g>
+ <g id="shape91-231" v:mID="91" v:groupContext="shape" transform="translate(336.326,-81.6288)">
+ <title>Sheet.91</title>
+ <path d="M0 281.23 L0 347.02 L41.18 347.02 L41.18 281.23 L0 281.23 L0 281.23 Z" class="st1"/>
+ </g>
+ <g id="shape92-233" v:mID="92" v:groupContext="shape" transform="translate(339.598,-81.6288)">
+ <title>Sheet.92</title>
+ <path d="M0 281.23 L41.18 281.23 L41.18 347.02 L0 347.02 L0 281.23" class="st9"/>
+ </g>
+ <g id="shape93-236" v:mID="93" v:groupContext="shape" transform="translate(339.264,-132.398)">
+ <title>Sheet.93</title>
+ <desc>0x0103</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.3211" cy="341.046" width="42.65" height="11.9384"/>
+ <path d="M42.64 335.08 L0 335.08 L0 347.02 L42.64 347.02 L42.64 335.08" class="st3"/>
+ <text x="4" y="344.03" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0103</text> </g>
+ <g id="shape94-240" v:mID="94" v:groupContext="shape" transform="translate(339.024,-82.7016)">
+ <title>Sheet.94</title>
+ <desc>2</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.79425" cy="341.046" width="9.59" height="11.9384"/>
+ <path d="M9.59 335.08 L0 335.08 L0 347.02 L9.59 347.02 L9.59 335.08" class="st3"/>
+ <text x="1.84" y="344.03" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>2</text> </g>
+ <g id="shape95-244" v:mID="95" v:groupContext="shape" transform="translate(438.598,-81.5089)">
+ <title>Sheet.95</title>
+ <path d="M0 281.23 L0 347.02 L41.18 347.02 L41.18 281.23 L0 281.23 L0 281.23 Z" class="st1"/>
+ </g>
+ <g id="shape96-246" v:mID="96" v:groupContext="shape" transform="translate(438.598,-81.5089)">
+ <title>Sheet.96</title>
+ <path d="M0 281.23 L41.18 281.23 L41.18 347.02 L0 347.02 L0 281.23" class="st9"/>
+ </g>
+ <g id="shape97-249" v:mID="97" v:groupContext="shape" transform="translate(437.81,-132.27)">
+ <title>Sheet.97</title>
+ <desc>0x0104</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="21.3211" cy="341.046" width="42.65" height="11.9384"/>
+ <path d="M42.64 335.08 L0 335.08 L0 347.02 L42.64 347.02 L42.64 335.08" class="st3"/>
+ <text x="4" y="344.03" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>0x0104</text> </g>
+ <g id="shape98-253" v:mID="98" v:groupContext="shape" transform="translate(437.57,-82.5735)">
+ <title>Sheet.98</title>
+ <desc>1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="4.79425" cy="341.046" width="9.59" height="11.9384"/>
+ <path d="M9.59 335.08 L0 335.08 L0 347.02 L9.59 347.02 L9.59 335.08" class="st3"/>
+ <text x="1.84" y="344.03" class="st11" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>1</text> </g>
+ <g id="shape99-257" v:mID="99" v:groupContext="shape" transform="translate(53.5505,-147.924)">
+ <title>Sheet.99</title>
+ <path d="M0.59 283.52 L206.27 343.39 L205.7 345.34 L0 285.48 L0.59 283.52 L0.59 283.52 ZM205.85 341.14 L210.88 345.79
+ L204.14 347.02 L205.85 341.14 L205.85 341.14 Z" class="st12"/>
+ </g>
+ <g id="shape100-259" v:mID="100" v:groupContext="shape" transform="translate(151.516,-147.924)">
+ <title>Sheet.100</title>
+ <path d="M0.59 283.52 L202.41 343.41 L201.83 345.35 L0 285.48 L0.59 283.52 L0.59 283.52 ZM202.01 341.16 L207.01 345.83
+ L200.27 347.02 L202.01 341.16 L202.01 341.16 Z" class="st13"/>
+ </g>
+ <g id="shape101-261" v:mID="101" v:groupContext="shape" transform="translate(246.975,-147.37)">
+ <title>Sheet.101</title>
+ <path d="M2 283.72 L15.77 341.83 L13.79 342.3 L0 284.18 L2 283.72 L2 283.72 ZM17.53 340.36 L15.97 347.02 L11.57 341.77
+ L17.53 340.36 L17.53 340.36 Z" class="st12"/>
+ </g>
+ <g id="shape102-263" v:mID="102" v:groupContext="shape" transform="translate(262.972,-147.37)">
+ <title>Sheet.102</title>
+ <path d="M82.31 283.13 L3.45 343.12 L4.68 344.74 L83.54 284.76 L82.31 283.13 L82.31 283.13 ZM3.02 340.89 L0 347.02 L6.74
+ 345.74 L3.02 340.89 L3.02 340.89 Z" class="st12"/>
+ </g>
+ <g id="shape103-265" v:mID="103" v:groupContext="shape" transform="translate(358.537,-149.107)">
+ <title>Sheet.103</title>
+ <path d="M83.92 284.85 L3.53 343.2 L4.73 344.84 L85.12 286.5 L83.92 284.85 L83.92 284.85 ZM3.15 340.95 L0 347.02 L6.75
+ 345.89 L3.15 340.95 L3.15 340.95 Z" class="st13"/>
+ </g>
+ <g id="shape104-267" v:mID="104" v:groupContext="shape" transform="translate(264.413,-147.534)">
+ <title>Sheet.104</title>
+ <path d="M275.95 283 L4.77 343.27 L5.22 345.25 L276.37 285 L275.95 283 L275.95 283 ZM5.31 341.05 L0 345.37 L6.66 347.02
+ L5.31 341.05 L5.31 341.05 Z" class="st14"/>
+ </g>
+ <g id="shape105-269" v:mID="105" v:groupContext="shape" transform="translate(456.982,-148.103)">
+ <title>Sheet.105</title>
+ <path d="M179.48 283.72 L4.5 343.48 L5.16 345.43 L180.14 285.66 L179.48 283.72 L179.48 283.72 ZM4.8 341.23 L0 346.12
+ L6.81 347.02 L4.8 341.23 L4.8 341.23 Z" class="st15"/>
+ </g>
+ <g id="shape106-271" v:mID="106" v:groupContext="shape" transform="translate(335.628,-18)">
+ <title>Sheet.106</title>
+ <path d="M0 309.64 C0 305.52 2.99 302.16 6.65 302.16 L14.2 302.16 L8.01 284.85 L35.48 302.16 L78.47 302.16 C82.15 302.16
+ 85.12 305.52 85.12 309.64 L85.12 309.64 L85.12 320.85 L85.12 339.54 C85.12 343.68 82.15 347.02 78.47 347.02
+ L35.48 347.02 L14.2 347.02 L14.2 347.02 L6.65 347.02 C2.99 347.02 0 343.68 0 339.54 L0 320.85 L0 309.64
+ L0 309.64 Z" class="st5"/>
+ </g>
+ <g id="shape109-273" v:mID="109" v:groupContext="shape" transform="translate(157.564,-62.4234)">
+ <title>Sheet.109</title>
+ <path d="M16.21 347.02 C11.74 347.02 8.1 346.42 8.1 345.67 L8.1 303.49 C8.1 302.75 4.49 302.14 0 302.14 C4.49 302.14
+ 8.1 301.54 8.1 300.79 L8.1 258.61 C8.1 257.88 11.74 257.26 16.21 257.26" class="st7"/>
+ </g>
+ <g id="shape110-276" v:mID="110" v:groupContext="shape" transform="translate(113.844,-100.157)">
+ <title>Sheet.110</title>
+ <desc>Groups</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="20.2175" cy="341.046" width="40.44" height="11.9384"/>
+ <path d="M40.44 335.08 L0 335.08 L0 347.02 L40.44 347.02 L40.44 335.08" class="st3"/>
+ <text x="3.85" y="344.03" class="st16" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Groups</text> </g>
+ <g id="shape111-280" v:mID="111" v:groupContext="shape" transform="translate(196.718,-76.2186)">
+ <title>Sheet.111</title>
+ <path d="M0 331.97 C0 330.32 2.27 328.96 5.04 328.96 L37.61 328.96 L60.43 284.85 L53.72 328.96 L59.43 328.96 C62.22 328.96
+ 64.47 330.32 64.47 331.97 L64.47 331.97 L64.47 336.48 L64.47 344.01 C64.47 345.67 62.22 347.02 59.43 347.02
+ L53.72 347.02 L37.61 347.02 L37.61 347.02 L5.04 347.02 C2.27 347.02 0 345.67 0 344.01 L0 336.48 L0 331.97
+ L0 331.97 Z" class="st5"/>
+ </g>
+ <g id="shape112-282" v:mID="112" v:groupContext="shape" transform="translate(196.65,-80.2991)">
+ <title>Sheet.112</title>
+ <desc>group id</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="27.7691" cy="339.824" width="55.54" height="14.3829"/>
+ <path d="M55.54 332.63 L0 332.63 L0 347.02 L55.54 347.02 L55.54 332.63" class="st3"/>
+ <text x="5.09" y="343.42" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>group id</text> </g>
+ <g id="shape114-286" v:mID="114" v:groupContext="shape" transform="translate(506.433,-128.007)">
+ <title>Sheet.114</title>
+ <desc>-</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="6.63728" cy="336.229" width="13.28" height="21.5726"/>
+ <path d="M13.27 325.44 L0 325.44 L0 347.02 L13.27 347.02 L13.27 325.44" class="st3"/>
+ <text x="3.06" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>-</text> </g>
+ <g id="shape115-290" v:mID="115" v:groupContext="shape" transform="translate(529.004,-128.007)">
+ <title>Sheet.115</title>
+ <desc>Keys separated into</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="97.1729" cy="336.229" width="194.35" height="21.5726"/>
+ <path d="M194.35 325.44 L0 325.44 L0 347.02 L194.35 347.02 L194.35 325.44" class="st3"/>
+ <text x="17.06" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Keys separated into </text> </g>
+ <g id="shape116-294" v:mID="116" v:groupContext="shape" transform="translate(529.004,-106.438)">
+ <title>Sheet.116</title>
+ <desc>groups based on</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="83.1587" cy="336.229" width="166.32" height="21.5726"/>
+ <path d="M166.32 325.44 L0 325.44 L0 347.02 L166.32 347.02 L166.32 325.44" class="st3"/>
+ <text x="15.23" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>groups based on </text> </g>
+ <g id="shape117-298" v:mID="117" v:groupContext="shape" transform="translate(529.004,-84.869)">
+ <title>Sheet.117</title>
+ <desc>some bits from hash</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="97.731" cy="336.229" width="195.47" height="21.5726"/>
+ <path d="M195.46 325.44 L0 325.44 L0 347.02 L195.46 347.02 L195.46 325.44" class="st3"/>
+ <text x="14.94" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>some bits from hash</text> </g>
+ <g id="shape118-302" v:mID="118" v:groupContext="shape" transform="translate(506.433,-63.2999)">
+ <title>Sheet.118</title>
+ <desc>-</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="6.63728" cy="336.229" width="13.28" height="21.5726"/>
+ <path d="M13.27 325.44 L0 325.44 L0 347.02 L13.27 347.02 L13.27 325.44" class="st3"/>
+ <text x="3.06" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>-</text> </g>
+ <g id="shape119-306" v:mID="119" v:groupContext="shape" transform="translate(529.004,-63.2999)">
+ <title>Sheet.119</title>
+ <desc>Groups contain a</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="84.2539" cy="336.229" width="168.51" height="21.5726"/>
+ <path d="M168.51 325.44 L0 325.44 L0 347.02 L168.51 347.02 L168.51 325.44" class="st3"/>
+ <text x="15.38" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Groups contain a </text> </g>
+ <g id="shape120-310" v:mID="120" v:groupContext="shape" transform="translate(529.004,-41.7308)">
+ <title>Sheet.120</title>
+ <desc>small number of</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="81.4635" cy="336.229" width="162.93" height="21.5726"/>
+ <path d="M162.93 325.44 L0 325.44 L0 347.02 L162.93 347.02 L162.93 325.44" class="st3"/>
+ <text x="15.01" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>small number of </text> </g>
+ <g id="shape121-314" v:mID="121" v:groupContext="shape" transform="translate(529.004,-20.1617)">
+ <title>Sheet.121</title>
+ <desc>keys (<28)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.4481" cy="336.229" width="100.9" height="21.5726"/>
+ <path d="M100.9 325.44 L0 325.44 L0 347.02 L100.9 347.02 L100.9 325.44" class="st3"/>
+ <text x="8.77" y="341.62" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>keys (<28)</text> </g>
+ <g id="shape122-318" v:mID="122" v:groupContext="shape" transform="translate(19.1996,-146.276)">
+ <title>Sheet.122</title>
+ <path d="M0 310.17 C-0 306.1 3.62 302.8 8.07 302.8 L14.46 302.8 L29.68 282.28 L36.14 302.8 L78.65 302.8 C83.11 302.8
+ 86.72 306.1 86.72 310.17 L86.72 310.17 L86.72 321.22 L86.72 339.65 C86.72 343.72 83.11 347.02 78.65 347.02
+ L36.14 347.02 L14.46 347.02 L14.46 347.02 L8.07 347.02 C3.62 347.02 0 343.72 0 339.65 L0 321.22 L0 310.17
+ L0 310.17 Z" class="st5"/>
+ </g>
+ <g id="shape123-320" v:mID="123" v:groupContext="shape" transform="translate(41.9777,-174.053)">
+ <title>Sheet.123</title>
+ <desc>Group</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="22.8289" cy="339.824" width="45.66" height="14.3829"/>
+ <path d="M45.66 332.63 L0 332.63 L0 347.02 L45.66 347.02 L45.66 332.63" class="st3"/>
+ <text x="5.9" y="343.42" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group </text> </g>
+ <g id="shape124-324" v:mID="124" v:groupContext="shape" transform="translate(34.4142,-159.674)">
+ <title>Sheet.124</title>
+ <desc>Identifier</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="31.5173" cy="339.824" width="63.04" height="14.3829"/>
+ <path d="M63.03 332.63 L0 332.63 L0 347.02 L63.03 347.02 L63.03 332.63" class="st3"/>
+ <text x="7.04" y="343.42" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Identifier </text> </g>
+ <g id="shape125-328" v:mID="125" v:groupContext="shape" transform="translate(28.7716,-145.295)">
+ <title>Sheet.125</title>
+ <desc>(simplified)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="36.2165" cy="339.824" width="72.44" height="14.3829"/>
+ <path d="M72.43 332.63 L0 332.63 L0 347.02 L72.43 347.02 L72.43 332.63" class="st3"/>
+ <text x="6.19" y="343.42" class="st17" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(simplified)</text> </g>
+ <g id="shape127-332" v:mID="127" v:groupContext="shape" transform="translate(517.688,-71.2991)">
+ <title>Sheet.127</title>
+ <desc>Keys separated into groups based on some bits from hash. Grou...</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="112.5" cy="302.139" width="225" height="89.7513"/>
+ <g id="shadow127-333" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st19">
+ <rect x="0" y="257.264" width="225" height="89.7513" class="st20"/>
+ </g>
+ <rect x="0" y="257.264" width="225" height="89.7513" class="st21"/>
+ <text x="4" y="281.09" class="st22" v:langID="1033"><v:paragraph v:indentFirst="-18" v:indentLeft="18" v:bullet="1"/><v:tabList/><tspan
+ class="st23" v:isBullet="true">·</tspan> <tspan class="st24">Keys separated into groups based </tspan><tspan
+ x="22" dy="1.204em" class="st24">on some bits from hash</tspan><tspan class="st24">.<v:newlineChar/></tspan><tspan
+ x="4" dy="1.211em" class="st23" v:isBullet="true">·</tspan> <tspan class="st24">Groups contain a small number of </tspan><tspan
+ x="22" dy="1.204em" class="st24">keys </tspan><tspan class="st24">(</tspan><tspan class="st24"><</tspan><tspan
+ class="st24">28</tspan><tspan class="st24">)</tspan></text> </g>
+ <g id="shape129-349" v:mID="129" v:groupContext="shape" transform="translate(336.326,-26.2991)">
+ <title>Sheet.129</title>
+ <desc>Total # of keys in group so far</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="39.6784" cy="333.515" width="79.36" height="27"/>
+ <rect x="0" y="320.015" width="79.3567" height="27" class="st25"/>
+ <text x="4.5" y="329.92" class="st26" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Total # of keys <tspan
+ x="4.39" dy="1.2em" class="st23">in group so far</tspan></text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i8.svg b/doc/guides/prog_guide/img/efd_i8.svg
new file mode 100644
index 0000000..d0fd463
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i8.svg
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i9.svg Page-2 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="4.98372in" height="2.08442in"
+ viewBox="0 0 358.828 150.078" xml:space="preserve" color-interpolation-filters="sRGB" class="st8">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st4 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st5 {fill:#000000;font-family:Intel Clear;font-size:0.998566em}
+ .st6 {fill:#c00000;font-family:Intel Clear;font-size:0.828804em;font-weight:bold}
+ .st7 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st8 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="4" v:index="2" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-2</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape4-1" v:mID="4" v:groupContext="shape" transform="translate(206.306,-19.0195)">
+ <title>Sheet.4</title>
+ <path d="M0 38.04 L0 150.08 L133.5 150.08 L133.5 38.04 L0 38.04 L0 38.04 Z" class="st1"/>
+ </g>
+ <g id="shape5-3" v:mID="5" v:groupContext="shape" transform="translate(206.306,-19.0195)">
+ <title>Sheet.5</title>
+ <path d="M0 38.04 L133.5 38.04 L133.5 150.08 L0 150.08 L0 38.04" class="st2"/>
+ </g>
+ <g id="shape6-6" v:mID="6" v:groupContext="shape" transform="translate(215.55,-70.7853)">
+ <title>Sheet.6</title>
+ <path d="M0 121.92 C0 118.82 2.54 116.29 5.64 116.29 L110.69 116.29 C113.81 116.29 116.33 118.82 116.33 121.92 L116.33
+ 144.45 C116.33 147.56 113.81 150.08 110.69 150.08 L5.64 150.08 C2.54 150.08 0 147.56 0 144.45 L0 121.92
+ Z" class="st1"/>
+ </g>
+ <g id="shape7-8" v:mID="7" v:groupContext="shape" transform="translate(215.55,-70.7853)">
+ <title>Sheet.7</title>
+ <path d="M0 121.92 C0 118.82 2.54 116.29 5.64 116.29 L110.69 116.29 C113.81 116.29 116.33 118.82 116.33 121.92 L116.33
+ 144.45 C116.33 147.56 113.81 150.08 110.69 150.08 L5.64 150.08 C2.54 150.08 0 147.56 0 144.45 L0 121.92
+ Z" class="st3"/>
+ </g>
+ <g id="shape8-10" v:mID="8" v:groupContext="shape" transform="translate(242.756,-86.1914)">
+ <title>Sheet.8</title>
+ <desc>hash_index</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="36.9951" cy="142.887" width="74" height="14.3829"/>
+ <path d="M73.99 135.7 L0 135.7 L0 150.08 L73.99 150.08 L73.99 135.7" class="st4"/>
+ <text x="6.29" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash_index</text> </g>
+ <g id="shape9-14" v:mID="9" v:groupContext="shape" transform="translate(229.67,-71.812)">
+ <title>Sheet.9</title>
+ <desc>(integer, 16 bits)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="52.0635" cy="142.887" width="104.13" height="14.3829"/>
+ <path d="M104.13 135.7 L0 135.7 L0 150.08 L104.13 150.08 L104.13 135.7" class="st4"/>
+ <text x="8.25" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(integer, 16 bits)</text> </g>
+ <g id="shape10-18" v:mID="10" v:groupContext="shape" transform="translate(215.55,-27.1678)">
+ <title>Sheet.10</title>
+ <path d="M0 121.92 C0 118.82 2.54 116.29 5.64 116.29 L110.69 116.29 C113.81 116.29 116.33 118.82 116.33 121.92 L116.33
+ 144.45 C116.33 147.56 113.81 150.08 110.69 150.08 L5.64 150.08 C2.54 150.08 0 147.56 0 144.45 L0 121.92
+ Z" class="st1"/>
+ </g>
+ <g id="shape11-20" v:mID="11" v:groupContext="shape" transform="translate(215.55,-27.1678)">
+ <title>Sheet.11</title>
+ <path d="M0 121.92 C0 118.82 2.54 116.29 5.64 116.29 L110.69 116.29 C113.81 116.29 116.33 118.82 116.33 121.92 L116.33
+ 144.45 C116.33 147.56 113.81 150.08 110.69 150.08 L5.64 150.08 C2.54 150.08 0 147.56 0 144.45 L0 121.92
+ Z" class="st3"/>
+ </g>
+ <g id="shape12-22" v:mID="12" v:groupContext="shape" transform="translate(237.836,-42.6033)">
+ <title>Sheet.12</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="42.693" cy="142.887" width="85.39" height="14.3829"/>
+ <path d="M85.39 135.7 L0 135.7 L0 150.08 L85.39 150.08 L85.39 135.7" class="st4"/>
+ <text x="7.03" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape13-26" v:mID="13" v:groupContext="shape" transform="translate(251.643,-28.2239)">
+ <title>Sheet.13</title>
+ <desc>(16 bits)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="26.9562" cy="142.887" width="53.92" height="14.3829"/>
+ <path d="M53.91 135.7 L0 135.7 L0 150.08 L53.91 150.08 L53.91 135.7" class="st4"/>
+ <text x="4.98" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(16 bits)</text> </g>
+ <g id="shape14-30" v:mID="14" v:groupContext="shape" transform="translate(213.473,-114.303)">
+ <title>Sheet.14</title>
+ <desc>Group ID: 0x0102</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="47.976" cy="144.109" width="95.96" height="11.9384"/>
+ <path d="M95.95 138.14 L0 138.14 L0 150.08 L95.95 150.08 L95.95 138.14" class="st4"/>
+ <text x="7.47" y="147.09" class="st6" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group ID: 0x0102</text> </g>
+ <g id="shape15-34" v:mID="15" v:groupContext="shape" transform="translate(19.0195,-99.4242)">
+ <title>Sheet.15</title>
+ <path d="M0 129.31 C0 127.02 1.87 125.15 4.16 125.15 L109.18 125.15 C111.47 125.15 113.33 127.02 113.33 129.31 L113.33
+ 145.93 C113.33 148.22 111.47 150.08 109.18 150.08 L4.16 150.08 C1.87 150.08 0 148.22 0 145.93 L0 129.31
+ Z" class="st1"/>
+ </g>
+ <g id="shape16-36" v:mID="16" v:groupContext="shape" transform="translate(19.0195,-99.4242)">
+ <title>Sheet.16</title>
+ <path d="M0 129.31 C0 127.02 1.87 125.15 4.16 125.15 L109.18 125.15 C111.47 125.15 113.33 127.02 113.33 129.31 L113.33
+ 145.93 C113.33 148.22 111.47 150.08 109.18 150.08 L4.16 150.08 C1.87 150.08 0 148.22 0 145.93 L0 129.31
+ Z" class="st3"/>
+ </g>
+ <g id="shape17-38" v:mID="17" v:groupContext="shape" transform="translate(33.9485,-103.285)">
+ <title>Sheet.17</title>
+ <desc>Key1: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="49.6862" cy="142.887" width="99.38" height="14.3829"/>
+ <path d="M99.37 135.7 L0 135.7 L0 150.08 L99.37 150.08 L99.37 135.7" class="st4"/>
+ <text x="7.94" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1: Value = 0</text> </g>
+ <g id="shape18-42" v:mID="18" v:groupContext="shape" transform="translate(19.0195,-74.6198)">
+ <title>Sheet.18</title>
+ <path d="M0 129.31 C0 127.02 1.87 125.15 4.16 125.15 L109.18 125.15 C111.47 125.15 113.33 127.02 113.33 129.31 L113.33
+ 145.93 C113.33 148.22 111.47 150.08 109.18 150.08 L4.16 150.08 C1.87 150.08 0 148.22 0 145.93 L0 129.31
+ Z" class="st1"/>
+ </g>
+ <g id="shape19-44" v:mID="19" v:groupContext="shape" transform="translate(19.0195,-74.6198)">
+ <title>Sheet.19</title>
+ <path d="M0 129.31 C0 127.02 1.87 125.15 4.16 125.15 L109.18 125.15 C111.47 125.15 113.33 127.02 113.33 129.31 L113.33
+ 145.93 C113.33 148.22 111.47 150.08 109.18 150.08 L4.16 150.08 C1.87 150.08 0 148.22 0 145.93 L0 129.31
+ Z" class="st3"/>
+ </g>
+ <g id="shape20-46" v:mID="20" v:groupContext="shape" transform="translate(33.9485,-78.4626)">
+ <title>Sheet.20</title>
+ <desc>Key3: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="49.6862" cy="142.887" width="99.38" height="14.3829"/>
+ <path d="M99.37 135.7 L0 135.7 L0 150.08 L99.37 150.08 L99.37 135.7" class="st4"/>
+ <text x="7.94" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3: Value = 1</text> </g>
+ <g id="shape21-50" v:mID="21" v:groupContext="shape" transform="translate(19.0195,-49.5757)">
+ <title>Sheet.21</title>
+ <path d="M0 129.21 C0 126.91 1.88 125.03 4.19 125.03 L109.15 125.03 C111.46 125.03 113.33 126.91 113.33 129.21 L113.33
+ 145.91 C113.33 148.21 111.46 150.08 109.15 150.08 L4.19 150.08 C1.88 150.08 0 148.21 0 145.91 L0 129.21
+ Z" class="st1"/>
+ </g>
+ <g id="shape22-52" v:mID="22" v:groupContext="shape" transform="translate(19.0195,-49.5757)">
+ <title>Sheet.22</title>
+ <path d="M0 129.21 C0 126.91 1.88 125.03 4.19 125.03 L109.15 125.03 C111.46 125.03 113.33 126.91 113.33 129.21 L113.33
+ 145.91 C113.33 148.21 111.46 150.08 109.15 150.08 L4.19 150.08 C1.88 150.08 0 148.21 0 145.91 L0 129.21
+ Z" class="st3"/>
+ </g>
+ <g id="shape23-54" v:mID="23" v:groupContext="shape" transform="translate(33.9485,-53.4903)">
+ <title>Sheet.23</title>
+ <desc>Key4: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="49.6862" cy="142.887" width="99.38" height="14.3829"/>
+ <path d="M99.37 135.7 L0 135.7 L0 150.08 L99.37 150.08 L99.37 135.7" class="st4"/>
+ <text x="7.94" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4: Value = 0</text> </g>
+ <g id="shape24-58" v:mID="24" v:groupContext="shape" transform="translate(19.0195,-25.0109)">
+ <title>Sheet.24</title>
+ <path d="M0 129.21 C0 126.91 1.88 125.03 4.19 125.03 L109.15 125.03 C111.46 125.03 113.33 126.91 113.33 129.21 L113.33
+ 145.91 C113.33 148.21 111.46 150.08 109.15 150.08 L4.19 150.08 C1.88 150.08 0 148.21 0 145.91 L0 129.21
+ Z" class="st1"/>
+ </g>
+ <g id="shape25-60" v:mID="25" v:groupContext="shape" transform="translate(19.0195,-25.0109)">
+ <title>Sheet.25</title>
+ <path d="M0 129.21 C0 126.91 1.88 125.03 4.19 125.03 L109.15 125.03 C111.46 125.03 113.33 126.91 113.33 129.21 L113.33
+ 145.91 C113.33 148.21 111.46 150.08 109.15 150.08 L4.19 150.08 C1.88 150.08 0 148.21 0 145.91 L0 129.21
+ Z" class="st3"/>
+ </g>
+ <g id="shape26-62" v:mID="26" v:groupContext="shape" transform="translate(33.9485,-28.927)">
+ <title>Sheet.26</title>
+ <desc>Key7: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="49.6862" cy="142.887" width="99.38" height="14.3829"/>
+ <path d="M99.37 135.7 L0 135.7 L0 150.08 L99.37 150.08 L99.37 135.7" class="st4"/>
+ <text x="7.94" y="146.48" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7: Value = 1</text> </g>
+ <g id="shape27-66" v:mID="27" v:groupContext="shape" transform="translate(141.536,-51.5529)">
+ <title>Sheet.27</title>
+ <path d="M0 115.39 L22.75 115.39 L22.75 103.82 L45.5 126.95 L22.75 150.08 L22.75 138.51 L0 138.51 L0 115.39 Z"
+ class="st7"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/img/efd_i9.svg b/doc/guides/prog_guide/img/efd_i9.svg
new file mode 100644
index 0000000..b2e385d
--- /dev/null
+++ b/doc/guides/prog_guide/img/efd_i9.svg
@@ -0,0 +1,390 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i10.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="9.8125in" height="3.76365in"
+ viewBox="0 0 706.5 270.983" xml:space="preserve" color-interpolation-filters="sRGB" class="st9">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st2 {fill:none;stroke:#00aeef;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.03901}
+ .st3 {stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st4 {fill:#000000;font-family:Arial;font-size:0.998566em}
+ .st5 {fill:#000000;font-family:Arial;font-size:0.918686em}
+ .st6 {fill:#0071c5;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st7 {fill:#ffffff;font-family:Arial;font-size:0.998566em}
+ .st8 {fill:#ffffff;font-family:Arial;font-size:1.49785em}
+ .st9 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape68-1" v:mID="68" v:groupContext="shape" transform="translate(196.523,-158.978)">
+ <title>Sheet.68</title>
+ <path d="M0 250.22 C0 247.95 1.89 246.06 4.17 246.06 L317.25 246.06 C319.53 246.06 321.39 247.95 321.39 250.22 L321.39
+ 266.85 C321.39 269.13 319.53 270.98 317.25 270.98 L4.17 270.98 C1.89 270.98 0 269.13 0 266.85 L0 250.22
+ Z" class="st1"/>
+ </g>
+ <g id="shape69-3" v:mID="69" v:groupContext="shape" transform="translate(196.523,-158.978)">
+ <title>Sheet.69</title>
+ <path d="M0 250.22 C0 247.95 1.89 246.06 4.17 246.06 L317.25 246.06 C319.53 246.06 321.39 247.95 321.39 250.22 L321.39
+ 266.85 C321.39 269.13 319.53 270.98 317.25 270.98 L4.17 270.98 C1.89 270.98 0 269.13 0 266.85 L0 250.22
+ Z" class="st2"/>
+ </g>
+ <g id="shape70-5" v:mID="70" v:groupContext="shape" transform="translate(186.139,-162.437)">
+ <title>Sheet.70</title>
+ <desc>(hash(key, seed1) + hash_index *</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="106.671" cy="263.792" width="213.35" height="14.3829"/>
+ <path d="M213.34 256.6 L0 256.6 L0 270.98 L213.34 270.98 L213.34 256.6" class="st3"/>
+ <text x="17.24" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>(hash(key, seed1) + hash_index * </text> </g>
+ <g id="shape71-9" v:mID="71" v:groupContext="shape" transform="translate(381.48,-162.845)">
+ <title>Sheet.71</title>
+ <desc>hash(key</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="27.4843" cy="264.367" width="54.97" height="13.2327"/>
+ <path d="M54.97 257.75 L0 257.75 L0 270.98 L54.97 270.98 L54.97 257.75" class="st3"/>
+ <text x="5.12" y="267.67" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash(key</text> </g>
+ <g id="shape72-13" v:mID="72" v:groupContext="shape" transform="translate(424.755,-162.437)">
+ <title>Sheet.72</title>
+ <desc>, seed2)) % 16</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="46.7254" cy="263.792" width="93.46" height="14.3829"/>
+ <path d="M93.45 256.6 L0 256.6 L0 270.98 L93.45 270.98 L93.45 256.6" class="st3"/>
+ <text x="7.76" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>, seed2)) % 16</text> </g>
+ <g id="shape73-17" v:mID="73" v:groupContext="shape" transform="translate(524.094,-148.373)">
+ <title>Sheet.73</title>
+ <path d="M0 236.29 L22.75 236.29 L22.75 224.73 L45.5 247.86 L22.75 270.98 L22.75 259.42 L0 259.42 L0 236.29 Z"
+ class="st6"/>
+ </g>
+ <g id="shape74-19" v:mID="74" v:groupContext="shape" transform="translate(574.148,-217.574)">
+ <title>Sheet.74</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st1"/>
+ </g>
+ <g id="shape75-21" v:mID="75" v:groupContext="shape" transform="translate(574.148,-217.574)">
+ <title>Sheet.75</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st2"/>
+ </g>
+ <g id="shape76-23" v:mID="76" v:groupContext="shape" transform="translate(584.296,-231.499)">
+ <title>Sheet.76</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="40.993" cy="263.792" width="81.99" height="14.3829"/>
+ <path d="M81.99 256.6 L0 256.6 L0 270.98 L81.99 270.98 L81.99 256.6" class="st3"/>
+ <text x="7.01" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape77-27" v:mID="77" v:groupContext="shape" transform="translate(655.369,-231.499)">
+ <title>Sheet.77</title>
+ <desc>bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1076" cy="263.792" width="22.22" height="14.3829"/>
+ <path d="M22.22 256.6 L0 256.6 L0 270.98 L22.22 270.98 L22.22 256.6" class="st3"/>
+ <text x="4.78" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit </text> </g>
+ <g id="shape78-31" v:mID="78" v:groupContext="shape" transform="translate(588.858,-217.12)">
+ <title>Sheet.78</title>
+ <desc>index for key1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="44.8113" cy="263.792" width="89.63" height="14.3829"/>
+ <path d="M89.62 256.6 L0 256.6 L0 270.98 L89.62 270.98 L89.62 256.6" class="st3"/>
+ <text x="7.51" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>index for key1</text> </g>
+ <g id="shape79-35" v:mID="79" v:groupContext="shape" transform="translate(573.548,-178.869)">
+ <title>Sheet.79</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st1"/>
+ </g>
+ <g id="shape80-37" v:mID="80" v:groupContext="shape" transform="translate(573.548,-178.869)">
+ <title>Sheet.80</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st2"/>
+ </g>
+ <g id="shape81-39" v:mID="81" v:groupContext="shape" transform="translate(584.296,-192.768)">
+ <title>Sheet.81</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="40.993" cy="263.792" width="81.99" height="14.3829"/>
+ <path d="M81.99 256.6 L0 256.6 L0 270.98 L81.99 270.98 L81.99 256.6" class="st3"/>
+ <text x="7.01" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape82-43" v:mID="82" v:groupContext="shape" transform="translate(655.369,-192.768)">
+ <title>Sheet.82</title>
+ <desc>bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1076" cy="263.792" width="22.22" height="14.3829"/>
+ <path d="M22.22 256.6 L0 256.6 L0 270.98 L22.22 270.98 L22.22 256.6" class="st3"/>
+ <text x="4.78" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit </text> </g>
+ <g id="shape83-47" v:mID="83" v:groupContext="shape" transform="translate(588.858,-178.388)">
+ <title>Sheet.83</title>
+ <desc>index for key3</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="44.8113" cy="263.792" width="89.63" height="14.3829"/>
+ <path d="M89.62 256.6 L0 256.6 L0 270.98 L89.62 270.98 L89.62 256.6" class="st3"/>
+ <text x="7.51" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>index for key3</text> </g>
+ <g id="shape84-51" v:mID="84" v:groupContext="shape" transform="translate(574.148,-139.326)">
+ <title>Sheet.84</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st1"/>
+ </g>
+ <g id="shape85-53" v:mID="85" v:groupContext="shape" transform="translate(574.148,-139.326)">
+ <title>Sheet.85</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st2"/>
+ </g>
+ <g id="shape86-55" v:mID="86" v:groupContext="shape" transform="translate(584.296,-153.227)">
+ <title>Sheet.86</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="40.993" cy="263.792" width="81.99" height="14.3829"/>
+ <path d="M81.99 256.6 L0 256.6 L0 270.98 L81.99 270.98 L81.99 256.6" class="st3"/>
+ <text x="7.01" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape87-59" v:mID="87" v:groupContext="shape" transform="translate(655.369,-153.227)">
+ <title>Sheet.87</title>
+ <desc>bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1076" cy="263.792" width="22.22" height="14.3829"/>
+ <path d="M22.22 256.6 L0 256.6 L0 270.98 L22.22 270.98 L22.22 256.6" class="st3"/>
+ <text x="4.78" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit </text> </g>
+ <g id="shape88-63" v:mID="88" v:groupContext="shape" transform="translate(588.858,-138.848)">
+ <title>Sheet.88</title>
+ <desc>index for key4</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="44.8113" cy="263.792" width="89.63" height="14.3829"/>
+ <path d="M89.62 256.6 L0 256.6 L0 270.98 L89.62 270.98 L89.62 256.6" class="st3"/>
+ <text x="7.51" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>index for key4</text> </g>
+ <g id="shape89-67" v:mID="89" v:groupContext="shape" transform="translate(574.148,-100.622)">
+ <title>Sheet.89</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st1"/>
+ </g>
+ <g id="shape90-69" v:mID="90" v:groupContext="shape" transform="translate(574.148,-100.622)">
+ <title>Sheet.90</title>
+ <path d="M0 244.83 C-0 241.95 2.37 239.59 5.25 239.59 L108.11 239.59 C110.99 239.59 113.33 241.95 113.33 244.83 L113.33
+ 265.77 C113.33 268.65 110.99 270.98 108.11 270.98 L5.25 270.98 C2.37 270.98 0 268.65 0 265.77 L0 244.83
+ Z" class="st2"/>
+ </g>
+ <g id="shape91-71" v:mID="91" v:groupContext="shape" transform="translate(584.296,-114.496)">
+ <title>Sheet.91</title>
+ <desc>lookup_table</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="40.993" cy="263.792" width="81.99" height="14.3829"/>
+ <path d="M81.99 256.6 L0 256.6 L0 270.98 L81.99 270.98 L81.99 256.6" class="st3"/>
+ <text x="7.01" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>lookup_table</text> </g>
+ <g id="shape92-75" v:mID="92" v:groupContext="shape" transform="translate(655.369,-114.496)">
+ <title>Sheet.92</title>
+ <desc>bit</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="11.1076" cy="263.792" width="22.22" height="14.3829"/>
+ <path d="M22.22 256.6 L0 256.6 L0 270.98 L22.22 270.98 L22.22 256.6" class="st3"/>
+ <text x="4.78" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit </text> </g>
+ <g id="shape93-79" v:mID="93" v:groupContext="shape" transform="translate(588.858,-100.117)">
+ <title>Sheet.93</title>
+ <desc>index for key7</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="44.8113" cy="263.792" width="89.63" height="14.3829"/>
+ <path d="M89.62 256.6 L0 256.6 L0 270.98 L89.62 270.98 L89.62 256.6" class="st3"/>
+ <text x="7.51" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>index for key7</text> </g>
+ <g id="shape94-83" v:mID="94" v:groupContext="shape" transform="translate(205.227,-191.137)">
+ <title>Sheet.94</title>
+ <path d="M0 217.76 C0 213 3.87 209.14 8.64 209.14 L14.53 209.14 L14.53 209.14 L36.32 209.14 L78.52 209.14 C83.3 209.14
+ 87.16 213 87.16 217.76 L87.16 239.33 L87.16 239.33 L87.16 252.27 L87.16 252.27 C87.16 257.05 83.3 260.9
+ 78.52 260.9 L36.32 260.9 L18.46 270.98 L14.53 260.9 L8.64 260.9 C3.87 260.9 0 257.05 0 252.27 L0 239.33
+ L0 239.33 L0 217.76 Z" class="st6"/>
+ </g>
+ <g id="shape95-85" v:mID="95" v:groupContext="shape" transform="translate(214.98,-225.215)">
+ <title>Sheet.95</title>
+ <desc>CRC32 (32</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="38.2947" cy="263.792" width="76.59" height="14.3829"/>
+ <path d="M76.59 256.6 L0 256.6 L0 270.98 L76.59 270.98 L76.59 256.6" class="st3"/>
+ <text x="8.33" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>CRC32 (32 </text> </g>
+ <g id="shape96-89" v:mID="96" v:groupContext="shape" transform="translate(222.123,-210.835)">
+ <title>Sheet.96</title>
+ <desc>bit output)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="32.5584" cy="263.792" width="65.12" height="14.3829"/>
+ <path d="M65.12 256.6 L0 256.6 L0 270.98 L65.12 270.98 L65.12 256.6" class="st3"/>
+ <text x="5.91" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit output)</text> </g>
+ <g id="shape97-93" v:mID="97" v:groupContext="shape" transform="translate(305.473,-188.366)">
+ <title>Sheet.97</title>
+ <path d="M0 226.84 C0 223.28 2.9 220.39 6.47 220.39 L21.37 220.39 L21.37 220.39 L53.42 220.39 L121.77 220.39 C125.34
+ 220.39 128.22 223.28 128.22 226.84 L128.22 242.97 L128.22 242.97 L128.22 252.65 L128.22 252.65 C128.22 256.21
+ 125.34 259.09 121.77 259.09 L53.42 259.09 L38.73 270.98 L21.37 259.09 L6.47 259.09 C2.9 259.09 0 256.21
+ 0 252.65 L0 242.97 L0 242.97 L0 226.84 Z" class="st6"/>
+ </g>
+ <g id="shape98-95" v:mID="98" v:groupContext="shape" transform="translate(318.48,-217.733)">
+ <title>Sheet.98</title>
+ <desc>Goal: Find a valid</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="57.4478" cy="263.792" width="114.9" height="14.3829"/>
+ <path d="M114.9 256.6 L0 256.6 L0 270.98 L114.9 270.98 L114.9 256.6" class="st3"/>
+ <text x="10.82" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Goal: Find a valid </text> </g>
+ <g id="shape99-99" v:mID="99" v:groupContext="shape" transform="translate(339.077,-203.354)">
+ <title>Sheet.99</title>
+ <desc>hash_index</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="37.1611" cy="263.792" width="74.33" height="14.3829"/>
+ <path d="M74.32 256.6 L0 256.6 L0 270.98 L74.32 270.98 L74.32 256.6" class="st3"/>
+ <text x="6.51" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>hash_index</text> </g>
+ <g id="shape100-103" v:mID="100" v:groupContext="shape" transform="translate(438.135,-185.939)">
+ <title>Sheet.100</title>
+ <path d="M0 217.36 C0 213.8 2.91 210.89 6.48 210.89 L21.37 210.89 L21.37 210.89 L53.42 210.89 L121.77 210.89 C125.34
+ 210.89 128.22 213.8 128.22 217.36 L128.22 233.48 L128.22 233.48 L128.22 243.15 L128.22 243.15 C128.22 246.72
+ 125.34 249.59 121.77 249.59 L53.42 249.59 L54.75 270.98 L21.37 249.59 L6.48 249.59 C2.91 249.59 0 246.72
+ 0 243.15 L0 233.48 L0 233.48 L0 217.36 Z" class="st6"/>
+ </g>
+ <g id="shape101-105" v:mID="101" v:groupContext="shape" transform="translate(448.763,-224.802)">
+ <title>Sheet.101</title>
+ <desc>Lookup Table has</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="58.6085" cy="263.792" width="117.22" height="14.3829"/>
+ <path d="M117.22 256.6 L0 256.6 L0 270.98 L117.22 270.98 L117.22 256.6" class="st3"/>
+ <text x="10.98" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Lookup Table has </text> </g>
+ <g id="shape102-109" v:mID="102" v:groupContext="shape" transform="translate(484.549,-210.423)">
+ <title>Sheet.102</title>
+ <desc>16 bits</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="22.2166" cy="263.792" width="44.44" height="14.3829"/>
+ <path d="M44.43 256.6 L0 256.6 L0 270.98 L44.43 270.98 L44.43 256.6" class="st3"/>
+ <text x="4.56" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>16 bits</text> </g>
+ <g id="shape103-113" v:mID="103" v:groupContext="shape" transform="translate(369.583,-90.8555)">
+ <title>Sheet.103</title>
+ <path d="M0 227.76 C0 222.98 3.89 219.1 8.67 219.1 L14.53 219.1 L34.47 205.09 L36.32 219.1 L78.5 219.1 C83.29 219.1 87.16
+ 222.98 87.16 227.76 L87.16 227.76 L87.16 240.73 L87.16 262.34 C87.16 267.12 83.29 270.98 78.5 270.98 L36.32
+ 270.98 L14.53 270.98 L14.53 270.98 L8.67 270.98 C3.89 270.98 0 267.12 0 262.34 L0 240.73 L0 227.76 L0 227.76
+ Z" class="st6"/>
+ </g>
+ <g id="shape104-115" v:mID="104" v:groupContext="shape" transform="translate(383.264,-114.932)">
+ <title>Sheet.104</title>
+ <desc>CRC32 (32</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="38.2947" cy="263.792" width="76.59" height="14.3829"/>
+ <path d="M76.59 256.6 L0 256.6 L0 270.98 L76.59 270.98 L76.59 256.6" class="st3"/>
+ <text x="8.33" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>CRC32 (32 </text> </g>
+ <g id="shape105-119" v:mID="105" v:groupContext="shape" transform="translate(386.505,-100.553)">
+ <title>Sheet.105</title>
+ <desc>bit output)</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="32.5584" cy="263.792" width="65.12" height="14.3829"/>
+ <path d="M65.12 256.6 L0 256.6 L0 270.98 L65.12 270.98 L65.12 256.6" class="st3"/>
+ <text x="5.91" y="267.39" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>bit output)</text> </g>
+ <g id="shape106-123" v:mID="106" v:groupContext="shape" transform="translate(313.397,-18)">
+ <title>Sheet.106</title>
+ <path d="M0 226.35 C0 221.43 4.02 217.42 8.94 217.42 L347.02 217.42 C351.97 217.42 355.96 221.43 355.96 226.35 L355.96
+ 262.06 C355.96 267 351.97 270.98 347.02 270.98 L8.94 270.98 C4.02 270.98 0 267 0 262.06 L0 226.35 Z"
+ class="st6"/>
+ </g>
+ <g id="shape107-125" v:mID="107" v:groupContext="shape" transform="translate(313.98,-41.963)">
+ <title>Sheet.107</title>
+ <desc>Goal is to find a hash_index that produces</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="177.75" cy="260.197" width="355.5" height="21.5726"/>
+ <path d="M355.5 249.41 L0 249.41 L0 270.98 L355.5 270.98 L355.5 249.41" class="st3"/>
+ <text x="9.88" y="265.59" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Goal is to find a hash_index that produces </text> </g>
+ <g id="shape108-129" v:mID="108" v:groupContext="shape" transform="translate(318.48,-20.3939)">
+ <title>Sheet.108</title>
+ <desc>a lookup_table with no contradictions</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="175.5" cy="260.197" width="351" height="21.5726"/>
+ <path d="M351 249.41 L0 249.41 L0 270.98 L351 270.98 L351 249.41" class="st3"/>
+ <text x="28.12" y="265.59" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>a lookup_table with no contradictions</text> </g>
+ <g id="shape109-133" v:mID="109" v:groupContext="shape" transform="translate(18,-196.244)">
+ <title>Sheet.109</title>
+ <path d="M0 250.22 C0 247.92 1.87 246.06 4.16 246.06 L109.18 246.06 C111.47 246.06 113.33 247.92 113.33 250.22 L113.33
+ 266.83 C113.33 269.13 111.47 270.98 109.18 270.98 L4.16 270.98 C1.87 270.98 0 269.13 0 266.83 L0 250.22
+ Z" class="st1"/>
+ </g>
+ <g id="shape110-135" v:mID="110" v:groupContext="shape" transform="translate(29.8201,-196.244)">
+ <title>Sheet.110</title>
+ <path d="M0 250.22 C-0 247.92 1.67 246.06 3.73 246.06 L97.79 246.06 C99.85 246.06 101.51 247.92 101.51 250.22 L101.51
+ 266.83 C101.51 269.13 99.85 270.98 97.79 270.98 L3.73 270.98 C1.67 270.98 0 269.13 0 266.83 L0 250.22 Z"
+ class="st2"/>
+ </g>
+ <g id="shape111-137" v:mID="111" v:groupContext="shape" transform="translate(32.5663,-199.746)">
+ <title>Sheet.111</title>
+ <desc>Key1: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.7562" cy="263.792" width="101.52" height="14.3829"/>
+ <path d="M101.51 256.6 L0 256.6 L0 270.98 L101.51 270.98 L101.51 256.6" class="st3"/>
+ <text x="8.29" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key1: Value = 0</text> </g>
+ <g id="shape112-141" v:mID="112" v:groupContext="shape" transform="translate(18,-171.44)">
+ <title>Sheet.112</title>
+ <path d="M0 250.22 C0 247.92 1.87 246.06 4.16 246.06 L109.18 246.06 C111.47 246.06 113.33 247.92 113.33 250.22 L113.33
+ 266.83 C113.33 269.13 111.47 270.98 109.18 270.98 L4.16 270.98 C1.87 270.98 0 269.13 0 266.83 L0 250.22
+ Z" class="st1"/>
+ </g>
+ <g id="shape113-143" v:mID="113" v:groupContext="shape" transform="translate(29.8201,-171.44)">
+ <title>Sheet.113</title>
+ <path d="M0 250.22 C0 247.92 1.67 246.06 3.73 246.06 L97.79 246.06 C99.85 246.06 101.51 247.92 101.51 250.22 L101.51
+ 266.83 C101.51 269.13 99.85 270.98 97.79 270.98 L3.73 270.98 C1.67 270.98 0 269.13 0 266.83 L0 250.22 Z"
+ class="st2"/>
+ </g>
+ <g id="shape114-145" v:mID="114" v:groupContext="shape" transform="translate(32.5663,-174.923)">
+ <title>Sheet.114</title>
+ <desc>Key3: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.7562" cy="263.792" width="101.52" height="14.3829"/>
+ <path d="M101.51 256.6 L0 256.6 L0 270.98 L101.51 270.98 L101.51 256.6" class="st3"/>
+ <text x="8.29" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key3: Value = 1</text> </g>
+ <g id="shape115-149" v:mID="115" v:groupContext="shape" transform="translate(18,-146.396)">
+ <title>Sheet.115</title>
+ <path d="M0 250.12 C0 247.81 1.88 245.94 4.19 245.94 L109.15 245.94 C111.46 245.94 113.33 247.81 113.33 250.12 L113.33
+ 266.81 C113.33 269.12 111.46 270.98 109.15 270.98 L4.19 270.98 C1.88 270.98 0 269.12 0 266.81 L0 250.12
+ Z" class="st1"/>
+ </g>
+ <g id="shape116-151" v:mID="116" v:groupContext="shape" transform="translate(29.8201,-146.396)">
+ <title>Sheet.116</title>
+ <path d="M0 250.12 C0 247.81 1.68 245.94 3.75 245.94 L97.77 245.94 C99.84 245.94 101.51 247.81 101.51 250.12 L101.51
+ 266.81 C101.51 269.12 99.84 270.98 97.77 270.98 L3.75 270.98 C1.68 270.98 0 269.12 0 266.81 L0 250.12 Z"
+ class="st2"/>
+ </g>
+ <g id="shape117-153" v:mID="117" v:groupContext="shape" transform="translate(32.5663,-149.951)">
+ <title>Sheet.117</title>
+ <desc>Key4: Value = 0</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.7562" cy="263.792" width="101.52" height="14.3829"/>
+ <path d="M101.51 256.6 L0 256.6 L0 270.98 L101.51 270.98 L101.51 256.6" class="st3"/>
+ <text x="8.29" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key4: Value = 0</text> </g>
+ <g id="shape118-157" v:mID="118" v:groupContext="shape" transform="translate(18,-121.831)">
+ <title>Sheet.118</title>
+ <path d="M0 250.12 C0 247.81 1.88 245.94 4.19 245.94 L109.15 245.94 C111.46 245.94 113.33 247.81 113.33 250.12 L113.33
+ 266.81 C113.33 269.12 111.46 270.98 109.15 270.98 L4.19 270.98 C1.88 270.98 0 269.12 0 266.81 L0 250.12
+ Z" class="st1"/>
+ </g>
+ <g id="shape119-159" v:mID="119" v:groupContext="shape" transform="translate(29.8201,-121.831)">
+ <title>Sheet.119</title>
+ <path d="M0 250.12 C0 247.81 1.68 245.94 3.75 245.94 L97.77 245.94 C99.84 245.94 101.51 247.81 101.51 250.12 L101.51
+ 266.81 C101.51 269.12 99.84 270.98 97.77 270.98 L3.75 270.98 C1.68 270.98 0 269.12 0 266.81 L0 250.12 Z"
+ class="st2"/>
+ </g>
+ <g id="shape120-161" v:mID="120" v:groupContext="shape" transform="translate(32.5663,-125.388)">
+ <title>Sheet.120</title>
+ <desc>Key7: Value = 1</desc>
+ <v:textBlock v:margins="rect(0,0,0,0)"/>
+ <v:textRect cx="50.7562" cy="263.792" width="101.52" height="14.3829"/>
+ <path d="M101.51 256.6 L0 256.6 L0 270.98 L101.51 270.98 L101.51 256.6" class="st3"/>
+ <text x="8.29" y="267.39" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key7: Value = 1</text> </g>
+ <g id="shape121-165" v:mID="121" v:groupContext="shape" transform="translate(140.517,-148.373)">
+ <title>Sheet.121</title>
+ <path d="M0 236.29 L22.75 236.29 L22.75 224.73 L45.5 247.86 L22.75 270.98 L22.75 259.42 L0 259.42 L0 236.29 Z"
+ class="st6"/>
+ </g>
+ </g>
+</svg>
diff --git a/doc/guides/prog_guide/index.rst b/doc/guides/prog_guide/index.rst
index ed7f770..7f825cb 100644
--- a/doc/guides/prog_guide/index.rst
+++ b/doc/guides/prog_guide/index.rst
@@ -47,6 +47,7 @@ Programmer's Guide
link_bonding_poll_mode_drv_lib
timer_lib
hash_lib
+ efd_lib
lpm_lib
lpm6_lib
packet_distrib_lib
@@ -167,6 +168,28 @@ Programmer's Guide
:numref:`figure_figure39` :ref:`figure_figure39`
+:numref:`figure_efd1` :ref:`figure_efd1`
+
+:numref:`figure_efd2` :ref:`figure_efd2`
+
+:numref:`figure_efd3` :ref:`figure_efd3`
+
+:numref:`figure_efd4` :ref:`figure_efd4`
+
+:numref:`figure_efd5` :ref:`figure_efd5`
+
+:numref:`figure_efd6` :ref:`figure_efd6`
+
+:numref:`figure_efd7` :ref:`figure_efd7`
+
+:numref:`figure_efd8` :ref:`figure_efd8`
+
+:numref:`figure_efd9` :ref:`figure_efd9`
+
+:numref:`figure_efd10` :ref:`figure_efd10`
+
+:numref:`figure_efd11` :ref:`figure_efd11`
+
**Tables**
diff --git a/doc/guides/rel_notes/release_17_02.rst b/doc/guides/rel_notes/release_17_02.rst
index 32ea8d9..b0a032b 100644
--- a/doc/guides/rel_notes/release_17_02.rst
+++ b/doc/guides/rel_notes/release_17_02.rst
@@ -107,6 +107,9 @@ New Features
is much smaller than a hash-based flow table and therefore, it can better fit for
CPU cache, being able to scale to millions of flow keys.
+ See the :ref:`Elastic Flow Distributor Library <Efd_Library>` documentation in
+ the Programmers Guide document, for more information.
+
Resolved Issues
---------------
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* [dpdk-dev] [PATCH v8 6/6] doc: add flow distributor guide
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 " Pablo de Lara
` (4 preceding siblings ...)
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 5/6] doc: add EFD library section in Programmers guide Pablo de Lara
@ 2017-01-17 22:23 ` Pablo de Lara
2017-01-18 19:57 ` [dpdk-dev] [PATCH v8 0/6] Elastic Flow Distributor Thomas Monjalon
6 siblings, 0 replies; 63+ messages in thread
From: Pablo de Lara @ 2017-01-17 22:23 UTC (permalink / raw)
To: dev; +Cc: Pablo de Lara, Sameh Gobriel
Signed-off-by: Sameh Gobriel <sameh.gobriel@intel.com>
Signed-off-by: Pablo de Lara <pablo.de.lara.guarch@intel.com>
Acked-by: Christian Maciocco <christian.maciocco@intel.com>
---
MAINTAINERS | 1 +
doc/guides/sample_app_ug/flow_distributor.rst | 494 ++++++++
doc/guides/sample_app_ug/img/flow_distributor.svg | 1254 +++++++++++++++++++++
doc/guides/sample_app_ug/index.rst | 1 +
4 files changed, 1750 insertions(+)
create mode 100644 doc/guides/sample_app_ug/flow_distributor.rst
create mode 100644 doc/guides/sample_app_ug/img/flow_distributor.svg
diff --git a/MAINTAINERS b/MAINTAINERS
index 66e9466..0d3b247 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -535,6 +535,7 @@ F: lib/librte_efd/
F: doc/guides/prog_guide/efd_lib.rst
F: app/test/test_efd*
F: examples/flow_distributor/
+F: doc/guides/sample_app_ug/flow_distributor.rst
Hashes
M: Bruce Richardson <bruce.richardson@intel.com>
diff --git a/doc/guides/sample_app_ug/flow_distributor.rst b/doc/guides/sample_app_ug/flow_distributor.rst
new file mode 100644
index 0000000..a12df76
--- /dev/null
+++ b/doc/guides/sample_app_ug/flow_distributor.rst
@@ -0,0 +1,494 @@
+.. BSD LICENSE
+ Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of Intel Corporation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Flow Distributor Sample Application
+===================================
+
+This sample application demonstrates the use of EFD library as a flow-level
+load balancer, for more information about the EFD Library please refer to the
+DPDK programmer's guide.
+
+This sample application is a variant of the
+:ref:`client-server sample application <multi_process_app>`
+where a specific target node is specified for every and each flow
+(not in a round-robin fashion as the original load balancing sample application).
+
+Overview
+--------
+
+The architecture of the EFD flow-based load balancer sample application is
+presented in the following figure.
+
+.. _figure_efd_sample_app_overview:
+
+.. figure:: img/flow_distributor.*
+
+ Using EFD as a Flow-Level Load Balancer
+
+As shown in :numref:`figure_efd_sample_app_overview`,
+the sample application consists of a front-end node (distributor)
+using the EFD library to create a load-balancing table for flows,
+for each flow a target backend worker node is specified. The EFD table does not
+store the flow key (unlike a regular hash table), and hence, it can
+individually load-balance millions of flows (number of targets * maximum number
+of flows fit in a flow table per target) while still fitting in CPU cache.
+
+It should be noted that although they are referred to as nodes, the frontend
+distributor and worker nodes are processes running on the same platform.
+
+Front-end Distributor
+~~~~~~~~~~~~~~~~~~~~~
+
+Upon initializing, the frontend distributor node (process) creates a flow
+distributor table (based on the EFD library) which is populated with flow
+information and its intended target node.
+
+The sample application assigns a specific target node_id (process) for each of
+the IP destination addresses as follows:
+
+.. code-block:: c
+
+ node_id = i % num_nodes; /* Target node id is generated */
+ ip_dst = rte_cpu_to_be_32(i); /* Specific ip destination address is
+ assigned to this target node */
+
+then the pair of <key,target> is inserted into the flow distribution table.
+
+The main loop of the the distributor node receives a burst of packets, then for
+each packet, a flow key (IP destination address) is extracted. The flow
+distributor table is looked up and the target node id is returned. Packets are
+then enqueued to the specified target node id.
+
+It should be noted that flow distributor table is not a membership test table.
+I.e. if the key has already been inserted the target node id will be correct,
+but for new keys the flow distributor table will return a value (which can be
+valid).
+
+Backend Worker Nodes
+~~~~~~~~~~~~~~~~~~~~
+
+Upon initializing, the worker node (process) creates a flow table (a regular
+hash table that stores the key default size 1M flows) which is populated with
+only the flow information that is serviced at this node. This flow key is
+essential to point out new keys that have not been inserted before.
+
+The worker node's main loop is simply receiving packets then doing a hash table
+lookup. If a match occurs then statistics are updated for flows serviced by
+this node. If no match is found in the local hash table then this indicates
+that this is a new flow, which is dropped.
+
+
+Compiling the Application
+-------------------------
+
+The sequence of steps used to build the application is:
+
+#. Export the required environment variables:
+
+ .. code-block:: console
+
+ export RTE_SDK=/path/to/rte_sdk
+ export RTE_TARGET=x86_64-native-linuxapp-gcc
+
+#. Build the application executable file:
+
+ .. code-block:: console
+
+ cd ${RTE_SDK}/examples/flow_distributor/
+ make
+
+ For more details on how to build the DPDK libraries and sample
+ applications,
+ please refer to the *DPDK Getting Started Guide.*
+
+
+Running the Application
+-----------------------
+
+The application has two binaries to be run: the front-end distributor
+and the back-end node.
+
+The frontend distributor (distributor) has the following command line options::
+
+ ./distributor [EAL options] -- -p PORTMASK -n NUM_NODES -f NUM_FLOWS
+
+Where,
+
+* ``-p PORTMASK:`` Hexadecimal bitmask of ports to configure
+* ``-n NUM_NODES:`` Number of back-end nodes that will be used
+* ``-f NUM_FLOWS:`` Number of flows to be added in the EFD table (1 million, by default)
+
+The back-end node (node) has the following command line options::
+
+ ./node [EAL options] -- -n NODE_ID
+
+Where,
+
+* ``-n NODE_ID:`` Node ID, which cannot be equal or higher than NUM_MODES
+
+
+First, the distributor app must be launched, with the number of nodes that will be run.
+Once it has been started, the node instances can be run, with different NODE_ID.
+These instances have to be run as secondary processes, with ``--proc-type=secondary``
+in the EAL options, which will attach to the primary process memory, and therefore,
+they can access the queues created by the primary process to distribute packets.
+
+To successfully run the application, the command line used to start the
+application has to be in sync with the traffic flows configured on the traffic
+generator side.
+
+For examples of application command lines and traffic generator flows, please
+refer to the DPDK Test Report. For more details on how to set up and run the
+sample applications provided with DPDK package, please refer to the
+:ref:`DPDK Getting Started Guide for Linux <linux_gsg>` and
+:ref:`DPDK Getting Started Guide for FreeBSD <freebsd_gsg>`.
+
+
+Explanation
+-----------
+
+As described in previous sections, there are two processes in this example.
+
+The first process, the front-end distributor, creates and populates the EFD table,
+which is used to distribute packets to nodes, which the number of flows
+specified in the command line (1 million, by default).
+
+
+.. code-block:: c
+
+ static void
+ create_flow_distributor_table(void)
+ {
+ uint8_t socket_id = rte_socket_id();
+
+ /* create table */
+ efd_table = rte_efd_create("flow table", num_flows * 2, sizeof(uint32_t),
+ 1 << socket_id, socket_id);
+
+ if (efd_table == NULL)
+ rte_exit(EXIT_FAILURE, "Problem creating the flow table\n");
+ }
+
+ static void
+ populate_flow_distributor_table(void)
+ {
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint8_t socket_id = rte_socket_id();
+ uint64_t node_id;
+
+ /* Add flows in table */
+ for (i = 0; i < num_flows; i++) {
+ node_id = i % num_nodes;
+
+ ip_dst = rte_cpu_to_be_32(i);
+ ret = rte_efd_update(efd_table, socket_id,
+ (void *)&ip_dst, (efd_value_t)node_id);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u in "
+ "flow distributor table\n", i);
+ }
+
+ printf("EFD table: Adding 0x%x keys\n", num_flows);
+ }
+
+After initialization, packets are received from the enabled ports, and the IPv4
+address from the packets is used as a key to look up in the EFD table,
+which tells the node where the packet has to be distributed.
+
+.. code-block:: c
+
+ static void
+ process_packets(uint32_t port_num __rte_unused, struct rte_mbuf *pkts[],
+ uint16_t rx_count, unsigned int socket_id)
+ {
+ uint16_t i;
+ uint8_t node;
+ efd_value_t data[EFD_BURST_MAX];
+ const void *key_ptrs[EFD_BURST_MAX];
+
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[EFD_BURST_MAX];
+
+ for (i = 0; i < rx_count; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(pkts[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = (void *)&ipv4_dst_ip[i];
+ }
+
+ rte_efd_lookup_bulk(efd_table, socket_id, rx_count,
+ (const void **) key_ptrs, data);
+ for (i = 0; i < rx_count; i++) {
+ node = (uint8_t) ((uintptr_t)data[i]);
+
+ if (node >= num_nodes) {
+ /*
+ * Node is out of range, which means that
+ * flow has not been inserted
+ */
+ flow_dist_stats.drop++;
+ rte_pktmbuf_free(pkts[i]);
+ } else {
+ flow_dist_stats.distributed++;
+ enqueue_rx_packet(node, pkts[i]);
+ }
+ }
+
+ for (i = 0; i < num_nodes; i++)
+ flush_rx_queue(i);
+ }
+
+The burst of packets received is enqueued in temporary buffers (per node),
+and enqueued in the shared ring between the distributor and the node.
+After this, a new burst of packets is received and this process is
+repeated infinitely.
+
+.. code-block:: c
+
+ static void
+ flush_rx_queue(uint16_t node)
+ {
+ uint16_t j;
+ struct node *cl;
+
+ if (cl_rx_buf[node].count == 0)
+ return;
+
+ cl = &nodes[node];
+ if (rte_ring_enqueue_bulk(cl->rx_q, (void **)cl_rx_buf[node].buffer,
+ cl_rx_buf[node].count) != 0){
+ for (j = 0; j < cl_rx_buf[node].count; j++)
+ rte_pktmbuf_free(cl_rx_buf[node].buffer[j]);
+ cl->stats.rx_drop += cl_rx_buf[node].count;
+ } else
+ cl->stats.rx += cl_rx_buf[node].count;
+
+ cl_rx_buf[node].count = 0;
+ }
+
+The second process, the back-end node, receives the packets from the shared
+ring with the distributor and send them out, if they belong to the node.
+
+At initialization, it attaches to the distributor process memory, to have
+access to the shared ring, parameters and statistics.
+
+.. code-block:: c
+
+ rx_ring = rte_ring_lookup(get_rx_queue_name(node_id));
+ if (rx_ring == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get RX ring - "
+ "is distributor process running?\n");
+
+ mp = rte_mempool_lookup(PKTMBUF_POOL_NAME);
+ if (mp == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get mempool for mbufs\n");
+
+ mz = rte_memzone_lookup(MZ_SHARED_INFO);
+ if (mz == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot get port info structure\n");
+ info = mz->addr;
+ tx_stats = &(info->tx_stats[node_id]);
+ filter_stats = &(info->filter_stats[node_id]);
+
+Then, the hash table that contains the flows that will be handled
+by the node is created and populated.
+
+.. code-block:: c
+
+ static struct rte_hash *
+ create_hash_table(const struct shared_info *info)
+ {
+ uint32_t num_flows_node = info->num_flows / info->num_nodes;
+ char name[RTE_HASH_NAMESIZE];
+ struct rte_hash *h;
+
+ /* create table */
+ struct rte_hash_parameters hash_params = {
+ .entries = num_flows_node * 2, /* table load = 50% */
+ .key_len = sizeof(uint32_t), /* Store IPv4 dest IP address */
+ .socket_id = rte_socket_id(),
+ .hash_func_init_val = 0,
+ };
+
+ snprintf(name, sizeof(name), "hash_table_%d", node_id);
+ hash_params.name = name;
+ h = rte_hash_create(&hash_params);
+
+ if (h == NULL)
+ rte_exit(EXIT_FAILURE,
+ "Problem creating the hash table for node %d\n",
+ node_id);
+ return h;
+ }
+
+ static void
+ populate_hash_table(const struct rte_hash *h, const struct shared_info *info)
+ {
+ unsigned int i;
+ int32_t ret;
+ uint32_t ip_dst;
+ uint32_t num_flows_node = 0;
+ uint64_t target_node;
+
+ /* Add flows in table */
+ for (i = 0; i < info->num_flows; i++) {
+ target_node = i % info->num_nodes;
+ if (target_node != node_id)
+ continue;
+
+ ip_dst = rte_cpu_to_be_32(i);
+
+ ret = rte_hash_add_key(h, (void *) &ip_dst);
+ if (ret < 0)
+ rte_exit(EXIT_FAILURE, "Unable to add entry %u "
+ "in hash table\n", i);
+ else
+ num_flows_node++;
+
+ }
+
+ printf("Hash table: Adding 0x%x keys\n", num_flows_node);
+ }
+
+After initialization, packets are dequeued from the shared ring
+(from the distributor) and, like in the distributor process,
+the IPv4 address from the packets is used as a key to look up in the hash table.
+If there is a hit, packet is stored in a buffer, to be eventually transmitted
+in one of the enabled ports. If key is not there, packet is dropped, since the
+flow is not handled by the node.
+
+.. code-block:: c
+
+ static inline void
+ handle_packets(struct rte_hash *h, struct rte_mbuf **bufs, uint16_t num_packets)
+ {
+ struct ipv4_hdr *ipv4_hdr;
+ uint32_t ipv4_dst_ip[PKT_READ_SIZE];
+ const void *key_ptrs[PKT_READ_SIZE];
+ unsigned int i;
+ int32_t positions[PKT_READ_SIZE] = {0};
+
+ for (i = 0; i < num_packets; i++) {
+ /* Handle IPv4 header.*/
+ ipv4_hdr = rte_pktmbuf_mtod_offset(bufs[i], struct ipv4_hdr *,
+ sizeof(struct ether_hdr));
+ ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+ key_ptrs[i] = &ipv4_dst_ip[i];
+ }
+ /* Check if packets belongs to any flows handled by this node */
+ rte_hash_lookup_bulk(h, key_ptrs, num_packets, positions);
+
+ for (i = 0; i < num_packets; i++) {
+ if (likely(positions[i] >= 0)) {
+ filter_stats->passed++;
+ transmit_packet(bufs[i]);
+ } else {
+ filter_stats->drop++;
+ /* Drop packet, as flow is not handled by this node */
+ rte_pktmbuf_free(bufs[i]);
+ }
+ }
+ }
+
+Finally, note that both processes updates statistics, such as transmitted, received
+and dropped packets, which are shown and refreshed by the distributor app.
+
+.. code-block:: c
+
+ static void
+ do_stats_display(void)
+ {
+ unsigned int i, j;
+ const char clr[] = {27, '[', '2', 'J', '\0'};
+ const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
+ uint64_t port_tx[RTE_MAX_ETHPORTS], port_tx_drop[RTE_MAX_ETHPORTS];
+ uint64_t node_tx[MAX_NODES], node_tx_drop[MAX_NODES];
+
+ /* to get TX stats, we need to do some summing calculations */
+ memset(port_tx, 0, sizeof(port_tx));
+ memset(port_tx_drop, 0, sizeof(port_tx_drop));
+ memset(node_tx, 0, sizeof(node_tx));
+ memset(node_tx_drop, 0, sizeof(node_tx_drop));
+
+ for (i = 0; i < num_nodes; i++) {
+ const struct tx_stats *tx = &info->tx_stats[i];
+
+ for (j = 0; j < info->num_ports; j++) {
+ const uint64_t tx_val = tx->tx[info->id[j]];
+ const uint64_t drop_val = tx->tx_drop[info->id[j]];
+
+ port_tx[j] += tx_val;
+ port_tx_drop[j] += drop_val;
+ node_tx[i] += tx_val;
+ node_tx_drop[i] += drop_val;
+ }
+ }
+
+ /* Clear screen and move to top left */
+ printf("%s%s", clr, topLeft);
+
+ printf("PORTS\n");
+ printf("-----\n");
+ for (i = 0; i < info->num_ports; i++)
+ printf("Port %u: '%s'\t", (unsigned int)info->id[i],
+ get_printable_mac_addr(info->id[i]));
+ printf("\n\n");
+ for (i = 0; i < info->num_ports; i++) {
+ printf("Port %u - rx: %9"PRIu64"\t"
+ "tx: %9"PRIu64"\n",
+ (unsigned int)info->id[i], info->rx_stats.rx[i],
+ port_tx[i]);
+ }
+
+ printf("\nFLOW DISTRIBUTOR\n");
+ printf("-----\n");
+ printf("distributed: %9"PRIu64", drop: %9"PRIu64"\n",
+ flow_dist_stats.distributed, flow_dist_stats.drop);
+
+ printf("\nNODES\n");
+ printf("-------\n");
+ for (i = 0; i < num_nodes; i++) {
+ const unsigned long long rx = nodes[i].stats.rx;
+ const unsigned long long rx_drop = nodes[i].stats.rx_drop;
+ const struct filter_stats *filter = &info->filter_stats[i];
+
+ printf("Node %2u - rx: %9llu, rx_drop: %9llu\n"
+ " tx: %9"PRIu64", tx_drop: %9"PRIu64"\n"
+ " filter_passed: %9"PRIu64", "
+ "filter_drop: %9"PRIu64"\n",
+ i, rx, rx_drop, node_tx[i], node_tx_drop[i],
+ filter->passed, filter->drop);
+ }
+
+ printf("\n");
+ }
diff --git a/doc/guides/sample_app_ug/img/flow_distributor.svg b/doc/guides/sample_app_ug/img/flow_distributor.svg
new file mode 100644
index 0000000..9aee30b
--- /dev/null
+++ b/doc/guides/sample_app_ug/img/flow_distributor.svg
@@ -0,0 +1,1254 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by Microsoft Visio, SVG Export efd_i6.svg Page-1 -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="8.2496in" height="5.89673in"
+ viewBox="0 0 593.971 424.565" xml:space="preserve" color-interpolation-filters="sRGB" class="st27">
+ <v:documentProperties v:langID="1033" v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
+ <v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style type="text/css">
+ <![CDATA[
+ .st1 {visibility:visible}
+ .st2 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st3 {fill:#5b9bd5;stroke:#c7c8c8;stroke-width:0.25}
+ .st4 {fill:#feffff;font-family:Calibri;font-size:0.833336em}
+ .st5 {fill:#feffff;font-family:Calibri;font-size:0.75em}
+ .st6 {fill:none;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
+ .st7 {fill:none;stroke:#2e75b5;stroke-width:2.25}
+ .st8 {fill:#305497;stroke:#2e75b5;stroke-width:1}
+ .st9 {fill:#feffff;font-family:Calibri;font-size:0.833336em;font-weight:bold}
+ .st10 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2)}
+ .st11 {fill:#5b9bd5}
+ .st12 {stroke:#c7c8c8;stroke-width:0.25}
+ .st13 {fill:#acccea;stroke:#c7c8c8;stroke-width:0.25}
+ .st14 {fill:#feffff;font-family:Calibri;font-size:1.00001em;font-weight:bold}
+ .st15 {fill:#ed7d31;stroke:#c7c8c8;stroke-width:0.25}
+ .st16 {fill:#deebf6;stroke:#c7c8c8;stroke-width:0.25}
+ .st17 {marker-end:url(#mrkr5-212);stroke:#ff0000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
+ .st18 {fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-opacity:1;stroke-width:0.28409090909091}
+ .st19 {fill:none;stroke:#2e75b5;stroke-width:1}
+ .st20 {fill:#5b9bd5;font-family:Calibri;font-size:1.00001em}
+ .st21 {fill:none;stroke:none;stroke-width:0.25}
+ .st22 {font-size:1em}
+ .st23 {fill:#ffffff}
+ .st24 {stroke:#5b9bd5;stroke-dasharray:1.5,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st25 {marker-end:url(#mrkr5-444);stroke:#5b9bd5;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+ .st26 {fill:#5b9bd5;fill-opacity:1;stroke:#5b9bd5;stroke-opacity:1;stroke-width:0.37313432835821}
+ .st27 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs id="Markers">
+ <g id="lend5">
+ <path d="M 2 1 L 0 0 L 1.98117 -0.993387 C 1.67173 -0.364515 1.67301 0.372641 1.98465 1.00043 " style="stroke:none"/>
+ </g>
+ <marker id="mrkr5-212" class="st18" v:arrowType="5" v:arrowSize="2" v:setback="5.8" refX="-5.8" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-3.52,-3.52) "/>
+ </marker>
+ <marker id="mrkr5-444" class="st26" v:arrowType="5" v:arrowSize="2" v:setback="4.69" refX="-4.69" orient="auto"
+ markerUnits="strokeWidth" overflow="visible">
+ <use xlink:href="#lend5" transform="scale(-2.68,-2.68) "/>
+ </marker>
+ </defs>
+ <defs id="Filters">
+ <filter id="filter_2">
+ <feGaussianBlur stdDeviation="2"/>
+ </filter>
+ </defs>
+ <g v:mID="0" v:index="1" v:groupContext="foregroundPage">
+ <v:userDefs>
+ <v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
+ </v:userDefs>
+ <title>Page-1</title>
+ <v:pageProperties v:drawingScale="1" v:pageScale="1" v:drawingUnits="0" v:shadowOffsetX="9" v:shadowOffsetY="-9"/>
+ <g id="shape3-1" v:mID="3" v:groupContext="shape" transform="translate(319.501,-335.688)">
+ <title>Rectangle.58</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow3-2" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape4-7" v:mID="4" v:groupContext="shape" transform="translate(353.251,-335.688)">
+ <title>Rectangle.59</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow4-8" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape5-13" v:mID="5" v:groupContext="shape" transform="translate(400.501,-335.688)">
+ <title>Rectangle.60</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow5-14" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape6-19" v:mID="6" v:groupContext="shape" transform="translate(434.251,-335.688)">
+ <title>Rectangle.61</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow6-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape7-25" v:mID="7" v:groupContext="shape" transform="translate(481.501,-335.688)">
+ <title>Rectangle.62</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow7-26" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape8-30" v:mID="8" v:groupContext="shape" transform="translate(515.251,-335.688)">
+ <title>Rectangle.63</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow8-31" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape9-35" v:mID="9" v:groupContext="shape" transform="translate(319.501,-313.188)">
+ <title>Rectangle.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow9-36" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape10-40" v:mID="10" v:groupContext="shape" transform="translate(353.251,-313.188)">
+ <title>Rectangle.65</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow10-41" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape11-45" v:mID="11" v:groupContext="shape" transform="translate(400.501,-313.188)">
+ <title>Rectangle.66</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow11-46" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape12-50" v:mID="12" v:groupContext="shape" transform="translate(434.251,-313.188)">
+ <title>Rectangle.67</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow12-51" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape13-55" v:mID="13" v:groupContext="shape" transform="translate(481.501,-313.188)">
+ <title>Rectangle.68</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow13-56" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape14-60" v:mID="14" v:groupContext="shape" transform="translate(515.251,-313.188)">
+ <title>Rectangle.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow14-61" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape15-65" v:mID="15" v:groupContext="shape" transform="translate(319.501,-277.188)">
+ <title>Rectangle.70</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow15-66" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape16-71" v:mID="16" v:groupContext="shape" transform="translate(353.251,-277.188)">
+ <title>Rectangle.71</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow16-72" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape17-77" v:mID="17" v:groupContext="shape" transform="translate(400.501,-277.188)">
+ <title>Rectangle.72</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow17-78" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape18-83" v:mID="18" v:groupContext="shape" transform="translate(434.251,-277.188)">
+ <title>Rectangle.73</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow18-84" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape19-89" v:mID="19" v:groupContext="shape" transform="translate(481.501,-277.188)">
+ <title>Rectangle.74</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow19-90" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape20-95" v:mID="20" v:groupContext="shape" transform="translate(515.251,-277.188)">
+ <title>Rectangle.75</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow20-96" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape21-101" v:mID="21" v:groupContext="shape" transform="translate(319.501,-240.687)">
+ <title>Rectangle.76</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow21-102" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape22-106" v:mID="22" v:groupContext="shape" transform="translate(353.251,-240.687)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow22-107" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape23-111" v:mID="23" v:groupContext="shape" transform="translate(400.501,-240.687)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow23-112" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape24-116" v:mID="24" v:groupContext="shape" transform="translate(434.251,-240.687)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow24-117" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape25-121" v:mID="25" v:groupContext="shape" transform="translate(481.501,-240.687)">
+ <title>Rectangle.80</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow25-122" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape26-127" v:mID="26" v:groupContext="shape" transform="translate(515.251,-240.687)">
+ <title>Rectangle.81</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow26-128" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape27-133" v:mID="27" v:groupContext="shape" transform="translate(317.251,-231.687)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow27-134" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st6"/>
+ </g>
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st7"/>
+ </g>
+ <g id="shape28-138" v:mID="28" v:groupContext="shape" transform="translate(328.501,-362.688)">
+ <title>Sheet.28</title>
+ <desc>Local Table for N Specific Flows Serviced at Node 1</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="110.423" cy="418.94" width="220.85" height="11.25"/>
+ <rect x="0" y="413.315" width="220.846" height="11.25" class="st8"/>
+ <text x="5.77" y="421.94" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Local Table for N Specific Flows Serviced at Node 1</text> </g>
+ <g id="group34-141" transform="translate(66.0294,-165.569)" v:mID="34" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Device)"/>
+ <v:cp v:nameU="SubShapeType" v:lbl="SubShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Load balancer)"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Load balancer</title>
+ <g id="shape35-142" v:mID="35" v:groupContext="shape" transform="translate(0,-7.33146)">
+ <title>Sheet.35</title>
+ <g id="shadow35-143" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23 Z" class="st10"/>
+ <path d="M0 377.86 L72 377.86" class="st6"/>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23" class="st6"/>
+ </g>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23 Z" class="st11"/>
+ <path d="M0 377.86 L72 377.86" class="st12"/>
+ <path d="M54 367.23 L18 367.23 L0 377.86 L0 424.56 L72 424.56 L72 377.86 L54 367.23" class="st12"/>
+ </g>
+ <g id="shape36-152" v:mID="36" v:groupContext="shape" transform="translate(8.03054,-12.9324)">
+ <title>Sheet.36</title>
+ <g id="shadow36-153" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M45.34 421.81 L41.2 422.66 L44.12 424.56 L49.68 423.16 L48.75 417.51 L45.8 415.59 L46.69 419.68 L36.97
+ 413.34 L35.6 415.45 L45.34 421.81 ZM50.83 405.36 L39.2 405.36 L39.2 407.88 L50.8 407.88 L47.82 410.83
+ L51.34 410.83 L55.21 406.61 L51.32 402.39 L47.83 402.39 L50.83 405.36 ZM46.49 392.01 L36.75 398.37
+ L38.13 400.48 L47.84 394.14 L46.96 398.23 L49.91 396.31 L50.84 390.66 L45.28 389.26 L42.36 391.16
+ L46.49 392.01 ZM27.71 397.16 C22.66 397.16 18.58 401.25 18.58 406.29 C18.58 411.33 22.66 415.42
+ 27.71 415.42 C32.75 415.42 36.84 411.33 36.84 406.29 C36.84 401.25 32.75 397.16 27.71 397.16 ZM27.71
+ 400.04 C31.15 400.04 33.96 402.84 33.96 406.29 C33.96 409.74 31.15 412.54 27.71 412.54 C24.26 412.54
+ 21.46 409.74 21.46 406.29 C21.46 402.84 24.26 400.04 27.71 400.04 ZM11.64 405.04 L0 405.04 L0 407.56
+ L11.6 407.56 L8.62 410.51 L12.14 410.51 L16.01 406.29 L12.12 402.07 L8.64 402.07 L11.64 405.04 Z"
+ class="st2"/>
+ </g>
+ <path d="M45.34 421.81 L41.2 422.66 L44.12 424.56 L49.68 423.16 L48.75 417.51 L45.8 415.59 L46.69 419.68 L36.97 413.34
+ L35.6 415.45 L45.34 421.81 ZM50.83 405.36 L39.2 405.36 L39.2 407.88 L50.8 407.88 L47.82 410.83 L51.34
+ 410.83 L55.21 406.61 L51.32 402.39 L47.83 402.39 L50.83 405.36 ZM46.49 392.01 L36.75 398.37 L38.13 400.48
+ L47.84 394.14 L46.96 398.23 L49.91 396.31 L50.84 390.66 L45.28 389.26 L42.36 391.16 L46.49 392.01 ZM27.71
+ 397.16 C22.66 397.16 18.58 401.25 18.58 406.29 C18.58 411.33 22.66 415.42 27.71 415.42 C32.75 415.42
+ 36.84 411.33 36.84 406.29 C36.84 401.25 32.75 397.16 27.71 397.16 ZM27.71 400.04 C31.15 400.04 33.96
+ 402.84 33.96 406.29 C33.96 409.74 31.15 412.54 27.71 412.54 C24.26 412.54 21.46 409.74 21.46 406.29
+ C21.46 402.84 24.26 400.04 27.71 400.04 ZM11.64 405.04 L0 405.04 L0 407.56 L11.6 407.56 L8.62 410.51
+ L12.14 410.51 L16.01 406.29 L12.12 402.07 L8.64 402.07 L11.64 405.04 Z" class="st13"/>
+ </g>
+ </g>
+ <g id="shape37-157" v:mID="37" v:groupContext="shape" transform="translate(21.0294,-45.4375)">
+ <title>Rectangle</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow37-158" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="336.433" width="135" height="88.1315" class="st2"/>
+ </g>
+ <rect x="0" y="336.433" width="135" height="88.1315" class="st3"/>
+ </g>
+ <g id="shape38-162" v:mID="38" v:groupContext="shape" transform="translate(34.693,-126.438)">
+ <title>Sheet.38</title>
+ <desc>EFD Table</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="49.3364" cy="415.565" width="98.68" height="18"/>
+ <rect x="0" y="406.565" width="98.6728" height="18" class="st8"/>
+ <text x="24.87" y="419.17" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>EFD Table</text> </g>
+ <g id="shape39-165" v:mID="39" v:groupContext="shape" transform="translate(30.0294,-99.4375)">
+ <title>Rectangle.39</title>
+ <desc>Group_id</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="26.9182" cy="415.565" width="53.84" height="18"/>
+ <g id="shadow39-166" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st15"/>
+ <text x="7.87" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Group_id</text> </g>
+ <g id="shape40-171" v:mID="40" v:groupContext="shape" transform="translate(93.193,-99.4375)">
+ <title>Rectangle.40</title>
+ <desc>Hash index</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="26.9182" cy="415.565" width="53.84" height="18"/>
+ <g id="shadow40-172" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="53.8364" height="18" class="st15"/>
+ <text x="4.64" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Hash index</text> </g>
+ <g id="shape41-177" v:mID="41" v:groupContext="shape" transform="translate(30.193,-82.4275)">
+ <title>Rectangle.41</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow41-178" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape42-182" v:mID="42" v:groupContext="shape" transform="translate(30.193,-66.8125)">
+ <title>Rectangle.42</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow42-183" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape43-187" v:mID="43" v:groupContext="shape" transform="translate(30.1112,-52.1875)">
+ <title>Rectangle.43</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow43-188" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape44-192" v:mID="44" v:groupContext="shape" transform="translate(93.0294,-81.4375)">
+ <title>Rectangle.44</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow44-193" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape45-197" v:mID="45" v:groupContext="shape" transform="translate(93.193,-66.8125)">
+ <title>Rectangle.45</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow45-198" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape46-202" v:mID="46" v:groupContext="shape" transform="translate(93.193,-52.1875)">
+ <title>Rectangle.46</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow46-203" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st2"/>
+ </g>
+ <rect x="0" y="413.315" width="53.8364" height="11.25" class="st16"/>
+ </g>
+ <g id="shape47-207" v:mID="47" v:groupContext="shape" transform="translate(374.924,544.022) rotate(135)">
+ <title>Sheet.47</title>
+ <path d="M-0 417.75 A40.674 18.0151 -156.2 0 0 40.24 422.15 L40.49 421.89" class="st17"/>
+ </g>
+ <g id="shape48-213" v:mID="48" v:groupContext="shape" transform="translate(21.0294,-19)">
+ <title>Sheet.48</title>
+ <desc>Supports X*N Flows</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="415.565" width="135" height="18"/>
+ <rect x="0" y="406.565" width="135" height="18" class="st19"/>
+ <text x="19.05" y="419.17" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Supports X*N Flows</text> </g>
+ <g id="shape49-216" v:mID="49" v:groupContext="shape" transform="translate(48.0294,-229.938)">
+ <title>Sheet.49</title>
+ <desc>Frontend Server or Load Balancer</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="400.94" width="108" height="47.25"/>
+ <rect x="0" y="377.315" width="108" height="47.25" class="st21"/>
+ <text x="14.56" y="397.34" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Frontend Server<v:newlineChar/><tspan
+ x="13.16" dy="1.2em" class="st22">or Load Balancer </tspan> </text> </g>
+ <g id="group51-220" transform="translate(223.876,-310.938)" v:mID="51" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server</title>
+ <g id="shape52-221" v:mID="52" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.52</title>
+ <g id="shadow52-222" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape53-226" v:mID="53" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.53</title>
+ <g id="shadow53-227" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape54-231" v:mID="54" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.54</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow54-232" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape59-239" v:mID="59" v:groupContext="shape" transform="translate(277.876,-373.938)">
+ <title>Sheet.59</title>
+ <path d="M-0 424.56 A111.108 53.2538 42.31 0 1 93.83 421.21 L94.14 421.41" class="st17"/>
+ </g>
+ <g id="shape60-244" v:mID="60" v:groupContext="shape" transform="translate(205.876,-283.938)">
+ <title>Sheet.60</title>
+ <desc>Backend Server 1</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.93" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server 1</text> </g>
+ <g id="group61-247" transform="translate(223.876,-207.438)" v:mID="61" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server.61</title>
+ <g id="shape62-248" v:mID="62" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.62</title>
+ <g id="shadow62-249" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape63-253" v:mID="63" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.63</title>
+ <g id="shadow63-254" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape64-258" v:mID="64" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.64</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow64-259" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape65-266" v:mID="65" v:groupContext="shape" transform="translate(205.876,-180.437)">
+ <title>Sheet.65</title>
+ <desc>Backend Server 2</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.93" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server 2</text> </g>
+ <g id="group66-269" transform="translate(219.029,-58.9375)" v:mID="66" v:groupContext="group">
+ <v:custProps>
+ <v:cp v:nameU="AssetNumber" v:lbl="Asset Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SerialNumber" v:lbl="Serial Number" v:prompt="" v:type="0" v:format="" v:sortKey="Asset"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Location" v:lbl="Location" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Building" v:lbl="Building" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Room" v:lbl="Room" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Manufacturer" v:lbl="Manufacturer" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductNumber" v:lbl="Product Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="PartNumber" v:lbl="Part Number" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ProductDescription" v:lbl="Product Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Equipment" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkName" v:lbl="Network Name" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="IPAddress" v:lbl="IP Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="SubnetMask" v:lbl="Subnet Mask" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="AdminInterface" v:lbl="Administrative Interface" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NumberofPorts" v:lbl="Number of Ports" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CommunityString" v:lbl="Community String" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="NetworkDescription" v:lbl="Network Description" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Network" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="MACAddress" v:lbl="MAC Address" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Memory" v:lbl="Memory" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="OperatingSystem" v:lbl="Operating System" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
+ v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="HardDriveSize" v:lbl="Hard Drive Capacity" v:prompt="" v:type="0" v:format=""
+ v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="Department" v:lbl="Department" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ <v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Equipment)"/>
+ <v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0" v:val="VT4(Server)"/>
+ <v:cp v:nameU="BelongsTo" v:lbl="Belongs To" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
+ v:ask="false" v:langID="1033" v:cal="0"/>
+ </v:custProps>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
+ <v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
+ <v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
+ <v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
+ </v:userDefs>
+ <title>Server.66</title>
+ <g id="shape67-270" v:mID="67" v:groupContext="shape" transform="translate(13.0183,0)">
+ <title>Sheet.67</title>
+ <g id="shadow67-271" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st2"/>
+ </g>
+ <rect x="0" y="352.565" width="45.9634" height="72" class="st3"/>
+ </g>
+ <g id="shape68-275" v:mID="68" v:groupContext="shape" transform="translate(47.371,-30.7354)">
+ <title>Sheet.68</title>
+ <g id="shadow68-276" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st2"/>
+ </g>
+ <ellipse cx="2.77848" cy="421.786" rx="2.77848" ry="2.77848" class="st13"/>
+ </g>
+ <g id="shape69-280" v:mID="69" v:groupContext="shape" transform="translate(30.51,-11.8022)">
+ <title>Sheet.69</title>
+ <v:userDefs>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
+ <v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
+ </v:userDefs>
+ <g id="shadow69-281" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31
+ L-0 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z"
+ class="st10"/>
+ </g>
+ <path d="M-0 424.56 L22.42 424.56 L22.42 422.76 L-0 422.76 L-0 424.56 ZM-0 419.11 L22.42 419.11 L22.42 417.31 L-0
+ 417.31 L-0 419.11 ZM-0 413.65 L22.42 413.65 L22.42 411.84 L-0 411.84 L-0 413.65 Z" class="st23"/>
+ </g>
+ </g>
+ <g id="shape70-288" v:mID="70" v:groupContext="shape" transform="translate(201.029,-26.056)">
+ <title>Sheet.70</title>
+ <desc>Backend Server X</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="54" cy="408.124" width="108" height="32.8815"/>
+ <rect x="0" y="391.683" width="108" height="32.8815" class="st21"/>
+ <text x="11.86" y="411.72" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Backend Server X</text> </g>
+ <g id="shape71-291" v:mID="71" v:groupContext="shape" transform="translate(684.44,239.627) rotate(90)">
+ <title>Sheet.71</title>
+ <path d="M0 424.56 L45 424.56" class="st24"/>
+ </g>
+ <g id="shape72-294" v:mID="72" v:groupContext="shape" transform="translate(6.85967,-22.443) rotate(-38.1076)">
+ <title>Sheet.72</title>
+ <path d="M-0 424.56 A96.1331 44.4001 55.03 0 1 68.24 420.56 L68.51 420.79" class="st17"/>
+ </g>
+ <g id="shape73-299" v:mID="73" v:groupContext="shape" transform="translate(328.501,-135.937)">
+ <title>Rectangle.73</title>
+ <desc>Key 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow73-300" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 1</text> </g>
+ <g id="shape74-305" v:mID="74" v:groupContext="shape" transform="translate(362.251,-135.937)">
+ <title>Rectangle.74</title>
+ <desc>Action 1</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow74-306" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 1</text> </g>
+ <g id="shape75-311" v:mID="75" v:groupContext="shape" transform="translate(409.501,-135.937)">
+ <title>Rectangle.75</title>
+ <desc>Key 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow75-312" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="4.74" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key 2</text> </g>
+ <g id="shape76-317" v:mID="76" v:groupContext="shape" transform="translate(443.251,-135.937)">
+ <title>Rectangle.76</title>
+ <desc>Action 2</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow76-318" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.62" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action 2</text> </g>
+ <g id="shape77-323" v:mID="77" v:groupContext="shape" transform="translate(490.501,-135.937)">
+ <title>Rectangle.77</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow77-324" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape78-328" v:mID="78" v:groupContext="shape" transform="translate(524.251,-135.937)">
+ <title>Rectangle.78</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow78-329" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape79-333" v:mID="79" v:groupContext="shape" transform="translate(328.501,-113.437)">
+ <title>Rectangle.79</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow79-334" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape80-338" v:mID="80" v:groupContext="shape" transform="translate(362.251,-113.437)">
+ <title>Rectangle.80</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow80-339" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape81-343" v:mID="81" v:groupContext="shape" transform="translate(409.501,-113.437)">
+ <title>Rectangle.81</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow81-344" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape82-348" v:mID="82" v:groupContext="shape" transform="translate(443.251,-113.437)">
+ <title>Rectangle.82</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow82-349" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape83-353" v:mID="83" v:groupContext="shape" transform="translate(490.501,-113.437)">
+ <title>Rectangle.83</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow83-354" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape84-358" v:mID="84" v:groupContext="shape" transform="translate(524.251,-113.437)">
+ <title>Rectangle.84</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow84-359" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape85-363" v:mID="85" v:groupContext="shape" transform="translate(328.501,-77.4375)">
+ <title>Rectangle.85</title>
+ <desc>Key x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow85-364" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.11" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key x</text> </g>
+ <g id="shape86-369" v:mID="86" v:groupContext="shape" transform="translate(362.251,-77.4375)">
+ <title>Rectangle.86</title>
+ <desc>Action x</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow86-370" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.99" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action x</text> </g>
+ <g id="shape87-375" v:mID="87" v:groupContext="shape" transform="translate(409.501,-77.4375)">
+ <title>Rectangle.87</title>
+ <desc>Key y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow87-376" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.01" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key y</text> </g>
+ <g id="shape88-381" v:mID="88" v:groupContext="shape" transform="translate(443.251,-77.4375)">
+ <title>Rectangle.88</title>
+ <desc>Action y</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow88-382" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="4.89" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action y</text> </g>
+ <g id="shape89-387" v:mID="89" v:groupContext="shape" transform="translate(490.501,-77.4375)">
+ <title>Rectangle.89</title>
+ <desc>Key z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow89-388" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.3" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key z</text> </g>
+ <g id="shape90-393" v:mID="90" v:groupContext="shape" transform="translate(524.251,-77.4375)">
+ <title>Rectangle.90</title>
+ <desc>Action z</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow90-394" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.18" y="418.56" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action z</text> </g>
+ <g id="shape91-399" v:mID="91" v:groupContext="shape" transform="translate(328.501,-40.9375)">
+ <title>Rectangle.91</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow91-400" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape92-404" v:mID="92" v:groupContext="shape" transform="translate(362.251,-40.9375)">
+ <title>Rectangle.92</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow92-405" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape93-409" v:mID="93" v:groupContext="shape" transform="translate(409.501,-40.9375)">
+ <title>Rectangle.93</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow93-410" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ </g>
+ <g id="shape94-414" v:mID="94" v:groupContext="shape" transform="translate(443.251,-40.9375)">
+ <title>Rectangle.94</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow94-415" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ </g>
+ <g id="shape95-419" v:mID="95" v:groupContext="shape" transform="translate(490.501,-40.9375)">
+ <title>Rectangle.95</title>
+ <desc>Key N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="15.75" cy="415.565" width="31.5" height="18"/>
+ <g id="shadow95-420" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="31.5" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="31.5" height="18" class="st3"/>
+ <text x="5.21" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Key N</text> </g>
+ <g id="shape96-425" v:mID="96" v:groupContext="shape" transform="translate(524.251,-40.9375)">
+ <title>Rectangle.96</title>
+ <desc>Action N</desc>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="21.375" cy="415.565" width="42.75" height="18"/>
+ <g id="shadow96-426" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="406.565" width="42.75" height="18" class="st2"/>
+ </g>
+ <rect x="0" y="406.565" width="42.75" height="18" class="st3"/>
+ <text x="5.67" y="418.26" class="st5" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Action N</text> </g>
+ <g id="shape97-431" v:mID="97" v:groupContext="shape" transform="translate(326.251,-31.9375)">
+ <title>Rectangle.97</title>
+ <v:userDefs>
+ <v:ud v:nameU="visVersion" v:val="VT0(15):26"/>
+ </v:userDefs>
+ <g id="shadow97-432" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
+ transform="matrix(1,0,0,1,0.345598,1.97279)" class="st1">
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st6"/>
+ </g>
+ <rect x="0" y="289.065" width="245.25" height="135.5" class="st7"/>
+ </g>
+ <g id="shape98-436" v:mID="98" v:groupContext="shape" transform="translate(337.501,-162.938)">
+ <title>Sheet.98</title>
+ <desc>Local Table for N Specific Flows Serviced at Node X</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="110.423" cy="418.94" width="220.85" height="11.25"/>
+ <rect x="0" y="413.315" width="220.846" height="11.25" class="st8"/>
+ <text x="5.55" y="421.94" class="st9" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Local Table for N Specific Flows Serviced at Node X</text> </g>
+ <g id="shape99-439" v:mID="99" v:groupContext="shape" transform="translate(-204.342,-29.4449) rotate(-53.7462)">
+ <title>Sheet.99</title>
+ <path d="M0 424.56 L160.37 424.56" class="st25"/>
+ </g>
+ <g id="shape100-445" v:mID="100" v:groupContext="shape" transform="translate(-37.6568,-164.882) rotate(-24.444)">
+ <title>Sheet.100</title>
+ <path d="M0 424.56 L101.71 424.56" class="st25"/>
+ </g>
+ <g id="shape101-450" v:mID="101" v:groupContext="shape" transform="translate(464.049,-50.8578) rotate(50.099)">
+ <title>Sheet.101</title>
+ <path d="M0 424.56 L139.8 424.56" class="st25"/>
+ </g>
+ <g id="shape102-455" v:mID="102" v:groupContext="shape" transform="translate(372.376,-207.438)">
+ <title>Sheet.102</title>
+ <desc>Supports N Flows</desc>
+ <v:textBlock v:margins="rect(4,4,4,4)"/>
+ <v:textRect cx="67.5" cy="415.565" width="135" height="18"/>
+ <rect x="0" y="406.565" width="135" height="18" class="st19"/>
+ <text x="25.15" y="419.17" class="st20" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Supports N Flows</text> </g>
+ </g>
+</svg>
diff --git a/doc/guides/sample_app_ug/index.rst b/doc/guides/sample_app_ug/index.rst
index 775e2f7..260f6a5 100644
--- a/doc/guides/sample_app_ug/index.rst
+++ b/doc/guides/sample_app_ug/index.rst
@@ -57,6 +57,7 @@ Sample Applications User Guides
l3_forward_virtual
link_status_intr
load_balancer
+ flow_distributor
multi_process
qos_metering
qos_scheduler
--
2.7.4
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [dpdk-dev] [PATCH v8 1/6] efd: new Elastic Flow Distributor library
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 1/6] efd: new Elastic Flow Distributor library Pablo de Lara
@ 2017-01-18 18:56 ` Thomas Monjalon
2017-01-18 19:27 ` De Lara Guarch, Pablo
0 siblings, 1 reply; 63+ messages in thread
From: Thomas Monjalon @ 2017-01-18 18:56 UTC (permalink / raw)
To: Pablo de Lara; +Cc: dev, Byron Marohn, Saikrishna Edupuganti
2017-01-17 22:23, Pablo de Lara:
> +# this lib depends upon:
> +DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_eal
> +DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_ring
It depends also on rte_hash.
Pablo, I think you know this library ;)
I will fix it for you.
> +#include <rte_jhash.h>
> +#include <rte_hash_crc.h>
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [dpdk-dev] [PATCH v8 1/6] efd: new Elastic Flow Distributor library
2017-01-18 18:56 ` Thomas Monjalon
@ 2017-01-18 19:27 ` De Lara Guarch, Pablo
2017-01-18 19:44 ` Thomas Monjalon
0 siblings, 1 reply; 63+ messages in thread
From: De Lara Guarch, Pablo @ 2017-01-18 19:27 UTC (permalink / raw)
To: Thomas Monjalon; +Cc: dev, Marohn, Byron, Edupuganti, Saikrishna
> -----Original Message-----
> From: Thomas Monjalon [mailto:thomas.monjalon@6wind.com]
> Sent: Wednesday, January 18, 2017 6:56 PM
> To: De Lara Guarch, Pablo
> Cc: dev@dpdk.org; Marohn, Byron; Edupuganti, Saikrishna
> Subject: Re: [dpdk-dev] [PATCH v8 1/6] efd: new Elastic Flow Distributor
> library
>
> 2017-01-17 22:23, Pablo de Lara:
> > +# this lib depends upon:
> > +DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_eal
> > +DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_ring
>
> It depends also on rte_hash.
> Pablo, I think you know this library ;)
>
> I will fix it for you.
Doh! Has any compilation failed? I didn't see any errors.
>
> > +#include <rte_jhash.h>
> > +#include <rte_hash_crc.h>
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [dpdk-dev] [PATCH v8 1/6] efd: new Elastic Flow Distributor library
2017-01-18 19:27 ` De Lara Guarch, Pablo
@ 2017-01-18 19:44 ` Thomas Monjalon
0 siblings, 0 replies; 63+ messages in thread
From: Thomas Monjalon @ 2017-01-18 19:44 UTC (permalink / raw)
To: De Lara Guarch, Pablo; +Cc: dev, Marohn, Byron, Edupuganti, Saikrishna
2017-01-18 19:27, De Lara Guarch, Pablo:
>
> > -----Original Message-----
> > From: Thomas Monjalon [mailto:thomas.monjalon@6wind.com]
> > Sent: Wednesday, January 18, 2017 6:56 PM
> > To: De Lara Guarch, Pablo
> > Cc: dev@dpdk.org; Marohn, Byron; Edupuganti, Saikrishna
> > Subject: Re: [dpdk-dev] [PATCH v8 1/6] efd: new Elastic Flow Distributor
> > library
> >
> > 2017-01-17 22:23, Pablo de Lara:
> > > +# this lib depends upon:
> > > +DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_eal
> > > +DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_ring
> >
> > It depends also on rte_hash.
> > Pablo, I think you know this library ;)
> >
> > I will fix it for you.
>
> Doh! Has any compilation failed? I didn't see any errors.
Yes, from a fresh build, it does not find below includes.
> > > +#include <rte_jhash.h>
> > > +#include <rte_hash_crc.h>
>
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [dpdk-dev] [PATCH v8 0/6] Elastic Flow Distributor
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 " Pablo de Lara
` (5 preceding siblings ...)
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 6/6] doc: add flow distributor guide Pablo de Lara
@ 2017-01-18 19:57 ` Thomas Monjalon
6 siblings, 0 replies; 63+ messages in thread
From: Thomas Monjalon @ 2017-01-18 19:57 UTC (permalink / raw)
To: Pablo de Lara; +Cc: dev
2017-01-17 22:23, Pablo de Lara:
> EFD is a distributor library that uses perfect hashing to determine a
> target/value for a given incoming flow key. It has the following advantages:
> first, because it uses perfect hashing it does not store the key itself and
> hence lookup performance is not dependent on the key size. Second, the
> target/value can be any arbitrary value hence the system designer and/or
> operator can better optimize service rates and inter-cluster network traffic
> locating. Third, since the storage requirement is much smaller than a
> hash-based flow table (i.e. better fit for CPU cache), EFD can scale to millions
> of flow keys. Finally, with current optimized library implementation performance
> is fully scalable with number of CPU cores.
>
> The basic idea of EFD is when a given key is to be inserted, a family of hash
> functions is searched until the correct hash function that maps the input key to
> the correct value is found. However, rather than explicitly storing all keys and
> their associated values, EFD stores only indices of hash functions that map keys
> to values, and thereby consumes much less space than conventional flow-based
> tables. The lookup operation is very simple, similar to computational-based
> scheme, given an input key the lookup operation is reduced to hashing that key
> with the correct hash function.
>
> Intuitively, finding a hash function that maps each of a large number (millions)
> of input keys to the correct output value is effectively impossible, as a result
> EFD, breaks the problem into smaller pieces (divide and conquer). EFD divides
> the entire input key set into many small groups. Each group consists of
> approximately 20-28 keys (a configurable parameter for the library), then, for
> each small group, a brute force search to find a hash function that produces the
> correct outputs for each key in the group.
> It should be mentioned that since in the online lookup table for EFD doesn’t
> store the key itself, the size of the EFD table is independent of the key size
> and hence EFD lookup performance which is almost constant irrespective of the
> length of the key which is a highly desirable feature especially for longer
> keys.
>
> Library code is included in the patch, plus an sample application that shows
> how the library can be used.
Applied, thanks
^ permalink raw reply [flat|nested] 63+ messages in thread
end of thread, other threads:[~2017-01-18 19:57 UTC | newest]
Thread overview: 63+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-12-02 14:52 [dpdk-dev] [PATCH 0/2] Elastic Flow Distributor Pablo de Lara
2016-12-02 14:52 ` [dpdk-dev] [PATCH 1/2] efd: new Elastic Flow Distributor library Pablo de Lara
2016-12-02 14:52 ` [dpdk-dev] [PATCH 2/2] examples/flow_distributor: sample app to demonstrate EFD usage Pablo de Lara
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 0/5] Elastic Flow Distributor Pablo de Lara
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 1/5] efd: new Elastic Flow Distributor library Pablo de Lara
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 2/5] app/test: add EFD functional and perf tests Pablo de Lara
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 3/5] examples/flow_distributor: sample app to demonstrate EFD usage Pablo de Lara
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 4/5] doc: add EFD library section in Programmers guide Pablo de Lara
2017-01-07 1:06 ` [dpdk-dev] [PATCH v2 5/5] doc: add flow distributor guide Pablo de Lara
2017-01-09 18:19 ` [dpdk-dev] [PATCH v2 0/5] Elastic Flow Distributor Maciocco, Christian
2017-01-12 22:15 ` [dpdk-dev] [PATCH v3 " Pablo de Lara
2017-01-12 22:15 ` [dpdk-dev] [PATCH v3 1/5] efd: new Elastic Flow Distributor library Pablo de Lara
2017-01-12 22:15 ` [dpdk-dev] [PATCH v3 2/5] app/test: add EFD functional and perf tests Pablo de Lara
2017-01-12 22:15 ` [dpdk-dev] [PATCH v3 3/5] examples/flow_distributor: sample app to demonstrate EFD usage Pablo de Lara
2017-01-12 22:15 ` [dpdk-dev] [PATCH v3 4/5] doc: add EFD library section in Programmers guide Pablo de Lara
2017-01-12 22:16 ` [dpdk-dev] [PATCH v3 5/5] doc: add flow distributor guide Pablo de Lara
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 0/5] Elastic Flow Distributor Pablo de Lara
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 1/5] efd: new Elastic Flow Distributor library Pablo de Lara
2017-01-16 4:25 ` Jerin Jacob
2017-01-16 15:34 ` De Lara Guarch, Pablo
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 2/5] app/test: add EFD functional and perf tests Pablo de Lara
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 3/5] examples/flow_distributor: sample app to demonstrate EFD usage Pablo de Lara
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 4/5] doc: add EFD library section in Programmers guide Pablo de Lara
2017-01-16 4:15 ` Jerin Jacob
2017-01-16 15:33 ` De Lara Guarch, Pablo
2017-01-15 12:04 ` [dpdk-dev] [PATCH v4 5/5] doc: add flow distributor guide Pablo de Lara
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 0/5] Elastic Flow Distributor Pablo de Lara
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 1/5] efd: new Elastic Flow Distributor library Pablo de Lara
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 2/5] app/test: add EFD functional and perf tests Pablo de Lara
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 3/5] examples/flow_distributor: sample app to demonstrate EFD usage Pablo de Lara
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 4/5] doc: add EFD library section in Programmers guide Pablo de Lara
2017-01-16 9:43 ` [dpdk-dev] [PATCH v5 5/5] doc: add flow distributor guide Pablo de Lara
2017-01-16 15:08 ` [dpdk-dev] [PATCH v5 0/5] Elastic Flow Distributor Thomas Monjalon
2017-01-17 8:34 ` De Lara Guarch, Pablo
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 " Pablo de Lara
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 1/5] efd: new Elastic Flow Distributor library Pablo de Lara
2017-01-17 20:32 ` Thomas Monjalon
2017-01-17 21:11 ` Thomas Monjalon
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 2/5] app/test: add EFD functional and perf tests Pablo de Lara
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 3/5] examples/flow_distributor: sample app to demonstrate EFD usage Pablo de Lara
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 4/5] doc: add EFD library section in Programmers guide Pablo de Lara
2017-01-16 19:21 ` [dpdk-dev] [PATCH v6 5/5] doc: add flow distributor guide Pablo de Lara
2017-01-17 20:35 ` Thomas Monjalon
2017-01-17 20:29 ` [dpdk-dev] [PATCH v6 0/5] Elastic Flow Distributor Thomas Monjalon
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 0/6] " Pablo de Lara
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 1/6] efd: new Elastic Flow Distributor library Pablo de Lara
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 2/6] efd: add AVX2 vect lookup function Pablo de Lara
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 3/6] app/test: add EFD functional and perf tests Pablo de Lara
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 4/6] examples/flow_distributor: sample app to demonstrate EFD usage Pablo de Lara
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 5/6] doc: add EFD library section in Programmers guide Pablo de Lara
2017-01-17 22:10 ` [dpdk-dev] [PATCH v7 6/6] doc: add flow distributor guide Pablo de Lara
2017-01-17 22:18 ` [dpdk-dev] [PATCH v7 0/6] Elastic Flow Distributor De Lara Guarch, Pablo
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 " Pablo de Lara
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 1/6] efd: new Elastic Flow Distributor library Pablo de Lara
2017-01-18 18:56 ` Thomas Monjalon
2017-01-18 19:27 ` De Lara Guarch, Pablo
2017-01-18 19:44 ` Thomas Monjalon
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 2/6] efd: add AVX2 vect lookup function Pablo de Lara
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 3/6] app/test: add EFD functional and perf tests Pablo de Lara
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 4/6] examples/flow_distributor: sample app to demonstrate EFD usage Pablo de Lara
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 5/6] doc: add EFD library section in Programmers guide Pablo de Lara
2017-01-17 22:23 ` [dpdk-dev] [PATCH v8 6/6] doc: add flow distributor guide Pablo de Lara
2017-01-18 19:57 ` [dpdk-dev] [PATCH v8 0/6] Elastic Flow Distributor Thomas Monjalon
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).