summaryrefslogtreecommitdiffstats
path: root/pathd/path_pcep_config.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 13:16:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 13:16:35 +0000
commite2bbf175a2184bd76f6c54ccf8456babeb1a46fc (patch)
treef0b76550d6e6f500ada964a3a4ee933a45e5a6f1 /pathd/path_pcep_config.c
parentInitial commit. (diff)
downloadfrr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.tar.xz
frr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.zip
Adding upstream version 9.1.upstream/9.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'pathd/path_pcep_config.c')
-rw-r--r--pathd/path_pcep_config.c539
1 files changed, 539 insertions, 0 deletions
diff --git a/pathd/path_pcep_config.c b/pathd/path_pcep_config.c
new file mode 100644
index 0000000..da7ee89
--- /dev/null
+++ b/pathd/path_pcep_config.c
@@ -0,0 +1,539 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2020 NetDEF, Inc.
+ */
+
+#include <zebra.h>
+
+#include <northbound.h>
+#include <yang.h>
+#include <printfrr.h>
+#include "pceplib/pcep_msg_objects.h"
+#include "pathd/pathd.h"
+#include "pathd/path_pcep.h"
+#include "pathd/path_pcep_config.h"
+#include "pathd/path_pcep_debug.h"
+#include "frrevent.h"
+
+#define MAX_XPATH 256
+#define MAX_FLOAT_LEN 22
+#define INETADDR4_MAXLEN 16
+#define INETADDR6_MAXLEN 40
+#define INITIATED_CANDIDATE_PREFERENCE 255
+#define INITIATED_POLICY_COLOR 1
+
+
+static void copy_candidate_objfun_info(struct srte_candidate *candidate,
+ struct path *path);
+static void copy_candidate_affinity_filters(struct srte_candidate *candidate,
+ struct path *path);
+static struct path_hop *
+path_pcep_config_list_path_hops(struct srte_segment_list *segment_list);
+static struct srte_candidate *lookup_candidate(struct lsp_nb_key *key);
+static char *candidate_name(struct srte_candidate *candidate);
+static enum pcep_lsp_operational_status
+status_int_to_ext(enum srte_policy_status status);
+static enum pcep_sr_subobj_nai pcep_nai_type(enum srte_segment_nai_type type);
+static enum srte_segment_nai_type srte_nai_type(enum pcep_sr_subobj_nai type);
+
+void path_pcep_refine_path(struct path *path)
+{
+ struct srte_candidate *candidate = lookup_candidate(&path->nbkey);
+ struct srte_lsp *lsp;
+
+ if (candidate == NULL)
+ return;
+
+ lsp = candidate->lsp;
+
+ if (path->name == NULL)
+ path->name = candidate_name(candidate);
+ if (path->type == SRTE_CANDIDATE_TYPE_UNDEFINED)
+ path->type = candidate->type;
+ if (path->create_origin == SRTE_ORIGIN_UNDEFINED)
+ path->create_origin = candidate->protocol_origin;
+ if ((path->update_origin == SRTE_ORIGIN_UNDEFINED)
+ && (lsp->segment_list != NULL))
+ path->update_origin = lsp->segment_list->protocol_origin;
+}
+
+struct path *path_pcep_config_get_path(struct lsp_nb_key *key)
+{
+ struct srte_candidate *candidate = lookup_candidate(key);
+ if (candidate == NULL)
+ return NULL;
+ return candidate_to_path(candidate);
+}
+
+void path_pcep_config_list_path(path_list_cb_t cb, void *arg)
+{
+ struct path *path;
+ struct srte_policy *policy;
+ struct srte_candidate *candidate;
+
+ RB_FOREACH (policy, srte_policy_head, &srte_policies) {
+ RB_FOREACH (candidate, srte_candidate_head,
+ &policy->candidate_paths) {
+ path = candidate_to_path(candidate);
+ if (!cb(path, arg))
+ return;
+ }
+ }
+}
+
+struct path *candidate_to_path(struct srte_candidate *candidate)
+{
+ char *name;
+ struct path *path;
+ struct path_hop *hop = NULL;
+ struct path_metric *metric = NULL;
+ struct srte_policy *policy;
+ struct srte_lsp *lsp;
+ enum pcep_lsp_operational_status status;
+ enum srte_protocol_origin update_origin = 0;
+ char *originator = NULL;
+
+ policy = candidate->policy;
+ lsp = candidate->lsp;
+
+ if (lsp->segment_list != NULL) {
+ hop = path_pcep_config_list_path_hops(lsp->segment_list);
+ update_origin = lsp->segment_list->protocol_origin;
+ originator = XSTRDUP(MTYPE_PCEP, lsp->segment_list->originator);
+ }
+ path = pcep_new_path();
+ name = candidate_name(candidate);
+ if (CHECK_FLAG(candidate->flags, F_CANDIDATE_BEST)) {
+ status = status_int_to_ext(policy->status);
+ } else {
+ status = PCEP_LSP_OPERATIONAL_DOWN;
+ }
+ for (uint32_t i = 0; i < MAX_METRIC_TYPE; i++) {
+ struct path_metric *path_metric;
+ struct srte_metric *srte_metric = &lsp->metrics[i];
+ if (CHECK_FLAG(srte_metric->flags, F_METRIC_IS_DEFINED)) {
+ path_metric = pcep_new_metric();
+ path_metric->next = metric;
+ metric = path_metric;
+ metric->type = i + 1;
+ metric->value = srte_metric->value;
+ metric->enforce = CHECK_FLAG(srte_metric->flags,
+ F_METRIC_IS_REQUIRED);
+ metric->is_bound = CHECK_FLAG(srte_metric->flags,
+ F_METRIC_IS_BOUND);
+ metric->is_computed = CHECK_FLAG(srte_metric->flags,
+ F_METRIC_IS_COMPUTED);
+ }
+ }
+ *path = (struct path){
+ .nbkey = (struct lsp_nb_key){.color = policy->color,
+ .endpoint = policy->endpoint,
+ .preference =
+ candidate->preference},
+ .create_origin = lsp->protocol_origin,
+ .update_origin = update_origin,
+ .originator = originator,
+ .plsp_id = 0,
+ .name = name,
+ .type = candidate->type,
+ .srp_id = policy->srp_id,
+ .req_id = 0,
+ .binding_sid = policy->binding_sid,
+ .status = status,
+ .do_remove = false,
+ .go_active = false,
+ .was_created = false,
+ .was_removed = false,
+ .is_synching = false,
+ .is_delegated = false,
+ .first_hop = hop,
+ .first_metric = metric};
+
+ path->has_bandwidth = CHECK_FLAG(lsp->flags, F_CANDIDATE_HAS_BANDWIDTH);
+ if (path->has_bandwidth) {
+ path->enforce_bandwidth =
+ CHECK_FLAG(lsp->flags, F_CANDIDATE_REQUIRED_BANDWIDTH);
+ path->bandwidth = lsp->bandwidth;
+ } else {
+ path->enforce_bandwidth = true;
+ path->bandwidth = 0;
+ }
+
+ copy_candidate_objfun_info(candidate, path);
+ copy_candidate_affinity_filters(candidate, path);
+
+ return path;
+}
+
+void copy_candidate_objfun_info(struct srte_candidate *candidate,
+ struct path *path)
+{
+ struct srte_lsp *lsp = candidate->lsp;
+
+ if (lsp != NULL) {
+ if (CHECK_FLAG(lsp->flags, F_CANDIDATE_HAS_OBJFUN)) {
+ path->has_pce_objfun = true;
+ path->pce_objfun = lsp->objfun;
+ } else {
+ path->has_pce_objfun = false;
+ path->pce_objfun = OBJFUN_UNDEFINED;
+ }
+ }
+ if (CHECK_FLAG(candidate->flags, F_CANDIDATE_HAS_OBJFUN)) {
+ path->has_pcc_objfun = true;
+ path->pcc_objfun = candidate->objfun;
+ path->enforce_pcc_objfun = CHECK_FLAG(
+ candidate->flags, F_CANDIDATE_REQUIRED_OBJFUN);
+
+ } else {
+ path->has_pcc_objfun = false;
+ path->pcc_objfun = OBJFUN_UNDEFINED;
+ UNSET_FLAG(candidate->flags, F_CANDIDATE_REQUIRED_OBJFUN);
+ }
+}
+
+void copy_candidate_affinity_filters(struct srte_candidate *candidate,
+ struct path *path)
+{
+ bool eany = CHECK_FLAG(candidate->flags, F_CANDIDATE_HAS_EXCLUDE_ANY);
+ bool iany = CHECK_FLAG(candidate->flags, F_CANDIDATE_HAS_INCLUDE_ANY);
+ bool iall = CHECK_FLAG(candidate->flags, F_CANDIDATE_HAS_INCLUDE_ALL);
+ path->has_affinity_filters = eany || iany || iall;
+ path->affinity_filters[AFFINITY_FILTER_EXCLUDE_ANY - 1] =
+ eany ? candidate->affinity_filters[AFFINITY_FILTER_EXCLUDE_ANY
+ - 1]
+ : 0;
+ path->affinity_filters[AFFINITY_FILTER_INCLUDE_ANY - 1] =
+ iany ? candidate->affinity_filters[AFFINITY_FILTER_INCLUDE_ANY
+ - 1]
+ : 0;
+ path->affinity_filters[AFFINITY_FILTER_INCLUDE_ALL - 1] =
+ iall ? candidate->affinity_filters[AFFINITY_FILTER_INCLUDE_ALL
+ - 1]
+ : 0;
+}
+
+struct path_hop *
+path_pcep_config_list_path_hops(struct srte_segment_list *segment_list)
+{
+ struct srte_segment_entry *segment;
+ struct path_hop *hop = NULL, *last_hop = NULL;
+
+ RB_FOREACH_REVERSE (segment, srte_segment_entry_head,
+ &segment_list->segments) {
+ hop = pcep_new_hop();
+ *hop = (struct path_hop){
+ .next = last_hop,
+ .is_loose = false,
+ .has_sid = true,
+ .is_mpls = true,
+ .has_attribs = false,
+ .sid = {.mpls = {.label = segment->sid_value}},
+ .has_nai =
+ segment->nai_type != SRTE_SEGMENT_NAI_TYPE_NONE,
+ .nai = {.type = pcep_nai_type(segment->nai_type)}};
+ switch (segment->nai_type) {
+ case SRTE_SEGMENT_NAI_TYPE_IPV4_NODE:
+ case SRTE_SEGMENT_NAI_TYPE_IPV6_NODE:
+ case SRTE_SEGMENT_NAI_TYPE_IPV4_LOCAL_IFACE:
+ case SRTE_SEGMENT_NAI_TYPE_IPV6_LOCAL_IFACE:
+ case SRTE_SEGMENT_NAI_TYPE_IPV4_ALGORITHM:
+ case SRTE_SEGMENT_NAI_TYPE_IPV6_ALGORITHM:
+ memcpy(&hop->nai.local_addr, &segment->nai_local_addr,
+ sizeof(struct ipaddr));
+ break;
+ case SRTE_SEGMENT_NAI_TYPE_IPV4_ADJACENCY:
+ case SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY:
+ memcpy(&hop->nai.local_addr, &segment->nai_local_addr,
+ sizeof(struct ipaddr));
+ memcpy(&hop->nai.remote_addr, &segment->nai_remote_addr,
+ sizeof(struct ipaddr));
+ break;
+ case SRTE_SEGMENT_NAI_TYPE_IPV4_UNNUMBERED_ADJACENCY:
+ memcpy(&hop->nai.local_addr, &segment->nai_local_addr,
+ sizeof(struct ipaddr));
+ hop->nai.local_iface = segment->nai_local_iface;
+ memcpy(&hop->nai.remote_addr, &segment->nai_remote_addr,
+ sizeof(struct ipaddr));
+ hop->nai.remote_iface = segment->nai_remote_iface;
+ break;
+ case SRTE_SEGMENT_NAI_TYPE_NONE:
+ case SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY_LINK_LOCAL_ADDRESSES:
+ break;
+ }
+ last_hop = hop;
+ }
+ return hop;
+}
+
+int path_pcep_config_initiate_path(struct path *path)
+{
+ struct srte_policy *policy;
+ struct srte_candidate *candidate;
+
+ if (path->do_remove) {
+ zlog_warn("PCE %s tried to REMOVE pce-initiate a path ",
+ path->originator);
+ candidate = lookup_candidate(&path->nbkey);
+ if (candidate) {
+ if (!path->is_delegated) {
+ zlog_warn(
+ "(%s)PCE tried to REMOVE but it's not Delegated!",
+ __func__);
+ return ERROR_19_1;
+ }
+ if (candidate->type != SRTE_CANDIDATE_TYPE_DYNAMIC) {
+ zlog_warn(
+ "(%s)PCE tried to REMOVE but it's not PCE origin!",
+ __func__);
+ return ERROR_19_9;
+ }
+ zlog_warn(
+ "(%s)PCE tried to REMOVE found candidate!, let's remove",
+ __func__);
+ candidate->policy->srp_id = path->srp_id;
+ SET_FLAG(candidate->policy->flags, F_POLICY_DELETED);
+ SET_FLAG(candidate->flags, F_CANDIDATE_DELETED);
+ } else {
+ zlog_warn("(%s)PCE tried to REMOVE not existing LSP!",
+ __func__);
+ return ERROR_19_3;
+ }
+ srte_apply_changes();
+ } else {
+ assert(!IS_IPADDR_NONE(&path->nbkey.endpoint));
+
+ if (path->nbkey.preference == 0)
+ path->nbkey.preference = INITIATED_CANDIDATE_PREFERENCE;
+
+ if (path->nbkey.color == 0)
+ path->nbkey.color = INITIATED_POLICY_COLOR;
+
+ candidate = lookup_candidate(&path->nbkey);
+ if (!candidate) {
+ policy = srte_policy_add(
+ path->nbkey.color, &path->nbkey.endpoint,
+ SRTE_ORIGIN_PCEP, path->originator);
+ strlcpy(policy->name, path->name, sizeof(policy->name));
+ policy->binding_sid = path->binding_sid;
+ SET_FLAG(policy->flags, F_POLICY_NEW);
+ candidate = srte_candidate_add(
+ policy, path->nbkey.preference,
+ SRTE_ORIGIN_PCEP, path->originator);
+ candidate->policy->srp_id = path->srp_id;
+ strlcpy(candidate->name, path->name,
+ sizeof(candidate->name));
+ SET_FLAG(candidate->flags, F_CANDIDATE_NEW);
+ } else {
+ policy = candidate->policy;
+ if ((path->originator != candidate->originator)
+ || (path->originator != policy->originator)) {
+ /* There is already an initiated path from
+ * another PCE, show a warning and regect the
+ * initiated path */
+ zlog_warn(
+ "PCE %s tried to initiate a path already initiated by PCE %s",
+ path->originator,
+ candidate->originator);
+ return 1;
+ }
+ if ((policy->protocol_origin != SRTE_ORIGIN_PCEP)
+ || (candidate->protocol_origin
+ != SRTE_ORIGIN_PCEP)) {
+ /* There is already an initiated path from
+ * another PCE, show a warning and regect the
+ * initiated path */
+ zlog_warn(
+ "PCE %s tried to initiate a path created localy",
+ path->originator);
+ return 1;
+ }
+ }
+ return path_pcep_config_update_path(path);
+ }
+ return 0;
+}
+
+int path_pcep_config_update_path(struct path *path)
+{
+ assert(path != NULL);
+ assert(path->nbkey.preference != 0);
+ assert(path->nbkey.endpoint.ipa_type == IPADDR_V4);
+
+ int number_of_sid_clashed = 0;
+ struct path_hop *hop;
+ struct path_metric *metric;
+ int index;
+ char segment_list_name_buff[64 + 1 + 64 + 1 + 11 + 1];
+ char *segment_list_name = NULL;
+ struct srte_candidate *candidate;
+ struct srte_segment_list *segment_list = NULL;
+ struct srte_segment_entry *segment;
+
+ candidate = lookup_candidate(&path->nbkey);
+
+ // if there is no candidate to update we are done
+ if (!candidate)
+ return 0;
+
+ candidate->policy->srp_id = path->srp_id;
+ // first clean up old segment list if present
+ if (candidate->lsp->segment_list) {
+ SET_FLAG(candidate->lsp->segment_list->flags,
+ F_SEGMENT_LIST_DELETED);
+ srte_segment_list_del(candidate->lsp->segment_list);
+ candidate->lsp->segment_list = NULL;
+ }
+
+ if (path->first_hop == NULL)
+ return PATH_NB_ERR;
+
+ snprintf(segment_list_name_buff, sizeof(segment_list_name_buff),
+ "%s-%u", path->name, path->plsp_id);
+ segment_list_name = segment_list_name_buff;
+
+ segment_list = srte_segment_list_add(segment_list_name);
+ segment_list->protocol_origin = path->update_origin;
+ strlcpy(segment_list->originator, path->originator,
+ sizeof(segment_list->originator));
+ SET_FLAG(segment_list->flags, F_SEGMENT_LIST_NEW);
+ SET_FLAG(segment_list->flags, F_SEGMENT_LIST_MODIFIED);
+
+ for (hop = path->first_hop, index = 10; hop != NULL;
+ hop = hop->next, index += 10) {
+ assert(hop->has_sid);
+ assert(hop->is_mpls);
+
+ segment = srte_segment_entry_add(segment_list, index);
+
+ segment->sid_value = (mpls_label_t)hop->sid.mpls.label;
+ SET_FLAG(segment->segment_list->flags, F_SEGMENT_LIST_MODIFIED);
+
+ if (!hop->has_nai)
+ continue;
+ if (srte_segment_entry_set_nai(
+ segment, srte_nai_type(hop->nai.type),
+ &hop->nai.local_addr, hop->nai.local_iface,
+ &hop->nai.remote_addr, hop->nai.remote_iface, 0, 0)
+ == PATH_SID_ERROR)
+ /* TED queries don't match PCE */
+ /* Don't apply srte,zebra changes */
+ number_of_sid_clashed++;
+ }
+
+ candidate->lsp->segment_list = segment_list;
+ SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED);
+
+ for (metric = path->first_metric; metric != NULL; metric = metric->next)
+ srte_lsp_set_metric(
+ candidate->lsp,
+ (enum srte_candidate_metric_type)metric->type,
+ metric->value, metric->enforce, metric->is_bound,
+ metric->is_computed);
+
+ if (path->has_bandwidth)
+ srte_lsp_set_bandwidth(candidate->lsp, path->bandwidth,
+ path->enforce_bandwidth);
+
+ if (path->has_pce_objfun) {
+ SET_FLAG(candidate->lsp->flags, F_CANDIDATE_HAS_OBJFUN);
+ candidate->lsp->objfun = path->pce_objfun;
+ }
+
+ if (number_of_sid_clashed)
+ SET_FLAG(segment_list->flags, F_SEGMENT_LIST_SID_CONFLICT);
+ else
+ srte_apply_changes();
+
+ return 0;
+}
+
+struct srte_candidate *lookup_candidate(struct lsp_nb_key *key)
+{
+ struct srte_policy *policy = NULL;
+ policy = srte_policy_find(key->color, &key->endpoint);
+ if (policy == NULL)
+ return NULL;
+ return srte_candidate_find(policy, key->preference);
+}
+
+char *candidate_name(struct srte_candidate *candidate)
+{
+ if (candidate->protocol_origin == SRTE_ORIGIN_PCEP
+ || candidate->protocol_origin == SRTE_ORIGIN_BGP)
+ return asprintfrr(MTYPE_PCEP, "%s", candidate->policy->name);
+ else
+ return asprintfrr(MTYPE_PCEP, "%s-%s", candidate->policy->name,
+ candidate->name);
+}
+
+enum pcep_lsp_operational_status
+status_int_to_ext(enum srte_policy_status status)
+{
+ switch (status) {
+ case SRTE_POLICY_STATUS_UP:
+ return PCEP_LSP_OPERATIONAL_ACTIVE;
+ case SRTE_POLICY_STATUS_GOING_UP:
+ return PCEP_LSP_OPERATIONAL_GOING_UP;
+ case SRTE_POLICY_STATUS_GOING_DOWN:
+ return PCEP_LSP_OPERATIONAL_GOING_DOWN;
+ case SRTE_POLICY_STATUS_DOWN:
+ case SRTE_POLICY_STATUS_UNKNOWN:
+ return PCEP_LSP_OPERATIONAL_DOWN;
+ }
+
+ assert(!"Reached end of function where we are not expecting to");
+}
+
+enum pcep_sr_subobj_nai pcep_nai_type(enum srte_segment_nai_type type)
+{
+ switch (type) {
+ case SRTE_SEGMENT_NAI_TYPE_NONE:
+ return PCEP_SR_SUBOBJ_NAI_ABSENT;
+ case SRTE_SEGMENT_NAI_TYPE_IPV4_NODE:
+ return PCEP_SR_SUBOBJ_NAI_IPV4_NODE;
+ case SRTE_SEGMENT_NAI_TYPE_IPV6_NODE:
+ return PCEP_SR_SUBOBJ_NAI_IPV6_NODE;
+ case SRTE_SEGMENT_NAI_TYPE_IPV4_ADJACENCY:
+ return PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY;
+ case SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY:
+ return PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY;
+ case SRTE_SEGMENT_NAI_TYPE_IPV4_UNNUMBERED_ADJACENCY:
+ return PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY;
+ case SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY_LINK_LOCAL_ADDRESSES:
+ return PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY;
+ case SRTE_SEGMENT_NAI_TYPE_IPV4_LOCAL_IFACE:
+ return PCEP_SR_SUBOBJ_NAI_IPV4_NODE;
+ case SRTE_SEGMENT_NAI_TYPE_IPV6_LOCAL_IFACE:
+ return PCEP_SR_SUBOBJ_NAI_IPV6_NODE;
+ case SRTE_SEGMENT_NAI_TYPE_IPV4_ALGORITHM:
+ return PCEP_SR_SUBOBJ_NAI_IPV4_NODE;
+ case SRTE_SEGMENT_NAI_TYPE_IPV6_ALGORITHM:
+ return PCEP_SR_SUBOBJ_NAI_IPV6_NODE;
+ default:
+ return PCEP_SR_SUBOBJ_NAI_UNKNOWN;
+ }
+}
+
+enum srte_segment_nai_type srte_nai_type(enum pcep_sr_subobj_nai type)
+{
+ switch (type) {
+ case PCEP_SR_SUBOBJ_NAI_ABSENT:
+ return SRTE_SEGMENT_NAI_TYPE_NONE;
+ case PCEP_SR_SUBOBJ_NAI_IPV4_NODE:
+ return SRTE_SEGMENT_NAI_TYPE_IPV4_NODE;
+ case PCEP_SR_SUBOBJ_NAI_IPV6_NODE:
+ return SRTE_SEGMENT_NAI_TYPE_IPV6_NODE;
+ case PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY:
+ return SRTE_SEGMENT_NAI_TYPE_IPV4_ADJACENCY;
+ case PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY:
+ return SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY;
+ case PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY:
+ return SRTE_SEGMENT_NAI_TYPE_IPV4_UNNUMBERED_ADJACENCY;
+ case PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY:
+ case PCEP_SR_SUBOBJ_NAI_UNKNOWN:
+ return SRTE_SEGMENT_NAI_TYPE_NONE;
+ }
+
+ assert(!"Reached end of function where we were not expecting to");
+}