Repository: scionproto/scion
Pull request #3843 (2020-07-29 11:57:14)

colibri: add store and segment admission
Introduces the COLIBRI store.
Adds the segment admission handling to the store.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/scionproto/scion/3843)
<!-- Reviewable:end -->


Oncilla: /rebase

Patch:
diff --git a/go/cs/reservation/BUILD.bazel b/go/cs/reservation/BUILD.bazel
index 3fcd9552c..1a244179d 100644
--- a/go/cs/reservation/BUILD.bazel
+++ b/go/cs/reservation/BUILD.bazel
@@ -5,11 +5,13 @@ go_library(
     srcs = [
         "index.go",
         "request.go",
+        "types.go",
     ],
     importpath = "github.com/scionproto/scion/go/cs/reservation",
     visibility = ["//visibility:public"],
     deps = [
         "//go/lib/colibri/reservation:go_default_library",
+        "//go/lib/common:go_default_library",
         "//go/lib/serrors:go_default_library",
         "//go/lib/spath:go_default_library",
         "//go/lib/util:go_default_library",
diff --git a/go/cs/reservation/conf/BUILD.bazel b/go/cs/reservation/conf/BUILD.bazel
new file mode 100644
index 000000000..99a2bd1c4
--- /dev/null
+++ b/go/cs/reservation/conf/BUILD.bazel
@@ -0,0 +1,25 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+    name = "go_default_library",
+    srcs = ["capacities.go"],
+    importpath = "github.com/scionproto/scion/go/cs/reservation/conf",
+    visibility = ["//visibility:public"],
+    deps = [
+        "//go/cs/reservation:go_default_library",
+        "//go/lib/common:go_default_library",
+        "//go/lib/serrors:go_default_library",
+    ],
+)
+
+go_test(
+    name = "go_default_test",
+    srcs = ["capacities_test.go"],
+    data = glob(["testdata/**"]),
+    embed = [":go_default_library"],
+    deps = [
+        "//go/lib/common:go_default_library",
+        "//go/lib/xtest:go_default_library",
+        "@com_github_stretchr_testify//require:go_default_library",
+    ],
+)
diff --git a/go/cs/reservation/conf/capacities.go b/go/cs/reservation/conf/capacities.go
new file mode 100644
index 000000000..fd2ee68f5
--- /dev/null
+++ b/go/cs/reservation/conf/capacities.go
@@ -0,0 +1,117 @@
+// Copyright 2020 ETH Zurich, Anapaya Systems
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package conf
+
+import (
+	"encoding/json"
+	"sort"
+
+	base "github.com/scionproto/scion/go/cs/reservation"
+	"github.com/scionproto/scion/go/lib/common"
+	"github.com/scionproto/scion/go/lib/serrors"
+)
+
+// internal structure used to serialize to and from json.
+type capacities struct {
+	// ingress capacities
+	CapIn map[common.IFIDType]uint64 `json:"ingress_kbps"`
+	// egress capacities
+	CapEg map[common.IFIDType]uint64 `json:"egress_kbps"`
+	// configured allowed transit
+	In2Eg map[common.IFIDType]map[common.IFIDType]uint64 `json:"ingress_to_egress_kbps"`
+}
+
+// Capacities aka capacity matrix.
+type Capacities struct {
+	c capacities
+	// derived fields from the above ones:
+	inIfs []common.IFIDType
+	egIfs []common.IFIDType
+}
+
+var _ base.Capacities = (*Capacities)(nil)
+var _ json.Unmarshaler = (*Capacities)(nil)
+var _ json.Marshaler = (*Capacities)(nil)
+
+func (c *Capacities) IngressInterfaces() []common.IFIDType           { return c.inIfs }
+func (c *Capacities) EgressInterfaces() []common.IFIDType            { return c.egIfs }
+func (c *Capacities) Capacity(from, to common.IFIDType) uint64       { return c.c.In2Eg[from][to] }
+func (c *Capacities) CapacityIngress(ingress common.IFIDType) uint64 { return c.c.CapIn[ingress] }
+func (c *Capacities) CapacityEgress(egress common.IFIDType) uint64   { return c.c.CapEg[egress] }
+
+// UnmarshalJSON deserializes into the json-aware internal data structure.
+func (c *Capacities) UnmarshalJSON(b []byte) error {
+	if err := json.Unmarshal(b, &c.c); err != nil {
+		return err
+	}
+	return c.init()
+}
+
+// MarshalJSON serializes the internal json-friendly structure.
+func (c Capacities) MarshalJSON() ([]byte, error) {
+	return json.Marshal(c.c)
+}
+
+func (c *Capacities) init() error {
+	totalEgress := make(map[common.IFIDType]uint64)
+	for ingress, intoMap := range c.c.In2Eg {
+		var accumIngress uint64
+		for egress, cap := range intoMap {
+			accumIngress += cap
+			totalEgress[egress] += cap
+			if egress == ingress && cap != 0 {
+				return serrors.New("capacity is inconsistent, ingress to itself not zero",
+					"ingress", ingress)
+			}
+		}
+		if _, found := c.c.CapIn[ingress]; !found {
+			return serrors.New("capacity is inconsistent, must declare ingress capacity",
+				"ingress", ingress)
+		}
+		if accumIngress > c.c.CapIn[ingress] {
+			return serrors.New("capacity is inconsistent, ingress accum too high", "ingress",
+				ingress, "ingress_accum", accumIngress, "ingress_declared", c.c.CapIn[ingress])
+		}
+	}
+	for egress, accum := range totalEgress {
+		if _, found := c.c.CapEg[egress]; !found {
+			return serrors.New("capacity is inconsistent, must declare egress capacity",
+				"egress", egress)
+		}
+		if accum > c.c.CapEg[egress] {
+			return serrors.New("capacity is inconsistent, egress accum too high", "egress", egress,
+				"egress_accum", accum, "egress_declared", c.c.CapEg[egress])
+		}
+	}
+	// init list of ingress interfaces
+	c.inIfs = make([]common.IFIDType, len(c.c.CapIn))
+	i := 0
+	for ifid := range c.c.CapIn {
+		c.inIfs[i] = ifid
+		i++
+	}
+	// init list of egress interfaces
+	c.egIfs = make([]common.IFIDType, len(c.c.CapEg))
+	i = 0
+	for ifid := range c.c.CapEg {
+		c.egIfs[i] = ifid
+		i++
+	}
+	// sort them just to simplify debugging
+	sort.Slice(c.inIfs, func(i, j int) bool { return c.inIfs[i] < c.inIfs[j] })
+	sort.Slice(c.egIfs, func(i, j int) bool { return c.egIfs[i] < c.egIfs[j] })
+
+	return nil
+}
diff --git a/go/cs/reservation/request.go b/go/cs/reservation/request.go
index 3985a974b..96e41de3b 100644
--- a/go/cs/reservation/request.go
+++ b/go/cs/reservation/request.go
@@ -20,8 +20,9 @@ import (
 )
 
 // RequestMetadata contains information about the request, such as its forwarding path.
+// This base struct can be used by any request or response packets.
 type RequestMetadata struct {
-	Path spath.Path // the path the packet came with
+	path spath.Path // the path the packet came / will go with
 }
 
 // NewRequestMetadata constructs the base Request type.
@@ -30,6 +31,26 @@ func NewRequestMetadata(path *spath.Path) (*RequestMetadata, error) {
 		return nil, serrors.New("new request with nil path")
 	}
 	return &RequestMetadata{
-		Path: *path.Copy(),
+		path: *path.Copy(),
 	}, nil
 }
+
+// Path returns the spath.Path in this metadata.
+func (m *RequestMetadata) Path() *spath.Path {
+	return &m.path
+}
+
+// NumberOfHops returns the number of hops in this reservation.
+func (m *RequestMetadata) NumberOfHops() int {
+	return (len(m.path.Raw) - spath.InfoFieldLength) / spath.HopFieldLength
+}
+
+// IndexOfCurrentHop returns the 0-based index of the current hop.
+func (m *RequestMetadata) IndexOfCurrentHop() int {
+	return (m.path.HopOff - spath.InfoFieldLength) / spath.HopFieldLength
+}
+
+// IsLastAS returns true if this hop is the last one (this AS is the destination).
+func (m *RequestMetadata) IsLastAS() bool {
+	return m.IndexOfCurrentHop() == m.NumberOfHops()-1
+}
diff --git a/go/cs/reservation/segment/BUILD.bazel b/go/cs/reservation/segment/BUILD.bazel
index 08f837805..a9cbcc835 100644
--- a/go/cs/reservation/segment/BUILD.bazel
+++ b/go/cs/reservation/segment/BUILD.bazel
@@ -7,6 +7,7 @@ go_library(
         "path.go",
         "request.go",
         "reservation.go",
+        "response.go",
     ],
     importpath = "github.com/scionproto/scion/go/cs/reservation/segment",
     visibility = ["//visibility:public"],
diff --git a/go/cs/reservation/segment/admission/BUILD.bazel b/go/cs/reservation/segment/admission/BUILD.bazel
new file mode 100644
index 000000000..00e6a3bbb
--- /dev/null
+++ b/go/cs/reservation/segment/admission/BUILD.bazel
@@ -0,0 +1,9 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+    name = "go_default_library",
+    srcs = ["admitter.go"],
+    importpath = "github.com/scionproto/scion/go/cs/reservation/segment/admission",
+    visibility = ["//visibility:public"],
+    deps = ["//go/cs/reservation/segment:go_default_library"],
+)
diff --git a/go/cs/reservation/segment/admission/admitter.go b/go/cs/reservation/segment/admission/admitter.go
new file mode 100644
index 000000000..56d62e296
--- /dev/null
+++ b/go/cs/reservation/segment/admission/admitter.go
@@ -0,0 +1,28 @@
+// Copyright 2020 ETH Zurich, Anapaya Systems
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package admission
+
+import (
+	"context"
+
+	"github.com/scionproto/scion/go/cs/reservation/segment"
+)
+
+// Admitter specifies what an admission entity has to implement to govern the segment admission.
+type Admitter interface {
+	// req will be modified with the allowed and maximum bandwidths if they were computed.
+	// It can also return an error.
+	AdmitRsv(ctx context.Context, req *segment.SetupReq) error
+}
diff --git a/go/cs/reservation/segment/admission/impl/BUILD.bazel b/go/cs/reservation/segment/admission/impl/BUILD.bazel
new file mode 100644
index 000000000..dd5846c58
--- /dev/null
+++ b/go/cs/reservation/segment/admission/impl/BUILD.bazel
@@ -0,0 +1,35 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+    name = "go_default_library",
+    srcs = ["stateless.go"],
+    importpath = "github.com/scionproto/scion/go/cs/reservation/segment/admission/impl",
+    visibility = ["//visibility:public"],
+    deps = [
+        "//go/cs/reservation:go_default_library",
+        "//go/cs/reservation/segment:go_default_library",
+        "//go/cs/reservation/segment/admission:go_default_library",
+        "//go/cs/reservationstorage/backend:go_default_library",
+        "//go/lib/addr:go_default_library",
+        "//go/lib/colibri/reservation:go_default_library",
+        "//go/lib/common:go_default_library",
+        "//go/lib/serrors:go_default_library",
+    ],
+)
+
+go_test(
+    name = "go_default_test",
+    srcs = ["stateless_test.go"],
+    embed = [":go_default_library"],
+    deps = [
+        "//go/cs/reservation:go_default_library",
+        "//go/cs/reservation/segment:go_default_library",
+        "//go/cs/reservationstorage/backend/mock_backend:go_default_library",
+        "//go/lib/colibri/reservation:go_default_library",
+        "//go/lib/common:go_default_library",
+        "//go/lib/util:go_default_library",
+        "//go/lib/xtest:go_default_library",
+        "@com_github_golang_mock//gomock:go_default_library",
+        "@com_github_stretchr_testify//require:go_default_library",
+    ],
+)
diff --git a/go/cs/reservation/segment/admission/impl/stateless.go b/go/cs/reservation/segment/admission/impl/stateless.go
new file mode 100644
index 000000000..e547b085c
--- /dev/null
+++ b/go/cs/reservation/segment/admission/impl/stateless.go
@@ -0,0 +1,293 @@
+// Copyright 2020 ETH Zurich, Anapaya Systems
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package impl
+
+import (
+	"context"
+	"math"
+
+	base "github.com/scionproto/scion/go/cs/reservation"
+	"github.com/scionproto/scion/go/cs/reservation/segment"
+	"github.com/scionproto/scion/go/cs/reservation/segment/admission"
+	"github.com/scionproto/scion/go/cs/reservationstorage/backend"
+	"github.com/scionproto/scion/go/lib/addr"
+	"github.com/scionproto/scion/go/lib/colibri/reservation"
+	"github.com/scionproto/scion/go/lib/common"
+	"github.com/scionproto/scion/go/lib/serrors"
+)
+
+// StatelessAdmission can admit a segment reservation without any state other than the DB.
+type StatelessAdmission struct {
+	DB         backend.DB
+	Capacities base.Capacities // aka capacity matrix
+	Delta      float64         // fraction of free BW that can be reserved in one request
+}
+
+var _ admission.Admitter = (*StatelessAdmission)(nil)
+
+// AdmitRsv admits a segment reservation. The request will be modified with the allowed and
+// maximum bandwidths if they were computed. It can also return an error that must be checked.
+func (a *StatelessAdmission) AdmitRsv(ctx context.Context, req *segment.SetupReq) error {
+	avail, err := a.availableBW(ctx, req)
+	if err != nil {
+		return serrors.WrapStr("cannot compute available bandwidth", err, "segment_id", req.ID)
+	}
+	ideal, err := a.idealBW(ctx, req)
+	if err != nil {
+		return serrors.WrapStr("cannot compute ideal bandwidth", err, "segment_id", req.ID)
+	}
+	maxAlloc := reservation.BWClsFromBW(minBW(avail, ideal))
+	bead := reservation.AllocationBead{
+		AllocBW: reservation.MinBWCls(maxAlloc, req.MaxBW),
+		MaxBW:   maxAlloc,
+	}
+	req.AllocTrail = append(req.AllocTrail, bead)
+	if maxAlloc < req.MinBW {
+		return serrors.New("admission denied", "maxalloc", maxAlloc, "minbw", req.MinBW,
+			"segment_id", req.ID)
+	}
+	return nil
+}
+
+func (a *StatelessAdmission) availableBW(ctx context.Context, req *segment.SetupReq) (
+	uint64, error) {
+
+	sameIngress, err := a.DB.GetSegmentRsvsFromIFPair(ctx, &req.Ingress, nil)
+	if err != nil {
+		return 0, serrors.WrapStr("cannot get reservations using ingress", err,
+			"ingress", req.Ingress)
+	}
+	sameEgress, err := a.DB.GetSegmentRsvsFromIFPair(ctx, nil, &req.Egress)
+	if err != nil {
+		return 0, serrors.WrapStr("cannot get reservations using egress", err,
+			"egress", req.Egress)
+	}
+	bwIngress := sumMaxBlockedBW(sameIngress, req.ID)
+	freeIngress := a.Capacities.CapacityIngress(req.Ingress) - bwIngress
+	bwEgress := sumMaxBlockedBW(sameEgress, req.ID)
+	freeEgress := a.Capacities.CapacityEgress(req.Egress) - bwEgress
+	// `free` excludes the BW from an existing reservation if its ID equals the request's ID
+	free := float64(minBW(freeIngress, freeEgress))
+	return uint64(free * a.Delta), nil
+}
+
+func (a *StatelessAdmission) idealBW(ctx context.Context, req *segment.SetupReq) (uint64, error) {
+	demsPerSrcRegIngress, err := a.computeTempDemands(ctx, req.Ingress, req)
+	if err != nil {
+		return 0, serrors.WrapStr("cannot compute temporary demands", err)
+	}
+	tubeRatio, err := a.tubeRatio(ctx, req, demsPerSrcRegIngress)
+	if err != nil {
+		return 0, serrors.WrapStr("cannot compute tube ratio", err)
+	}
+	linkRatio, err := a.linkRatio(ctx, req, demsPerSrcRegIngress)
+	if err != nil {
+		return 0, serrors.WrapStr("cannot compute link ratio", err)
+	}
+	cap := float64(a.Capacities.CapacityEgress(req.Egress))
+	return uint64(cap * tubeRatio * linkRatio), nil
+}
+
+func (a *StatelessAdmission) tubeRatio(ctx context.Context, req *segment.SetupReq,
+	demsPerSrc demPerSource) (float64, error) {
+
+	// TODO(juagargi) to avoid calling several times to computeTempDemands, refactor the
+	// type holding the results, so that it stores capReqDem per source per ingress interface.
+	// InScalFctr and EgScalFctr will be stored independently, per source per interface.
+	transitDemand, err := a.transitDemand(ctx, req, req.Ingress, demsPerSrc)
+	if err != nil {
+		return 0, serrors.WrapStr("cannot compute transit demand", err)
+	}
+	capIn := a.Capacities.CapacityIngress(req.Ingress)
+	numerator := minBW(capIn, transitDemand)
+	var sum uint64
+	for _, in := range a.Capacities.IngressInterfaces() {
+		demandsForThisIngress, err := a.computeTempDemands(ctx, in, req)
+		if err != nil {
+			return 0, serrors.WrapStr("cannot compute transit demand", err)
+		}
+		dem, err := a.transitDemand(ctx, req, in, demandsForThisIngress)
+		if err != nil {
+			return 0, serrors.WrapStr("cannot compute transit demand", err)
+		}
+		sum += minBW(a.Capacities.CapacityIngress(in), dem)
+	}
+	return float64(numerator) / float64(sum), nil
+}
+
+func (a *StatelessAdmission) linkRatio(ctx context.Context, req *segment.SetupReq,
+	demsPerSrc demPerSource) (float64, error) {
+
+	capEg := a.Capacities.CapacityEgress(req.Egress)
+	demEg := demsPerSrc[req.ID.ASID].eg
+
+	prevBW := req.AllocTrail.MinMax().ToKbps() // min of maxBW in the trail
+	var egScalFctr float64
+	if demEg != 0 {
+		egScalFctr = float64(minBW(capEg, demEg)) / float64(demEg)
+	}
+	numerator := egScalFctr * float64(prevBW)
+	egScalFctrs := make(map[addr.AS]float64)
+	for src, dem := range demsPerSrc {
+		var egScalFctr float64
+		if dem.eg != 0 {
+			egScalFctr = float64(minBW(capEg, dem.eg)) / float64(dem.eg)
+		}
+		egScalFctrs[src] = egScalFctr
+	}
+	rsvs, err := a.DB.GetAllSegmentRsvs(ctx)
+	if err != nil {
+		return 0, serrors.WrapStr("cannot list all reservations", err)
+	}
+	srcAllocPerSrc := make(map[addr.AS]uint64)
+	for _, rsv := range rsvs {
+		if rsv.ID == req.ID {
+			continue
+		}
+		src := rsv.ID.ASID
+		srcAlloc := rsv.MaxBlockedBW()
+		srcAllocPerSrc[src] += srcAlloc
+	}
+	if _, found := srcAllocPerSrc[req.ID.ASID]; !found {
+		// add the source of the request, if not already present
+		srcAllocPerSrc[req.ID.ASID] = 0 // the value of the srcAlloc itself won't be used
+	}
+	// TODO(juagargi) after debugging, integrate this loop into the previous one:
+	var denom float64
+	for src, srcAlloc := range srcAllocPerSrc {
+		if src == req.ID.ASID {
+			srcAlloc += prevBW
+		}
+		egScalFctr, found := egScalFctrs[src]
+		if !found {
+			return 0, serrors.New("cannot compute link ratio, internal error: "+
+				"source not found in the egress scale factors", "src", src)
+		}
+		denom += float64(srcAlloc) * egScalFctr
+	}
+	return numerator / denom, nil
+}
+
+// demands represents the demands for a given source, and a specific ingress-egress interface pair.
+// from the admission spec: srcDem, inDem and egDem for a given source.
+type demands struct {
+	src, in, eg uint64
+}
+
+// demsPerSrc is used in the transit demand computation.
+type demPerSource map[addr.AS]demands
+
+// computeTempDemands will compute inDem, egDem and srcDem grouped by source, for all sources.
+// this is, all cap. requested demands from all reservations, grouped by source, that enter
+// the AS at "ingress" and exit at "egress". It also stores all the source demands that enter
+// the AS at "ingress", and the source demands that exit the AS at "egress".
+func (a *StatelessAdmission) computeTempDemands(ctx context.Context, ingress common.IFIDType,
+	req *segment.SetupReq) (demPerSource, error) {
+
+	// TODO(juagargi) consider adding a call to db to get all srcDem,inDem,egDem grouped by source
+	rsvs, err := a.DB.GetAllSegmentRsvs(ctx)
+	if err != nil {
+		return nil, serrors.WrapStr("cannot obtain segment rsvs. from ingress/egress pair", err)
+	}
+	capIn := a.Capacities.CapacityIngress(ingress)
+	capEg := a.Capacities.CapacityEgress(req.Egress)
+	// srcDem, inDem and egDem grouped by source
+	demsPerSrc := make(demPerSource)
+	for _, rsv := range rsvs {
+		if rsv.ID == req.ID {
+			continue
+		}
+		dem := min3BW(capIn, capEg, rsv.MaxRequestedBW()) // capReqDem in the formulas
+		bucket := demsPerSrc[rsv.ID.ASID]
+		if rsv.Ingress == ingress {
+			bucket.in += dem
+		}
+		if rsv.Egress == req.Egress {
+			bucket.eg += dem
+		}
+		if rsv.Ingress == ingress && rsv.Egress == req.Egress {
+			bucket.src += dem
+		}
+		demsPerSrc[rsv.ID.ASID] = bucket
+	}
+	// add the request itself to whatever we have for that source
+	bucket := demsPerSrc[req.ID.ASID]
+	dem := min3BW(capIn, capEg, req.MaxBW.ToKbps())
+	if req.Ingress == ingress {
+		bucket.in += dem
+	}
+	if req.Egress == req.Egress {
+		bucket.eg += dem
+	}
+	if req.Ingress == ingress && req.Egress == req.Egress {
+		bucket.src += dem
+	}
+	demsPerSrc[req.ID.ASID] = bucket
+
+	return demsPerSrc, nil
+}
+
+// transitDemand computes the transit demand from ingress to req.Egress. The parameter
+// demsPerSrc must hold the inDem, egDem and srcDem of all reservations, grouped by source, and
+// for an ingress interface = ingress parameter.
+func (a *StatelessAdmission) transitDemand(ctx context.Context, req *segment.SetupReq,
+	ingress common.IFIDType, demsPerSrc demPerSource) (uint64, error) {
+
+	capIn := a.Capacities.CapacityIngress(ingress)
+	capEg := a.Capacities.CapacityEgress(req.Egress)
+	// TODO(juagargi) adjSrcDem is not needed, remove after finishing debugging the admission
+	adjSrcDem := make(map[addr.AS]uint64) // every adjSrcDem grouped by source
+	for src, dems := range demsPerSrc {
+		var inScalFctr float64 = 1.
+		if dems.in != 0 {
+			inScalFctr = float64(minBW(capIn, dems.in)) / float64(dems.in)
+		}
+		var egScalFctr float64 = 1.
+		if dems.eg != 0 {
+			egScalFctr = float64(minBW(capEg, dems.eg)) / float64(dems.eg)
+		}
+		adjSrcDem[src] = uint64(math.Min(inScalFctr, egScalFctr) * float64(dems.src))
+	}
+	// now reduce adjSrcDem
+	var transitDem uint64
+	for _, dem := range adjSrcDem {
+		transitDem += dem
+	}
+
+	return transitDem, nil
+}
+
+// sumMaxBlockedBW adds up all the max blocked bandwidth by the reservation, for all reservations,
+// iff they don't have the same ID as "excludeThisRsv".
+func sumMaxBlockedBW(rsvs []*segment.Reservation, excludeThisRsv reservation.SegmentID) uint64 {
+	var total uint64
+	for _, r := range rsvs {
+		if r.ID != excludeThisRsv {
+			total += r.MaxBlockedBW()
+		}
+	}
+	return total
+}
+
+func minBW(a, b uint64) uint64 {
+	if a < b {
+		return a
+	}
+	return b
+}
+
+func min3BW(a, b, c uint64) uint64 {
+	return minBW(minBW(a, b), c)
+}
diff --git a/go/cs/reservation/segment/request.go b/go/cs/reservation/segment/request.go
index d33123491..3c47a48ba 100644
--- a/go/cs/reservation/segment/request.go
+++ b/go/cs/reservation/segment/request.go
@@ -15,6 +15,7 @@
 package segment
 
 import (
+	"encoding/hex"
 	"time"
 
 	base "github.com/scionproto/scion/go/cs/reservation"
@@ -27,12 +28,12 @@ import (
 
 // Request is the base struct for any type of COLIBRI request.
 type Request struct {
-	Metadata    base.RequestMetadata  // information about the request (forwarding path)
-	ID          reservation.SegmentID // the ID this request refers to
-	Timestamp   time.Time             // the mandatory timestamp
-	Ingress     common.IFIDType       // the interface the reservation traffic uses to enter the AS
-	Egress      common.IFIDType       // the interface the reservation traffic uses to leave the AS
-	Reservation *Reservation          // nil if no reservation yet
+	base.RequestMetadata                       // information about the request (forwarding path)
+	ID                   reservation.SegmentID // the ID this request refers to
+	Timestamp            time.Time             // the mandatory timestamp
+	Ingress              common.IFIDType       // the interface the traffic uses to enter the AS
+	Egress               common.IFIDType       // the interface the traffic uses to leave the AS
+	Reservation          *Reservation          // nil if no reservation yet
 }
 
 // NewRequest constructs the segment Request type.
@@ -64,11 +65,11 @@ func NewRequest(ts time.Time, ID *colibri_mgmt.SegmentReservationID,
 		return nil, serrors.WrapStr("new segment request", err)
 	}
 	return &Request{
-		Timestamp: ts,
-		Metadata:  *metadata,
-		ID:        *segID,
-		Ingress:   ingressIFID,
-		Egress:    egressIFID,
+		RequestMetadata: *metadata,
+		Timestamp:       ts,
+		ID:              *segID,
+		Ingress:         ingressIFID,
+		Egress:          egressIFID,
 	}, nil
 }
 
@@ -77,11 +78,12 @@ func NewRequest(ts time.Time, ID *colibri_mgmt.SegmentReservationID,
 // This same type is used for renewal of the segment reservation.
 type SetupReq struct {
 	Request
-	MinBW      uint8
-	MaxBW      uint8
-	SplitCls   uint8
+	InfoField  reservation.InfoField
+	MinBW      reservation.BWCls
+	MaxBW      reservation.BWCls
+	SplitCls   reservation.SplitCls
 	PathProps  reservation.PathEndProps
-	AllocTrail []reservation.AllocationBead
+	AllocTrail reservation.AllocationBeads
 }
 
 // NewSetupReqFromCtrlMsg constructs a SetupReq from its control message counterpart. The timestamp
@@ -93,19 +95,27 @@ func NewSetupReqFromCtrlMsg(setup *colibri_mgmt.SegmentSetup, ts time.Time,
 	if err != nil {
 		return nil, serrors.WrapStr("cannot construct segment setup request", err)
 	}
+	inF, err := reservation.InfoFieldFromRaw(setup.InfoField)
+	if err != nil {
+		return nil, serrors.WrapStr("cannot construct info field from raw", err)
+	}
+	if inF == nil {
+		return nil, serrors.New("empty info field", "raw", hex.EncodeToString(setup.InfoField))
+	}
 	s := SetupReq{
 		Request:    *r,
-		MinBW:      setup.MinBW,
-		MaxBW:      setup.MaxBW,
-		SplitCls:   setup.SplitCls,
-		AllocTrail: make([]reservation.AllocationBead, len(setup.AllocationTrail)),
+		InfoField:  *inF,
+		MinBW:      reservation.BWCls(setup.MinBW),
+		MaxBW:      reservation.BWCls(setup.MaxBW),
+		SplitCls:   reservation.SplitCls(setup.SplitCls),
+		AllocTrail: make(reservation.AllocationBeads, len(setup.AllocationTrail)),
 		PathProps: reservation.NewPathEndProps(setup.StartProps.Local, setup.StartProps.Transfer,
 			setup.EndProps.Local, setup.EndProps.Transfer),
 	}
 	for i, ab := range setup.AllocationTrail {
 		s.AllocTrail[i] = reservation.AllocationBead{
-			AllocBW: ab.AllocBW,
-			MaxBW:   ab.MaxBW,
+			AllocBW: reservation.BWCls(ab.AllocBW),
+			MaxBW:   reservation.BWCls(ab.MaxBW),
 		}
 	}
 	return &s, nil
@@ -113,10 +123,11 @@ func NewSetupReqFromCtrlMsg(setup *colibri_mgmt.SegmentSetup, ts time.Time,
 
 // ToCtrlMsg creates a new segment setup control message filled with the information from here.
 func (r *SetupReq) ToCtrlMsg() *colibri_mgmt.SegmentSetup {
+
 	msg := &colibri_mgmt.SegmentSetup{
-		MinBW:    r.MinBW,
-		MaxBW:    r.MaxBW,
-		SplitCls: r.SplitCls,
+		MinBW:    uint8(r.MinBW),
+		MaxBW:    uint8(r.MaxBW),
+		SplitCls: uint8(r.SplitCls),
 		StartProps: colibri_mgmt.PathEndProps{
 			Local:    (r.PathProps & reservation.StartLocal) != 0,
 			Transfer: (r.PathProps & reservation.StartTransfer) != 0,
@@ -125,12 +136,13 @@ func (r *SetupReq) ToCtrlMsg() *colibri_mgmt.SegmentSetup {
 			Local:    (r.PathProps & reservation.EndLocal) != 0,
 			Transfer: (r.PathProps & reservation.EndTransfer) != 0,
 		},
-		AllocationTrail: make([]*colibri_mgmt.AllocationBeads, len(r.AllocTrail)),
+		InfoField:       r.InfoField.ToRaw(),
+		AllocationTrail: make([]*colibri_mgmt.AllocationBead, len(r.AllocTrail)),
 	}
 	for i, bead := range r.AllocTrail {
-		msg.AllocationTrail[i] = &colibri_mgmt.AllocationBeads{
-			AllocBW: bead.AllocBW,
-			MaxBW:   bead.MaxBW,
+		msg.AllocationTrail[i] = &colibri_mgmt.AllocationBead{
+			AllocBW: uint8(bead.AllocBW),
+			MaxBW:   uint8(bead.MaxBW),
 		}
 	}
 	return msg
diff --git a/go/cs/reservation/segment/reservation.go b/go/cs/reservation/segment/reservation.go
index 7ef3c6a72..011792bda 100644
--- a/go/cs/reservation/segment/reservation.go
+++ b/go/cs/reservation/segment/reservation.go
@@ -31,6 +31,7 @@ type Reservation struct {
 	Ingress      common.IFIDType          // igress interface ID: reservation packets enter
 	Egress       common.IFIDType          // egress interface ID: reservation packets leave
 	Path         Path                     // empty if not at the source of the reservation
+	PathType     reservation.PathType     // the type of path (up,core,down)
 	PathEndProps reservation.PathEndProps // the properties for stitching and start/end
 	TrafficSplit reservation.SplitCls     // the traffic split between control and data planes
 }
@@ -143,12 +144,12 @@ func (r *Reservation) addIndex(index *Index) (reservation.IndexNumber, error) {
 }
 
 // Index finds the Index with that IndexNumber and returns a pointer to it.
-func (r *Reservation) Index(idx reservation.IndexNumber) (*Index, error) {
+func (r *Reservation) Index(idx reservation.IndexNumber) *Index {
 	sliceIndex, err := base.FindIndex(r.Indices, idx)
 	if err != nil {
-		return nil, err
+		return nil
 	}
-	return &r.Indices[sliceIndex], nil
+	return &r.Indices[sliceIndex]
 }
 
 // SetIndexConfirmed sets the index as IndexPending (confirmed but not active). If the requested
@@ -206,3 +207,28 @@ func (r *Reservation) RemoveIndex(idx reservation.IndexNumber) error {
 	}
 	return nil
 }
+
+// MaxBlockedBW returns the maximum bandwidth blocked by this reservation, which is
+// the same as the maximum allocated bandwidth indicated by its indices.
+func (r *Reservation) MaxBlockedBW() uint64 {
+	if len(r.Indices) == 0 {
+		return 0
+	}
+	var max reservation.BWCls
+	for _, idx := range r.Indices {
+		max = reservation.MaxBWCls(max, idx.AllocBW)
+	}
+	return max.ToKbps()
+}
+
+// MaxRequestedBW returns the maximum bandwidth requested by this reservation.
+func (r *Reservation) MaxRequestedBW() uint64 {
+	if len(r.Indices) == 0 {
+		return 0
+	}
+	var max reservation.BWCls
+	for _, idx := range r.Indices {
+		max = reservation.MaxBWCls(max, idx.MaxBW)
+	}
+	return max.ToKbps()
+}
diff --git a/go/cs/reservation/segment/response.go b/go/cs/reservation/segment/response.go
new file mode 100644
index 000000000..b9f81a1e8
--- /dev/null
+++ b/go/cs/reservation/segment/response.go
@@ -0,0 +1,33 @@
+// Copyright 2020 ETH Zurich, Anapaya Systems
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package segment
+
+import (
+	base "github.com/scionproto/scion/go/cs/reservation"
+	"github.com/scionproto/scion/go/lib/colibri/reservation"
+)
+
+// ResponseSetupSuccess is the response to a success setup. It's sent on the reverse direction.
+type ResponseSetupSuccess struct {
+	base.RequestMetadata // information about the request (forwarding path)
+	Token                reservation.Token
+}
+
+// ResponseSetupFailure is the response to a failed setup. It's sent on the reverse direction.
+type ResponseSetupFailure struct {
+	base.RequestMetadata // information about the request (forwarding path)
+	FailedSetup          *SetupReq
+	FailedHop            uint8
+}
diff --git a/go/cs/reservation/sqlite/db.go b/go/cs/reservation/sqlite/db.go
index 3247cd34f..53d7be24a 100644
--- a/go/cs/reservation/sqlite/db.go
+++ b/go/cs/reservation/sqlite/db.go
@@ -181,6 +181,11 @@ func (x *executor) GetSegmentRsvFromPath(ctx context.Context, path segment.Path)
 	}
 }
 
+// GetAllSegmentRsvs returns all segment reservations.
+func (x *executor) GetAllSegmentRsvs(ctx context.Context) ([]*segment.Reservation, error) {
+	return getSegReservations(ctx, x.db, "", nil)
+}
+
 // GetSegmentRsvsFromIFPair returns all segment reservations that enter this AS at
 // the specified ingress and exit at that egress.
 func (x *executor) GetSegmentRsvsFromIFPair(ctx context.Context, ingress, egress *common.IFIDType) (
diff --git a/go/cs/reservation/types.go b/go/cs/reservation/types.go
new file mode 100644
index 000000000..240453df6
--- /dev/null
+++ b/go/cs/reservation/types.go
@@ -0,0 +1,35 @@
+// Copyright 2020 ETH Zurich, Anapaya Systems
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package reservation
+
+import (
+	"github.com/scionproto/scion/go/lib/common"
+	"github.com/scionproto/scion/go/lib/spath"
+)
+
+// Capacities describes what a capacity description must offer.
+type Capacities interface {
+	IngressInterfaces() []common.IFIDType
+	EgressInterfaces() []common.IFIDType
+	Capacity(from, to common.IFIDType) uint64
+	CapacityIngress(ingress common.IFIDType) uint64
+	CapacityEgress(egress common.IFIDType) uint64
+}
+
+// MessageWithPath is used to send messages from the COLIBRI service via the BR.
+type MessageWithPath interface {
+	Path() *spath.Path
+	// Payload() []byte
+}
diff --git a/go/cs/reservationstorage/BUILD.bazel b/go/cs/reservationstorage/BUILD.bazel
index e0223af62..5dada96df 100644
--- a/go/cs/reservationstorage/BUILD.bazel
+++ b/go/cs/reservationstorage/BUILD.bazel
@@ -6,8 +6,10 @@ go_library(
     importpath = "github.com/scionproto/scion/go/cs/reservationstorage",
     visibility = ["//visibility:public"],
     deps = [
+        "//go/cs/reservation:go_default_library",
         "//go/cs/reservation/e2e:go_default_library",
         "//go/cs/reservation/segment:go_default_library",
         "//go/lib/colibri/reservation:go_default_library",
+        "//go/lib/infra/modules/cleaner:go_default_library",
     ],
 )
diff --git a/go/cs/reservationstorage/backend/db.go b/go/cs/reservationstorage/backend/db.go
index 2ab20baad..50fd2575d 100644
--- a/go/cs/reservationstorage/backend/db.go
+++ b/go/cs/reservationstorage/backend/db.go
@@ -44,6 +44,8 @@ type ReserverOnly interface {
 
 // TransitOnly represents an AS in-path of a reservation, not the one originating it.
 type TransitOnly interface {
+	// GetAllSegmentRsvs returns all segment reservations. Used by setup req.
+	GetAllSegmentRsvs(ctx context.Context) ([]*segment.Reservation, error)
 	// GetSegmentRsvsFromIFPair returns all segment reservations that enter this AS at
 	// the specified ingress and exit at that egress. Used by setup req.
 	GetSegmentRsvsFromIFPair(ctx context.Context, ingress, egress *common.IFIDType) (
diff --git a/go/cs/reservationstorage/backend/mock_backend/BUILD.bazel b/go/cs/reservationstorage/backend/mock_backend/BUILD.bazel
new file mode 100644
index 000000000..e21a03d35
--- /dev/null
+++ b/go/cs/reservationstorage/backend/mock_backend/BUILD.bazel
@@ -0,0 +1,17 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+    name = "go_default_library",
+    srcs = ["backend.go"],
+    importpath = "github.com/scionproto/scion/go/cs/reservationstorage/backend/mock_backend",
+    visibility = ["//visibility:public"],
+    deps = [
+        "//go/cs/reservation/e2e:go_default_library",
+        "//go/cs/reservation/segment:go_default_library",
+        "//go/cs/reservationstorage/backend:go_default_library",
+        "//go/lib/addr:go_default_library",
+        "//go/lib/colibri/reservation:go_default_library",
+        "//go/lib/common:go_default_library",
+        "@com_github_golang_mock//gomock:go_default_library",
+    ],
+)
diff --git a/go/cs/reservationstorage/backend/mock_backend/backend.go b/go/cs/reservationstorage/backend/mock_backend/backend.go
new file mode 100644
index 000000000..f2b373a35
--- /dev/null
+++ b/go/cs/reservationstorage/backend/mock_backend/backend.go
@@ -0,0 +1,498 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: github.com/scionproto/scion/go/cs/reservationstorage/backend (interfaces: DB,Transaction)
+
+// Package mock_backend is a generated GoMock package.
+package mock_backend
+
+import (
+	context "context"
+	sql "database/sql"
+	gomock "github.com/golang/mock/gomock"
+	e2e "github.com/scionproto/scion/go/cs/reservation/e2e"
+	segment "github.com/scionproto/scion/go/cs/reservation/segment"
+	backend "github.com/scionproto/scion/go/cs/reservationstorage/backend"
+	addr "github.com/scionproto/scion/go/lib/addr"
+	reservation "github.com/scionproto/scion/go/lib/colibri/reservation"
+	common "github.com/scionproto/scion/go/lib/common"
+	reflect "reflect"
+	time "time"
+)
+
+// MockDB is a mock of DB interface
+type MockDB struct {
+	ctrl     *gomock.Controller
+	recorder *MockDBMockRecorder
+}
+
+// MockDBMockRecorder is the mock recorder for MockDB
+type MockDBMockRecorder struct {
+	mock *MockDB
+}
+
+// NewMockDB creates a new mock instance
+func NewMockDB(ctrl *gomock.Controller) *MockDB {
+	mock := &MockDB{ctrl: ctrl}
+	mock.recorder = &MockDBMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use
+func (m *MockDB) EXPECT() *MockDBMockRecorder {
+	return m.recorder
+}
+
+// BeginTransaction mocks base method
+func (m *MockDB) BeginTransaction(arg0 context.Context, arg1 *sql.TxOptions) (backend.Transaction, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "BeginTransaction", arg0, arg1)
+	ret0, _ := ret[0].(backend.Transaction)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// BeginTransaction indicates an expected call of BeginTransaction
+func (mr *MockDBMockRecorder) BeginTransaction(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BeginTransaction", reflect.TypeOf((*MockDB)(nil).BeginTransaction), arg0, arg1)
+}
+
+// Close mocks base method
+func (m *MockDB) Close() error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Close")
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Close indicates an expected call of Close
+func (mr *MockDBMockRecorder) Close() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockDB)(nil).Close))
+}
+
+// DeleteExpiredIndices mocks base method
+func (m *MockDB) DeleteExpiredIndices(arg0 context.Context, arg1 time.Time) (int, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "DeleteExpiredIndices", arg0, arg1)
+	ret0, _ := ret[0].(int)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// DeleteExpiredIndices indicates an expected call of DeleteExpiredIndices
+func (mr *MockDBMockRecorder) DeleteExpiredIndices(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteExpiredIndices", reflect.TypeOf((*MockDB)(nil).DeleteExpiredIndices), arg0, arg1)
+}
+
+// DeleteSegmentRsv mocks base method
+func (m *MockDB) DeleteSegmentRsv(arg0 context.Context, arg1 *reservation.SegmentID) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "DeleteSegmentRsv", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// DeleteSegmentRsv indicates an expected call of DeleteSegmentRsv
+func (mr *MockDBMockRecorder) DeleteSegmentRsv(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSegmentRsv", reflect.TypeOf((*MockDB)(nil).DeleteSegmentRsv), arg0, arg1)
+}
+
+// GetAllSegmentRsvs mocks base method
+func (m *MockDB) GetAllSegmentRsvs(arg0 context.Context) ([]*segment.Reservation, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetAllSegmentRsvs", arg0)
+	ret0, _ := ret[0].([]*segment.Reservation)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetAllSegmentRsvs indicates an expected call of GetAllSegmentRsvs
+func (mr *MockDBMockRecorder) GetAllSegmentRsvs(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllSegmentRsvs", reflect.TypeOf((*MockDB)(nil).GetAllSegmentRsvs), arg0)
+}
+
+// GetE2ERsvFromID mocks base method
+func (m *MockDB) GetE2ERsvFromID(arg0 context.Context, arg1 *reservation.E2EID) (*e2e.Reservation, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetE2ERsvFromID", arg0, arg1)
+	ret0, _ := ret[0].(*e2e.Reservation)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetE2ERsvFromID indicates an expected call of GetE2ERsvFromID
+func (mr *MockDBMockRecorder) GetE2ERsvFromID(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetE2ERsvFromID", reflect.TypeOf((*MockDB)(nil).GetE2ERsvFromID), arg0, arg1)
+}
+
+// GetE2ERsvsOnSegRsv mocks base method
+func (m *MockDB) GetE2ERsvsOnSegRsv(arg0 context.Context, arg1 *reservation.SegmentID) ([]*e2e.Reservation, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetE2ERsvsOnSegRsv", arg0, arg1)
+	ret0, _ := ret[0].([]*e2e.Reservation)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetE2ERsvsOnSegRsv indicates an expected call of GetE2ERsvsOnSegRsv
+func (mr *MockDBMockRecorder) GetE2ERsvsOnSegRsv(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetE2ERsvsOnSegRsv", reflect.TypeOf((*MockDB)(nil).GetE2ERsvsOnSegRsv), arg0, arg1)
+}
+
+// GetSegmentRsvFromID mocks base method
+func (m *MockDB) GetSegmentRsvFromID(arg0 context.Context, arg1 *reservation.SegmentID) (*segment.Reservation, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetSegmentRsvFromID", arg0, arg1)
+	ret0, _ := ret[0].(*segment.Reservation)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetSegmentRsvFromID indicates an expected call of GetSegmentRsvFromID
+func (mr *MockDBMockRecorder) GetSegmentRsvFromID(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSegmentRsvFromID", reflect.TypeOf((*MockDB)(nil).GetSegmentRsvFromID), arg0, arg1)
+}
+
+// GetSegmentRsvFromPath mocks base method
+func (m *MockDB) GetSegmentRsvFromPath(arg0 context.Context, arg1 segment.Path) (*segment.Reservation, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetSegmentRsvFromPath", arg0, arg1)
+	ret0, _ := ret[0].(*segment.Reservation)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetSegmentRsvFromPath indicates an expected call of GetSegmentRsvFromPath
+func (mr *MockDBMockRecorder) GetSegmentRsvFromPath(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSegmentRsvFromPath", reflect.TypeOf((*MockDB)(nil).GetSegmentRsvFromPath), arg0, arg1)
+}
+
+// GetSegmentRsvsFromIFPair mocks base method
+func (m *MockDB) GetSegmentRsvsFromIFPair(arg0 context.Context, arg1, arg2 *common.IFIDType) ([]*segment.Reservation, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetSegmentRsvsFromIFPair", arg0, arg1, arg2)
+	ret0, _ := ret[0].([]*segment.Reservation)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetSegmentRsvsFromIFPair indicates an expected call of GetSegmentRsvsFromIFPair
+func (mr *MockDBMockRecorder) GetSegmentRsvsFromIFPair(arg0, arg1, arg2 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSegmentRsvsFromIFPair", reflect.TypeOf((*MockDB)(nil).GetSegmentRsvsFromIFPair), arg0, arg1, arg2)
+}
+
+// GetSegmentRsvsFromSrcDstIA mocks base method
+func (m *MockDB) GetSegmentRsvsFromSrcDstIA(arg0 context.Context, arg1, arg2 addr.IA) ([]*segment.Reservation, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetSegmentRsvsFromSrcDstIA", arg0, arg1, arg2)
+	ret0, _ := ret[0].([]*segment.Reservation)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetSegmentRsvsFromSrcDstIA indicates an expected call of GetSegmentRsvsFromSrcDstIA
+func (mr *MockDBMockRecorder) GetSegmentRsvsFromSrcDstIA(arg0, arg1, arg2 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSegmentRsvsFromSrcDstIA", reflect.TypeOf((*MockDB)(nil).GetSegmentRsvsFromSrcDstIA), arg0, arg1, arg2)
+}
+
+// NewSegmentRsv mocks base method
+func (m *MockDB) NewSegmentRsv(arg0 context.Context, arg1 *segment.Reservation) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "NewSegmentRsv", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// NewSegmentRsv indicates an expected call of NewSegmentRsv
+func (mr *MockDBMockRecorder) NewSegmentRsv(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewSegmentRsv", reflect.TypeOf((*MockDB)(nil).NewSegmentRsv), arg0, arg1)
+}
+
+// PersistE2ERsv mocks base method
+func (m *MockDB) PersistE2ERsv(arg0 context.Context, arg1 *e2e.Reservation) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "PersistE2ERsv", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// PersistE2ERsv indicates an expected call of PersistE2ERsv
+func (mr *MockDBMockRecorder) PersistE2ERsv(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PersistE2ERsv", reflect.TypeOf((*MockDB)(nil).PersistE2ERsv), arg0, arg1)
+}
+
+// PersistSegmentRsv mocks base method
+func (m *MockDB) PersistSegmentRsv(arg0 context.Context, arg1 *segment.Reservation) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "PersistSegmentRsv", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// PersistSegmentRsv indicates an expected call of PersistSegmentRsv
+func (mr *MockDBMockRecorder) PersistSegmentRsv(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PersistSegmentRsv", reflect.TypeOf((*MockDB)(nil).PersistSegmentRsv), arg0, arg1)
+}
+
+// SetMaxIdleConns mocks base method
+func (m *MockDB) SetMaxIdleConns(arg0 int) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "SetMaxIdleConns", arg0)
+}
+
+// SetMaxIdleConns indicates an expected call of SetMaxIdleConns
+func (mr *MockDBMockRecorder) SetMaxIdleConns(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMaxIdleConns", reflect.TypeOf((*MockDB)(nil).SetMaxIdleConns), arg0)
+}
+
+// SetMaxOpenConns mocks base method
+func (m *MockDB) SetMaxOpenConns(arg0 int) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "SetMaxOpenConns", arg0)
+}
+
+// SetMaxOpenConns indicates an expected call of SetMaxOpenConns
+func (mr *MockDBMockRecorder) SetMaxOpenConns(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMaxOpenConns", reflect.TypeOf((*MockDB)(nil).SetMaxOpenConns), arg0)
+}
+
+// MockTransaction is a mock of Transaction interface
+type MockTransaction struct {
+	ctrl     *gomock.Controller
+	recorder *MockTransactionMockRecorder
+}
+
+// MockTransactionMockRecorder is the mock recorder for MockTransaction
+type MockTransactionMockRecorder struct {
+	mock *MockTransaction
+}
+
+// NewMockTransaction creates a new mock instance
+func NewMockTransaction(ctrl *gomock.Controller) *MockTransaction {
+	mock := &MockTransaction{ctrl: ctrl}
+	mock.recorder = &MockTransactionMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use
+func (m *MockTransaction) EXPECT() *MockTransactionMockRecorder {
+	return m.recorder
+}
+
+// Commit mocks base method
+func (m *MockTransaction) Commit() error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Commit")
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Commit indicates an expected call of Commit
+func (mr *MockTransactionMockRecorder) Commit() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Commit", reflect.TypeOf((*MockTransaction)(nil).Commit))
+}
+
+// DeleteExpiredIndices mocks base method
+func (m *MockTransaction) DeleteExpiredIndices(arg0 context.Context, arg1 time.Time) (int, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "DeleteExpiredIndices", arg0, arg1)
+	ret0, _ := ret[0].(int)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// DeleteExpiredIndices indicates an expected call of DeleteExpiredIndices
+func (mr *MockTransactionMockRecorder) DeleteExpiredIndices(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteExpiredIndices", reflect.TypeOf((*MockTransaction)(nil).DeleteExpiredIndices), arg0, arg1)
+}
+
+// DeleteSegmentRsv mocks base method
+func (m *MockTransaction) DeleteSegmentRsv(arg0 context.Context, arg1 *reservation.SegmentID) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "DeleteSegmentRsv", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// DeleteSegmentRsv indicates an expected call of DeleteSegmentRsv
+func (mr *MockTransactionMockRecorder) DeleteSegmentRsv(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSegmentRsv", reflect.TypeOf((*MockTransaction)(nil).DeleteSegmentRsv), arg0, arg1)
+}
+
+// GetAllSegmentRsvs mocks base method
+func (m *MockTransaction) GetAllSegmentRsvs(arg0 context.Context) ([]*segment.Reservation, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetAllSegmentRsvs", arg0)
+	ret0, _ := ret[0].([]*segment.Reservation)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetAllSegmentRsvs indicates an expected call of GetAllSegmentRsvs
+func (mr *MockTransactionMockRecorder) GetAllSegmentRsvs(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllSegmentRsvs", reflect.TypeOf((*MockTransaction)(nil).GetAllSegmentRsvs), arg0)
+}
+
+// GetE2ERsvFromID mocks base method
+func (m *MockTransaction) GetE2ERsvFromID(arg0 context.Context, arg1 *reservation.E2EID) (*e2e.Reservation, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetE2ERsvFromID", arg0, arg1)
+	ret0, _ := ret[0].(*e2e.Reservation)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetE2ERsvFromID indicates an expected call of GetE2ERsvFromID
+func (mr *MockTransactionMockRecorder) GetE2ERsvFromID(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetE2ERsvFromID", reflect.TypeOf((*MockTransaction)(nil).GetE2ERsvFromID), arg0, arg1)
+}
+
+// GetE2ERsvsOnSegRsv mocks base method
+func (m *MockTransaction) GetE2ERsvsOnSegRsv(arg0 context.Context, arg1 *reservation.SegmentID) ([]*e2e.Reservation, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetE2ERsvsOnSegRsv", arg0, arg1)
+	ret0, _ := ret[0].([]*e2e.Reservation)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetE2ERsvsOnSegRsv indicates an expected call of GetE2ERsvsOnSegRsv
+func (mr *MockTransactionMockRecorder) GetE2ERsvsOnSegRsv(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetE2ERsvsOnSegRsv", reflect.TypeOf((*MockTransaction)(nil).GetE2ERsvsOnSegRsv), arg0, arg1)
+}
+
+// GetSegmentRsvFromID mocks base method
+func (m *MockTransaction) GetSegmentRsvFromID(arg0 context.Context, arg1 *reservation.SegmentID) (*segment.Reservation, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetSegmentRsvFromID", arg0, arg1)
+	ret0, _ := ret[0].(*segment.Reservation)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetSegmentRsvFromID indicates an expected call of GetSegmentRsvFromID
+func (mr *MockTransactionMockRecorder) GetSegmentRsvFromID(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSegmentRsvFromID", reflect.TypeOf((*MockTransaction)(nil).GetSegmentRsvFromID), arg0, arg1)
+}
+
+// GetSegmentRsvFromPath mocks base method
+func (m *MockTransaction) GetSegmentRsvFromPath(arg0 context.Context, arg1 segment.Path) (*segment.Reservation, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetSegmentRsvFromPath", arg0, arg1)
+	ret0, _ := ret[0].(*segment.Reservation)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetSegmentRsvFromPath indicates an expected call of GetSegmentRsvFromPath
+func (mr *MockTransactionMockRecorder) GetSegmentRsvFromPath(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSegmentRsvFromPath", reflect.TypeOf((*MockTransaction)(nil).GetSegmentRsvFromPath), arg0, arg1)
+}
+
+// GetSegmentRsvsFromIFPair mocks base method
+func (m *MockTransaction) GetSegmentRsvsFromIFPair(arg0 context.Context, arg1, arg2 *common.IFIDType) ([]*segment.Reservation, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetSegmentRsvsFromIFPair", arg0, arg1, arg2)
+	ret0, _ := ret[0].([]*segment.Reservation)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetSegmentRsvsFromIFPair indicates an expected call of GetSegmentRsvsFromIFPair
+func (mr *MockTransactionMockRecorder) GetSegmentRsvsFromIFPair(arg0, arg1, arg2 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSegmentRsvsFromIFPair", reflect.TypeOf((*MockTransaction)(nil).GetSegmentRsvsFromIFPair), arg0, arg1, arg2)
+}
+
+// GetSegmentRsvsFromSrcDstIA mocks base method
+func (m *MockTransaction) GetSegmentRsvsFromSrcDstIA(arg0 context.Context, arg1, arg2 addr.IA) ([]*segment.Reservation, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetSegmentRsvsFromSrcDstIA", arg0, arg1, arg2)
+	ret0, _ := ret[0].([]*segment.Reservation)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetSegmentRsvsFromSrcDstIA indicates an expected call of GetSegmentRsvsFromSrcDstIA
+func (mr *MockTransactionMockRecorder) GetSegmentRsvsFromSrcDstIA(arg0, arg1, arg2 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSegmentRsvsFromSrcDstIA", reflect.TypeOf((*MockTransaction)(nil).GetSegmentRsvsFromSrcDstIA), arg0, arg1, arg2)
+}
+
+// NewSegmentRsv mocks base method
+func (m *MockTransaction) NewSegmentRsv(arg0 context.Context, arg1 *segment.Reservation) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "NewSegmentRsv", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// NewSegmentRsv indicates an expected call of NewSegmentRsv
+func (mr *MockTransactionMockRecorder) NewSegmentRsv(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewSegmentRsv", reflect.TypeOf((*MockTransaction)(nil).NewSegmentRsv), arg0, arg1)
+}
+
+// PersistE2ERsv mocks base method
+func (m *MockTransaction) PersistE2ERsv(arg0 context.Context, arg1 *e2e.Reservation) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "PersistE2ERsv", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// PersistE2ERsv indicates an expected call of PersistE2ERsv
+func (mr *MockTransactionMockRecorder) PersistE2ERsv(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PersistE2ERsv", reflect.TypeOf((*MockTransaction)(nil).PersistE2ERsv), arg0, arg1)
+}
+
+// PersistSegmentRsv mocks base method
+func (m *MockTransaction) PersistSegmentRsv(arg0 context.Context, arg1 *segment.Reservation) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "PersistSegmentRsv", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// PersistSegmentRsv indicates an expected call of PersistSegmentRsv
+func (mr *MockTransactionMockRecorder) PersistSegmentRsv(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PersistSegmentRsv", reflect.TypeOf((*MockTransaction)(nil).PersistSegmentRsv), arg0, arg1)
+}
+
+// Rollback mocks base method
+func (m *MockTransaction) Rollback() error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Rollback")
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Rollback indicates an expected call of Rollback
+func (mr *MockTransactionMockRecorder) Rollback() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Rollback", reflect.TypeOf((*MockTransaction)(nil).Rollback))
+}
diff --git a/go/cs/reservationstorage/store.go b/go/cs/reservationstorage/store.go
index 76beccde4..886593f26 100644
--- a/go/cs/reservationstorage/store.go
+++ b/go/cs/reservationstorage/store.go
@@ -17,17 +17,30 @@ package reservationstorage
 import (
 	"context"
 
+	base "github.com/scionproto/scion/go/cs/reservation"
 	"github.com/scionproto/scion/go/cs/reservation/e2e"
 	sgt "github.com/scionproto/scion/go/cs/reservation/segment"
 	rsv "github.com/scionproto/scion/go/lib/colibri/reservation"
+	"github.com/scionproto/scion/go/lib/infra/modules/cleaner"
 )
 
 // Store is the interface to interact with the reservation store.
 type Store interface {
-	AdmitSegmentReservation(ctx context.Context, req sgt.SetupReq) error
+	AdmitSegmentReservation(ctx context.Context, req *sgt.SetupReq) (base.MessageWithPath, error)
 	ConfirmSegmentReservation(ctx context.Context, id rsv.SegmentID, idx rsv.IndexNumber) error
 	CleanupSegmentReservation(ctx context.Context, id rsv.SegmentID, idx rsv.IndexNumber) error
 	TearDownSegmentReservation(ctx context.Context, id rsv.SegmentID, idx rsv.IndexNumber) error
 	AdmitE2EReservation(ctx context.Context, req e2e.SetupReq) error
 	CleanupE2EReservation(ctx context.Context, id rsv.E2EID, idx rsv.IndexNumber) error
+
+	DeleteExpiredIndices(ctx context.Context) (int, error)
+}
+
+// TODO(juagargi) there is a number of functions missing: all regarding responses.
+
+// NewIndexCleaner creates a cleaner removing expired indices and reservations.
+func NewIndexCleaner(s Store) *cleaner.Cleaner {
+	return cleaner.New(func(ctx context.Context) (int, error) {
+		return s.DeleteExpiredIndices(ctx)
+	}, "colibri")
 }
diff --git a/go/cs/reservationstore/BUILD.bazel b/go/cs/reservationstore/BUILD.bazel
new file mode 100644
index 000000000..d4e233a7a
--- /dev/null
+++ b/go/cs/reservationstore/BUILD.bazel
@@ -0,0 +1,25 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_test(
+    name = "go_default_test",
+    srcs = ["store_test.go"],
+    embed = [":go_default_library"],
+    deps = ["//go/cs/reservationstorage:go_default_library"],
+)
+
+go_library(
+    name = "go_default_library",
+    srcs = ["store.go"],
+    importpath = "github.com/scionproto/scion/go/cs/reservationstore",
+    visibility = ["//visibility:public"],
+    deps = [
+        "//go/cs/reservation:go_default_library",
+        "//go/cs/reservation/e2e:go_default_library",
+        "//go/cs/reservation/segment:go_default_library",
+        "//go/cs/reservation/segment/admission:go_default_library",
+        "//go/cs/reservationstorage:go_default_library",
+        "//go/cs/reservationstorage/backend:go_default_library",
+        "//go/lib/colibri/reservation:go_default_library",
+        "//go/lib/serrors:go_default_library",
+    ],
+)
diff --git a/go/cs/reservationstore/store.go b/go/cs/reservationstore/store.go
new file mode 100644
index 000000000..8a856b915
--- /dev/null
+++ b/go/cs/reservationstore/store.go
@@ -0,0 +1,183 @@
+// Copyright 2020 ETH Zurich, Anapaya Systems
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package reservationstore
+
+import (
+	"context"
+	"time"
+
+	base "github.com/scionproto/scion/go/cs/reservation"
+	"github.com/scionproto/scion/go/cs/reservation/e2e"
+	"github.com/scionproto/scion/go/cs/reservation/segment"
+	"github.com/scionproto/scion/go/cs/reservation/segment/admission"
+	"github.com/scionproto/scion/go/cs/reservationstorage"
+	"github.com/scionproto/scion/go/cs/reservationstorage/backend"
+	"github.com/scionproto/scion/go/lib/colibri/reservation"
+	"github.com/scionproto/scion/go/lib/serrors"
+)
+
+// Store is the reservation store.
+type Store struct {
+	db       backend.DB         // aka reservation map
+	admitter admission.Admitter // the chosen admission entity
+}
+
+var _ reservationstorage.Store = (*Store)(nil)
+
+// NewStore creates a new reservation store.
+func NewStore(db backend.DB, admitter admission.Admitter) *Store {
+	return &Store{
+		db:       db,
+		admitter: admitter,
+	}
+}
+
+// AdmitSegmentReservation receives a setup/renewal request to admit a segment reservation.
+// It is expected that this AS is not the reservation initiator.
+func (s *Store) AdmitSegmentReservation(ctx context.Context, req *segment.SetupReq) (
+	base.MessageWithPath, error) {
+
+	// validate request:
+	// DRKey authentication of request (will be left undone for later)
+	revPath := req.Path().Copy()
+	if err := revPath.Reverse(); err != nil {
+		return nil, serrors.WrapStr("while admitting a reservation, cannot reverse path", err,
+			"id", req.ID)
+	}
+	revMetadata, err := base.NewRequestMetadata(revPath)
+	if err != nil {
+		return nil, serrors.WrapStr("cannot construct metadata for reservation packet", err)
+	}
+	if req.IndexOfCurrentHop() != len(req.AllocTrail) {
+		return nil, serrors.New("inconsistent number of hops",
+			"len_alloctrail", len(req.AllocTrail), "hf_count", req.IndexOfCurrentHop())
+	}
+	failedResponse := &segment.ResponseSetupFailure{
+		RequestMetadata: *revMetadata,
+		FailedHop:       uint8(len(req.AllocTrail)),
+		FailedSetup:     req,
+	}
+	rsv, err := s.db.GetSegmentRsvFromID(ctx, &req.ID)
+	if err != nil {
+		return failedResponse, serrors.WrapStr("cannot obtain segment reservation", err,
+			"id", req.ID)
+	}
+	tx, err := s.db.BeginTransaction(ctx, nil)
+	if err != nil {
+		return failedResponse, serrors.WrapStr("cannot create transaction", err,
+			"id", req.ID)
+	}
+	defer tx.Rollback()
+
+	var index *segment.Index
+	if rsv != nil {
+		// renewal, ensure index is not used
+		index = rsv.Index(req.InfoField.Idx)
+		if index != nil {
+			return failedResponse, serrors.New("index from setup already in use",
+				"idx", req.InfoField.Idx, "id", req.ID)
+		}
+	} else {
+		// setup, create reservation and an index
+		rsv = segment.NewReservation()
+		rsv.ID = req.ID
+		err = tx.NewSegmentRsv(ctx, rsv)
+		if err != nil {
+			return failedResponse, serrors.WrapStr(
+				"unable to create a new segment reservation in db", err,
+				"id", req.ID)
+		}
+	}
+	req.Reservation = rsv
+	tok := &reservation.Token{InfoField: req.InfoField}
+	idx, err := rsv.NewIndexFromToken(tok, req.MinBW, req.MaxBW)
+	if err != nil {
+		return failedResponse, serrors.WrapStr("cannot create index from token", err,
+			"id", req.ID)
+	}
+	index = rsv.Index(idx)
+
+	// checkpath type compatibility with end properties
+	if err := rsv.PathEndProps.ValidateWithPathType(rsv.PathType); err != nil {
+		return failedResponse, serrors.WrapStr("error validating end props and path type", err,
+			"id", req.ID)
+	}
+	// compute admission max BW
+	err = s.admitter.AdmitRsv(ctx, req)
+	if err != nil {
+		return failedResponse, serrors.WrapStr("not admitted", err)
+	}
+	// admitted; the request contains already the value inside the "allocation beads" of the rsv
+	index.AllocBW = req.AllocTrail[len(req.AllocTrail)-1].AllocBW
+	err = tx.PersistSegmentRsv(ctx, rsv)
+	if err != nil {
+		return failedResponse, serrors.WrapStr("cannot persist segment reservation", err,
+			"id", req.ID)
+	}
+	if err := tx.Commit(); err != nil {
+		return failedResponse, serrors.WrapStr("cannot commit transaction", err,
+			"id", req.ID)
+	}
+	var msg base.MessageWithPath
+	if req.IsLastAS() {
+		// TODO(juagargi) update token here
+		msg = &segment.ResponseSetupSuccess{
+			RequestMetadata: *revMetadata,
+			Token:           *index.Token,
+		}
+	} else {
+		msg = req
+	}
+	// TODO(juagargi) refactor function
+	return msg, nil
+}
+
+// ConfirmSegmentReservation changes the state of an index from temporary to confirmed.
+func (s *Store) ConfirmSegmentReservation(ctx context.Context, id reservation.SegmentID,
+	idx reservation.IndexNumber) error {
+
+	return nil
+}
+
+// CleanupSegmentReservation deletes an index from a segment reservation.
+func (s *Store) CleanupSegmentReservation(ctx context.Context, id reservation.SegmentID,
+	idx reservation.IndexNumber) error {
+
+	return nil
+}
+
+// TearDownSegmentReservation removes a whole segment reservation.
+func (s *Store) TearDownSegmentReservation(ctx context.Context, id reservation.SegmentID,
+	idx reservation.IndexNumber) error {
+
+	return nil
+}
+
+// AdmitE2EReservation will atempt to admit an e2e reservation.
+func (s *Store) AdmitE2EReservation(ctx context.Context, req e2e.SetupReq) error {
+	return nil
+}
+
+// CleanupE2EReservation will remove an index from an e2e reservation.
+func (s *Store) CleanupE2EReservation(ctx context.Context, id reservation.E2EID,
+	idx reservation.IndexNumber) error {
+
+	return nil
+}
+
+// DeleteExpiredIndices will just call the DB's method to delete the expired indices.
+func (s *Store) DeleteExpiredIndices(ctx context.Context) (int, error) {
+	return s.db.DeleteExpiredIndices(ctx, time.Now())
+}
diff --git a/go/lib/colibri/reservation/types.go b/go/lib/colibri/reservation/types.go
index a9cc0b306..325c378cb 100644
--- a/go/lib/colibri/reservation/types.go
+++ b/go/lib/colibri/reservation/types.go
@@ -16,7 +16,10 @@ package reservation
 
 import (
 	"encoding/binary"
+	"encoding/hex"
+	"fmt"
 	"io"
+	"math"
 	"time"
 
 	"github.com/scionproto/scion/go/lib/addr"
@@ -79,6 +82,10 @@ func (id *SegmentID) Read(raw []byte) (int, error) {
 	return SegmentIDLen, nil
 }
 
+func (id *SegmentID) String() string {
+	return fmt.Sprintf("%s-%x", id.ASID, id.Suffix)
+}
+
 // E2EID identifies a COLIBRI E2E reservation. The suffix is different for each
 // reservation for any given AS.
 type E2EID struct {
@@ -146,9 +153,20 @@ func (t Tick) ToTime() time.Time {
 	return util.SecsToTime(uint32(t) * 4)
 }
 
-// BWCls is the bandwidth class. bandwidth = 16 * sqrt(2^(BWCls - 1)). 0 <= bwcls <= 63 .
+// BWCls is the bandwidth class. bandwidth = 16 * sqrt(2^(BWCls - 1)). 0 <= bwcls <= 63 kbps.
 type BWCls uint8
 
+// BWClsFromBW constructs a BWCls from the bandwidth. Given that
+// bandwidth = 16 * sqrt(2^(BWCls - 1))
+// where bandwidth is kbps. We then have
+// BWCls = 2 * log2( bandwidth/16 ) + 1
+// The value of BWCls will be the ceiling of the previous expression.
+func BWClsFromBW(bwKbps uint64) BWCls {
+	cls := 2*math.Log2(float64(bwKbps)/16) + 1
+	cls = math.Min(cls, 63)
+	return BWCls(math.Ceil(cls))
+}
+
 // Validate will return an error for invalid values.
 func (b BWCls) Validate() error {
 	if b > 63 {
@@ -157,6 +175,27 @@ func (b BWCls) Validate() error {
 	return nil
 }
 
+// ToKbps returns the kilobits per second this BWCls represents.
+func (b BWCls) ToKbps() uint64 {
+	return uint64(16 * math.Sqrt(math.Pow(2, float64(b)-1)))
+}
+
+// MaxBWCls returns the maximum of two BWCls.
+func MaxBWCls(a, b BWCls) BWCls {
+	if a > b {
+		return a
+	}
+	return b
+}
+
+// MinBWCls returns the minimum of two BWCls.
+func MinBWCls(a, b BWCls) BWCls {
+	if a < b {
+		return a
+	}
+	return b
+}
+
 // SplitCls is the traffic split parameter. split = sqrt(2^c). The split divides the bandwidth
 // in control traffic (BW * split) and end to end traffic (BW * (1-s)). 0 <= splitCls <= 256 .
 type SplitCls uint8
@@ -294,6 +333,16 @@ func (f *InfoField) Read(b []byte) (int, error) {
 	return 8, nil
 }
 
+// ToRaw returns the serial representation of the InfoField.
+func (f *InfoField) ToRaw() []byte {
+	var buff []byte = nil
+	if f != nil {
+		buff = make([]byte, InfoFieldLen)
+		f.Read(buff) // safely ignore errors as they can only come from buffer size
+	}
+	return buff
+}
+
 // PathEndProps represent the zero or more properties a COLIBRI path can have at both ends.
 type PathEndProps uint8
 
@@ -318,6 +367,43 @@ func (pep PathEndProps) Validate() error {
 	return nil
 }
 
+// ValidateWithPathType checks the validity of the properties when in a path of the specified type.
+func (pep PathEndProps) ValidateWithPathType(pt PathType) error {
+	if err := pep.Validate(); err != nil {
+		return err
+	}
+	var invalid bool
+	s := pep & 0xF0
+	e := pep & 0x0F
+	switch pt {
+	case CorePath:
+		if s == 0 {
+			invalid = true
+		}
+	case UpPath:
+		if s != StartLocal {
+			invalid = true
+		}
+	case DownPath:
+		if e != EndLocal {
+			invalid = true
+		}
+	case PeeringUpPath:
+		if s != StartLocal || e == 0 {
+			invalid = true
+		}
+	case PeeringDownPath:
+		if e != EndLocal || s == 0 {
+			invalid = true
+		}
+	}
+	if invalid {
+		return serrors.New("invalid combination of path end properties and path type",
+			"end_properties", hex.EncodeToString([]byte{byte(pep)}), "path_type", pt)
+	}
+	return nil
+}
+
 func NewPathEndProps(startLocal, startTransfer, endLocal, endTransfer bool) PathEndProps {
 	var props PathEndProps
 	if startLocal {
@@ -338,8 +424,24 @@ func NewPathEndProps(startLocal, startTransfer, endLocal, endTransfer bool) Path
 // AllocationBead represents an allocation resolved in an AS for a given reservation.
 // It is used in an array to represent the allocation trail that happened for a reservation.
 type AllocationBead struct {
-	AllocBW uint8
-	MaxBW   uint8
+	AllocBW BWCls
+	MaxBW   BWCls
+}
+
+type AllocationBeads []AllocationBead
+
+// MinMax returns the minimum of all the max BW in the AllocationBeads.
+func (bs AllocationBeads) MinMax() BWCls {
+	if len(bs) == 0 {
+		return 0
+	}
+	var min BWCls = math.MaxUint8
+	for _, b := range bs {
+		if b.MaxBW < min {
+			min = b.MaxBW
+		}
+	}
+	return min
 }
 
 // Token is used in the data plane to forward COLIBRI packets.
@@ -415,8 +517,9 @@ func (t *Token) Read(b []byte) (int, error) {
 
 // ToRaw returns the serial representation of the Token.
 func (t *Token) ToRaw() []byte {
-	buff := make([]byte, t.Len())
+	var buff []byte = nil
 	if t != nil {
+		buff = make([]byte, t.Len())
 		t.Read(buff) // safely ignore errors as they can only come from buffer size
 	}
 	return buff
diff --git a/go/lib/ctrl/colibri_mgmt/segment_setup.go b/go/lib/ctrl/colibri_mgmt/segment_setup.go
index 7121d30a7..c91471bce 100644
--- a/go/lib/ctrl/colibri_mgmt/segment_setup.go
+++ b/go/lib/ctrl/colibri_mgmt/segment_setup.go
@@ -24,7 +24,8 @@ type SegmentSetup struct {
 	SplitCls        uint8
 	StartProps      PathEndProps
 	EndProps        PathEndProps
-	AllocationTrail []*AllocationBeads
+	InfoField       []byte
+	AllocationTrail []*AllocationBead
 }
 
 func (s *SegmentSetup) ProtoId() proto.ProtoIdType {
@@ -50,11 +51,11 @@ func (pep *PathEndProps) ProtoId() proto.ProtoIdType {
 	return proto.PathEndProps_TypeID
 }
 
-type AllocationBeads struct {
+type AllocationBead struct {
 	AllocBW uint8
 	MaxBW   uint8
 }
 
-func (ab *AllocationBeads) ProtoId() proto.ProtoIdType {
-	return proto.AllocationBeads_TypeID
+func (ab *AllocationBead) ProtoId() proto.ProtoIdType {
+	return proto.AllocationBead_TypeID
 }
diff --git a/go/proto/colibri.capnp.go b/go/proto/colibri.capnp.go
index 96deff69a..390c0d934 100644
--- a/go/proto/colibri.capnp.go
+++ b/go/proto/colibri.capnp.go
@@ -295,73 +295,71 @@ func (p PathEndProps_Promise) Struct() (PathEndProps, error) {
 	return PathEndProps{s}, err
 }
 
-type AllocationBeads struct{ capnp.Struct }
+type AllocationBead struct{ capnp.Struct }
 
-// AllocationBeads_TypeID is the unique identifier for the type AllocationBeads.
-const AllocationBeads_TypeID = 0xd58bfb363200f142
+// AllocationBead_TypeID is the unique identifier for the type AllocationBead.
+const AllocationBead_TypeID = 0xc8ba760a91de682a
 
-func NewAllocationBeads(s *capnp.Segment) (AllocationBeads, error) {
+func NewAllocationBead(s *capnp.Segment) (AllocationBead, error) {
 	st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 0})
-	return AllocationBeads{st}, err
+	return AllocationBead{st}, err
 }
 
-func NewRootAllocationBeads(s *capnp.Segment) (AllocationBeads, error) {
+func NewRootAllocationBead(s *capnp.Segment) (AllocationBead, error) {
 	st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 0})
-	return AllocationBeads{st}, err
+	return AllocationBead{st}, err
 }
 
-func ReadRootAllocationBeads(msg *capnp.Message) (AllocationBeads, error) {
+func ReadRootAllocationBead(msg *capnp.Message) (AllocationBead, error) {
 	root, err := msg.RootPtr()
-	return AllocationBeads{root.Struct()}, err
+	return AllocationBead{root.Struct()}, err
 }
 
-func (s AllocationBeads) String() string {
-	str, _ := text.Marshal(0xd58bfb363200f142, s.Struct)
+func (s AllocationBead) String() string {
+	str, _ := text.Marshal(0xc8ba760a91de682a, s.Struct)
 	return str
 }
 
-func (s AllocationBeads) AllocBW() uint8 {
+func (s AllocationBead) AllocBW() uint8 {
 	return s.Struct.Uint8(0)
 }
 
-func (s AllocationBeads) SetAllocBW(v uint8) {
+func (s AllocationBead) SetAllocBW(v uint8) {
 	s.Struct.SetUint8(0, v)
 }
 
-func (s AllocationBeads) MaxBW() uint8 {
+func (s AllocationBead) MaxBW() uint8 {
 	return s.Struct.Uint8(1)
 }
 
-func (s AllocationBeads) SetMaxBW(v uint8) {
+func (s AllocationBead) SetMaxBW(v uint8) {
 	s.Struct.SetUint8(1, v)
 }
 
-// AllocationBeads_List is a list of AllocationBeads.
-type AllocationBeads_List struct{ capnp.List }
+// AllocationBead_List is a list of AllocationBead.
+type AllocationBead_List struct{ capnp.List }
 
-// NewAllocationBeads creates a new list of AllocationBeads.
-func NewAllocationBeads_List(s *capnp.Segment, sz int32) (AllocationBeads_List, error) {
+// NewAllocationBead creates a new list of AllocationBead.
+func NewAllocationBead_List(s *capnp.Segment, sz int32) (AllocationBead_List, error) {
 	l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 8, PointerCount: 0}, sz)
-	return AllocationBeads_List{l}, err
+	return AllocationBead_List{l}, err
 }
 
-func (s AllocationBeads_List) At(i int) AllocationBeads { return AllocationBeads{s.List.Struct(i)} }
+func (s AllocationBead_List) At(i int) AllocationBead { return AllocationBead{s.List.Struct(i)} }
 
-func (s AllocationBeads_List) Set(i int, v AllocationBeads) error {
-	return s.List.SetStruct(i, v.Struct)
-}
+func (s AllocationBead_List) Set(i int, v AllocationBead) error { return s.List.SetStruct(i, v.Struct) }
 
-func (s AllocationBeads_List) String() string {
-	str, _ := text.MarshalList(0xd58bfb363200f142, s.List)
+func (s AllocationBead_List) String() string {
+	str, _ := text.MarshalList(0xc8ba760a91de682a, s.List)
 	return str
 }
 
-// AllocationBeads_Promise is a wrapper for a AllocationBeads promised by a client call.
-type AllocationBeads_Promise struct{ *capnp.Pipeline }
+// AllocationBead_Promise is a wrapper for a AllocationBead promised by a client call.
+type AllocationBead_Promise struct{ *capnp.Pipeline }
 
-func (p AllocationBeads_Promise) Struct() (AllocationBeads, error) {
+func (p AllocationBead_Promise) Struct() (AllocationBead, error) {
 	s, err := p.Pipeline.Struct()
-	return AllocationBeads{s}, err
+	return AllocationBead{s}, err
 }
 
 type SegmentSetupReqData struct{ capnp.Struct }
@@ -370,12 +368,12 @@ type SegmentSetupReqData struct{ capnp.Struct }
 const SegmentSetupReqData_TypeID = 0xa26d74bea4eb287e
 
 func NewSegmentSetupReqData(s *capnp.Segment) (SegmentSetupReqData, error) {
-	st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 3})
+	st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 4})
 	return SegmentSetupReqData{st}, err
 }
 
 func NewRootSegmentSetupReqData(s *capnp.Segment) (SegmentSetupReqData, error) {
-	st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 3})
+	st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 4})
 	return SegmentSetupReqData{st}, err
 }
 
@@ -463,28 +461,42 @@ func (s SegmentSetupReqData) NewEndProps() (PathEndProps, error) {
 	return ss, err
 }
 
-func (s SegmentSetupReqData) AllocationTrail() (AllocationBeads_List, error) {
+func (s SegmentSetupReqData) InfoField() ([]byte, error) {
 	p, err := s.Struct.Ptr(2)
-	return AllocationBeads_List{List: p.List()}, err
+	return []byte(p.Data()), err
 }
 
-func (s SegmentSetupReqData) HasAllocationTrail() bool {
+func (s SegmentSetupReqData) HasInfoField() bool {
 	p, err := s.Struct.Ptr(2)
 	return p.IsValid() || err != nil
 }
 
-func (s SegmentSetupReqData) SetAllocationTrail(v AllocationBeads_List) error {
-	return s.Struct.SetPtr(2, v.List.ToPtr())
+func (s SegmentSetupReqData) SetInfoField(v []byte) error {
+	return s.Struct.SetData(2, v)
+}
+
+func (s SegmentSetupReqData) AllocationTrail() (AllocationBead_List, error) {
+	p, err := s.Struct.Ptr(3)
+	return AllocationBead_List{List: p.List()}, err
+}
+
+func (s SegmentSetupReqData) HasAllocationTrail() bool {
+	p, err := s.Struct.Ptr(3)
+	return p.IsValid() || err != nil
+}
+
+func (s SegmentSetupReqData) SetAllocationTrail(v AllocationBead_List) error {
+	return s.Struct.SetPtr(3, v.List.ToPtr())
 }
 
 // NewAllocationTrail sets the allocationTrail field to a newly
-// allocated AllocationBeads_List, preferring placement in s's segment.
-func (s SegmentSetupReqData) NewAllocationTrail(n int32) (AllocationBeads_List, error) {
-	l, err := NewAllocationBeads_List(s.Struct.Segment(), n)
+// allocated AllocationBead_List, preferring placement in s's segment.
+func (s SegmentSetupReqData) NewAllocationTrail(n int32) (AllocationBead_List, error) {
+	l, err := NewAllocationBead_List(s.Struct.Segment(), n)
 	if err != nil {
-		return AllocationBeads_List{}, err
+		return AllocationBead_List{}, err
 	}
-	err = s.Struct.SetPtr(2, l.List.ToPtr())
+	err = s.Struct.SetPtr(3, l.List.ToPtr())
 	return l, err
 }
 
@@ -493,7 +505,7 @@ type SegmentSetupReqData_List struct{ capnp.List }
 
 // NewSegmentSetupReqData creates a new list of SegmentSetupReqData.
 func NewSegmentSetupReqData_List(s *capnp.Segment, sz int32) (SegmentSetupReqData_List, error) {
-	l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 8, PointerCount: 3}, sz)
+	l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 8, PointerCount: 4}, sz)
 	return SegmentSetupReqData_List{l}, err
 }
 
@@ -1276,12 +1288,12 @@ type E2ECleanupData struct{ capnp.Struct }
 const E2ECleanupData_TypeID = 0x96ec60724ebd66d7
 
 func NewE2ECleanupData(s *capnp.Segment) (E2ECleanupData, error) {
-	st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
+	st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 1})
 	return E2ECleanupData{st}, err
 }
 
 func NewRootE2ECleanupData(s *capnp.Segment) (E2ECleanupData, error) {
-	st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
+	st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 1})
 	return E2ECleanupData{st}, err
 }
 
@@ -1320,12 +1332,20 @@ func (s E2ECleanupData) NewReservationID() (E2EReservationID, error) {
 	return ss, err
 }
 
+func (s E2ECleanupData) Index() uint8 {
+	return s.Struct.Uint8(0)
+}
+
+func (s E2ECleanupData) SetIndex(v uint8) {
+	s.Struct.SetUint8(0, v)
+}
+
 // E2ECleanupData_List is a list of E2ECleanupData.
 type E2ECleanupData_List struct{ capnp.List }
 
 // NewE2ECleanupData creates a new list of E2ECleanupData.
 func NewE2ECleanupData_List(s *capnp.Segment, sz int32) (E2ECleanupData_List, error) {
-	l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz)
+	l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 8, PointerCount: 1}, sz)
 	return E2ECleanupData_List{l}, err
 }
 
@@ -2480,126 +2500,128 @@ func (p ColibriRequestPayload_Promise) Response() Response_Promise {
 	return Response_Promise{Pipeline: p.Pipeline.GetPipeline(0)}
 }
 
-const schema_a3bf9fed859570f0 = "x\xda\xd4Wm\x8cTW\xf9\x7f\x9es\xe6maw" +
-	"g\x86;M\xf9\xf3O\\KJ\xb3\x10\x0at\x97\xa8" +
-	" \xed\xd4\xdd\x1de+\xd593\xbb)E6\xe5\xb2" +
-	"s\xb6\\\x9d\xb9w\xe6\xde\xbb]j\x8aT\xc4D\x82" +
-	"\x8amZ\xad\x1f\xacPi,\xbaI\x8b\xa9Ii\xa8" +
-	"o)\x81\xbe\x18c\xacV\x1b\x8d\x1am\x9aX1\x85" +
-	"\xa4\xd4-\x96k\x9e33\xf7\xce\xab\xcb\x07\xbf\xf8m" +
-	"\xf7<\xbfy\xces\x9f\xdf\xefy9\x1b\xfa\xc3\xb7\xb2" +
-	"\x9b\xc2G\"\x00b[8\xe2\x1d\\\xd8\xb6\xf3\xfb?" +
-	"\xfb\xcda\x10\xfd\x88\xde[\xe5\x87\xbfx\xfe\xdb?\xfe" +
-	"\x0e\x84\xa2\x00\xc37\xf25\xa8\xdd\xcc\xa3\x00\xda&\x9e" +
-	"\x06\xf4~x\xe5\x8d\xdf^<\xf7\x8f\xafB\xb2\xbf\x01" +
-	"\x1bf\x04\x9e\xe2+P+)\xb0\xc1\xe7\x00\xbdWg" +
-	"\x9e\xfb\x84\xbd\xeb\xef_o\x01#!\xce\xf0\xbfi\xbf" +
-	"T\xd8\x9f+\xc7w\xad\xdc\xfd\x97[^\xba\xf4\x0d\x10" +
-	"\xff\x8f\xe8\xfd\xc2\x9d\x1a\xfe\xc2\xce\xfc\x9bp\x0d\x8f\"" +
-	"\xc0\xf0\x05\xbe\x02\x01\xb5\x05\x05\xfd\xdc\xe0\x9b\xc7\x7f\xe4" +
-	"\x96\x1ek\x098L\xde\x86\xff/\xb4\x0c\xb5\xd5*\xf8" +
-	"U\xa1;\x10\xd0\xdbsf\xe9\xa7\xce\x7f\xfc\xc3\x8f7" +
-	"G\xa1\x10\x0f\x84\xd7\xa0v,\x1c\x05\xee=;\xf8\xd7" +
-	"\xc37?\x99?\xd1\xe9\xc3\xf6\x85W\xa2\xf6\x950\x05" +
-	"{(L\x1f\xf6\xf8\xfaug'\xc4\xc6\xef\xb5D\x90" +
-	"\xc1(\x07\x18~=\xbc\x0c\xb5\xb7\x15\xfcB\xf8I@" +
-	"\xef\x86?L\x1dY\xe6\xcd\x9f\xee\x90a\xedP\xe45" +
-	"\xed\xe1\x08\xfd\xf5@\x84\\\xcf/\x0c\xbe\xb3\xf3\x937" +
-	"<\xdf\x89\x8d\x0b\x91\x1d\xa8\x85\xa3\x04\xc6\xe8\x1c\xe0{" +
-	"\xf1\x07\xbf\xfb\xb1\x97+g\xda\xa3@6<\x15]\x89" +
-	"ZI\x81\x8d\xe8\x1b\x80\xde\xb1\x85\x89\xb7\x8e\\\xfb\xb5" +
-	"\xb3\x90\xecg\x01\x1ap8\x13[\x89\xdad\x8c\x90\"" +
-	"\xf6A@o\xcb\x8b\x9b\xde\x97.\xbd\xfe\xab\xd6\x04\x13" +
-	"q\xc3\x93\xb1%\xa8I\x85\xd6cs\x80\x97G.\x0c" +
-	"}\xe0\xf2\xe1_w\xf8\xb6\xe7b\x17\xb5\x17\x14\xf2\x0c" +
-	"!\xbd}\xdb\xcf\xffn\xfd\xfa\xb3\x7fjO\xdbR\x00" +
-	"\xed\xba\x9eg\xb5\xd5=\x8a\xb8\x9e?2@o\xcc>" +
-	"\xf7\xfb\xf9\xbbJ\x7f\xee\x0c\x9f\xec\xfd\x816\xd5K\xf0" +
-	";{-\x82\xfb\x9ai\x81s\"E\xeb\x89\xbf\xa6]" +
-	"\x13\xa7X\x92q\xca\xc6\xa3\x95[V]\xcf_\xba\xd8" +
-	"Yo'\xe3Jo\xa7\xe2D\xdf\xc1\xe5\xd7>z\xe0" +
-	"\xd5\xeb.u\x90\x86f$\xde\xd5f\x13\xf4W%A" +
-	"\x9f8m\x15\x8d\xdd\xb6\xb1\x8eO\xebe\xb3\xbc9/" +
-	"\xef.I\xd3\x9d\x90\xba]\xb0\xe6\xcc\x9ct\xe2c\xba" +
-	"\xabg\x11E\x88\x87\x00B\x08\x90\xec\xcb\x01\x88^\x8e" +
-	"b9CO\xda\xb6e\x8fZ\x05@\x89\x11`\x18\xe9" +
-	"\xea4'\x1di\xdf\xa3\xbb\x86e\xf2\xf11r\x19\xf3" +
-	"]\xae^\x03 \xae\xe7(60L\"\xa6\x90\x0eo" +
-	"\xdc\x0c \x069\x8a\x8d\x0c\xe3\xbac\x14\xb0\x0f\x18\xf6" +
-	"\x01\xa6\x9d\xd9\x99\x19co\xfd_\xff>V\xbd/3" +
-	"\x94\x19-J\xdd\x9c-w\x0a\xden\x08\xde\xae\x85\x04" +
-	"\x03\x86e\x8e\x8fa\"\xc8\x1d &\xda?%3\x94" +
-	"\xc9Kw\xb6L~\xd79\xb3\xd3\xd3\x92;\x0eyO" +
-	"`\x0a\x19\xb9\x1f\x02\x101\x8e\"\xc5p\xc0\xb5>#" +
-	"\xcd\xb6(\x9b\xb3\xa2\xdc\xe5deLwQ\x85\xba\xdc" +
-	"\x0f\xf5\x9b\xe4\xeb!\x8e\xe2hCR\xbeE\x87\x8fp" +
-	"\x14\xc7\x19&\x19\xab\xdez\xec6\x00q\x94\xa3\x98g" +
-	"\x88<\x85\x1c yb\x07\x80x\x82\xa3x\x9aa2" +
-	"\x84)\x0c\x01$O\x12\xf0)\x8e\xe24\xc3d\x98\xa5" +
-	"0\x0c\x90<u\x00@<\xc3Q\xbc\xc2p\xa0d\x98" +
-	"#w\xd4\x99\x1c(\xe9{\x83\xff<\xa7\\4\xdc\xd1" +
-	"\xa2\x03\x00\xc1\x99\xab\xdbn\xd6\xb6\x80\x97\x1dL\x04\xad" +
-	"\xa3\x96?i\x16\xb2\xb6UV?i\xb7\xea\xc5\xa25" +
-	"\xad\xbb\x06Z\xe6\x84\xad\x1bE\xc0~\xc0,GL\xd4" +
-	"\xab\x14\x90\x8e\x16\x95i\xa5\xcetWdQ:U\xe6" +
-	"\xa25I4\x88oh\x11\xf1\x0d8\xf4KL\x04\x9d" +
-	"\xbc\x1a\x7fz\xb7\xeeH%\x1b\x7f\xcct\x96M3\xd7" +
-	"\x8e\xcfu/\x0f\xf5z\x9e\x0a\"CA\xdc\xcaQl" +
-	"c\xd8\x87W\xbcj\x14\xe3#\x00b\x8c\xa3\xc82\xec" +
-	"c\xefyU\xbao'\xecV\x8eb\x82\xe1\xc0\xac\xe9" +
-	"H\x17\"\xfbgt\xa38k\xcb\xf6(\xbb\xc8\xb0V" +
-	",Y\xdd\xdd\x93Q$\xf1\xb2s5y!\x05\xad\xe5" +
-	"(>\xc4p\x80\xd8+\"\x02C\x1a\\\xae\xad\x9b\xce" +
-	"\x8c\xb4\x89\xeb\xfaY\xfd\xb6PS\"\xc6\xcd\x82\xdc;" +
-	"j\x993\x86]R\x0daL\xe7W\xc7\xcaP\x03+" +
-	"\x06y\xf1\xa5\xea\xb8\xba+1\x1e\x8c\x0d@\x8c\xb7S" +
-	"1Z\xfd7'+\xb3\xd2q\xb3\xfa\xbd\xc5\xa8\xa5\x17" +
-	"\xe8\xea\x84\x7f\xb5N\x0dn\x17GQ$.\xbc\x1a\x17" +
-	"\x06\xdd]\xe0(\xca\xc4\xc5\x95\x1a\x17%bh\x0fG" +
-	"\xe12\xec\xe3\xc4\x10\x15_\x85\xb2T\xe6(\xeec\xe8" +
-	"\xb9FI:\xae^\x02,c\x0c\x18\xc6 `\xcd\xae" +
-	"\xc6\x81\x89`z\xd4$dK\xa7l\x99\x8e\xacV\x8e" +
-	"?\x8a:\x0b\xac\xa1\xb7\xaa\xdc\xe6\xdd\xa8\xeeJ\x95P" +
-	"\x15er\x84~\x98\xec\xd9\x0c\xb0\xbf,\xcd\x82a\xde" +
-	"\x9d\xd6\xa7]\xe3\x1e\xd9E\xaa\xb5\x06JU\x05\xd0B" +
-	"\xcc\x8a\x80\x98N\xbcp\xa3\xd0^\x11\xcdd\xb5\x8a\xf0" +
-	"#\xb5>`\x99\x03#R/\xb4\xeapd\x11%\xec" +
-	"W}\xa4k\xdb\xaa_\x86\xf5\\\xa5\xab\x99\xa5[6" +
-	"\xfa\x15\xa8M\xe1\x10@~;r\xcc\x17\xb0\xa1\x085" +
-	"\x1d?\x0d\x90\xdfE\x86\"6\xd4\xa1f\xe0g\x01\xf2" +
-	"{\xc8\xe0\x92\x81\xff\xabJ\xbfV\xc1\x07\x01\xf2.\x19" +
-	"\xee'C\xe8\xb2\xa7\xfa\xaf\xb6\x0f\x1f\x03\xc8\xdfO\x86" +
-	"/\x93!\xfc\xae\xa7z\xb0v\x08\x0f\x00\xe4\xbfD\x86" +
-	"\x87\xc8\x10Y\xf0R\x18\xa1-\x0b_\x04\xc8?B\x86" +
-	"\xe3d\x88\xfe\xd3K\xa9\x85\xf4\x98\xba\xfc(\x19\xe6\xc9" +
-	"\x10{\xc7Ka\x0c@;\x81\xb7\x01\xe4\x9f \xc3\xd3" +
-	"d\xe8\xb9\xe4\xa5\xb0\x07@;\x89;\x00\xf2O\x91\xe1" +
-	"4\x19\x96\xbc\xed\xa5p\x09\x80vJ\x19\x9e!\xc3\xf3" +
-	"\xc80\xb94\x91BZY~\xaa<\xfd\x84\xce_\xa6" +
-	"\xf3^\x9e\xc2^\x00\xed\x05\xcc\x01\xe4\xcf\xd1\xf9+\x18" +
-	"\xb4 \xcf\xa9\xb59\x88\xd7\xfa\xa5\xbfw\xd6T[\x03" +
-	"\xe4 -M9\xa7\x17\xbbC&\xb0\xde\xb1\xb1\xbc\x18" +
-	"*'M\x19]\xcc[uRP)\xf9\xef\x87f\xcc" +
-	"8kmJ\xaa\xf0\xfc\xfd\xb6\x19=\x0a\xe9j\x85`" +
-	"\"X?\xeb3oH\xaaN_\xad\\\x7fU\x0b\xac" +
-	"\x14\xf0\x1cp\x15q\x07\xb3\xaa=\xe0\xca\xb7\xff$\xa9" +
-	"O\xcc\xe9iYve\xa1\xb1\xc7R\xe7\x97\x85\xad\x16" +
-	"\xf5\x97\xae\x9a\x1f\xa8\xf6;D\xb16\x90\xfc*%\xf9" +
-	"\xf7\x13\x91k\x9b$\xbfZI~\x90\x0c\x1b\x9b$\x7f" +
-	"\x93R\xdd\x062li\x92\xfc&%\xf9-d\xd8\xda" +
-	"$\xf9\x8c\x92\xfcV2L4I^(\xc9g\xc9\xb0" +
-	"\xb3I\xf2w*\xc9\x07\xf5\xe6K\xbe\xb5\xde|\xc9W" +
-	"\x94P\xcbd\xb8\xafI\xf2\xf7*e\xef%\xc3\xc1&" +
-	"\xc9\x7f^\x19\xfcB\xec.\xe1\x96a\xdaI\xc2] " +
-	"-\x12\xf6\x1fl\x8bH\xb8;.\x90\xb0\xffD\xfc_" +
-	"\x94p\xfb\xb6^[\xa9k+@\xc3\x1c\xb6\xaff\x0e" +
-	"c\xc3c\x9c\xc61\xa31\x8c\x0d/\xa6\xe4\xe4\x08\xb0" +
-	"\xc5w}\x7f&\xab\xbd\xdeq\xea\x1b\xd5\x7f|\x03(" +
-	"\x0c\xb7%\xadr\x09V\x1d\xfe\x99\\\xb0\xb4\xa9yE" +
-	"\x9b\xf7\xedt\xb8\x8d\xa3\xd8^\xdb\xdbi\xf3\x9e\xa4%" +
-	"3[\xfd\xc0\x8e/)\xc3\x9c\xb1>j\xc8\"`\xf0" +
-	"\xf8Q\xc3\xcd\xa9o\xca\x84\xec\xef\x98\xd5\xdaN\x90V" +
-	"K\xc1\x7f\xf3\xbd\xf5\xef\x00\x00\x00\xff\xff\xb3\xde\xb9R"
+const schema_a3bf9fed859570f0 = "x\xda\xd4W}\x8c\\U\x15?\xe7\xde\xf9lww" +
+	"f\xfa\xc6@\xea\xc7h\x03di\xfa\xb9[\xa2\xad\x85" +
+	"\x81\xdd\x1d\xed\xd6\xa2\xf3f\xb6\xb1\xd66\xf4u\xe7." +
+	"\xfbt\xe6\xbd\x99\xf7\xde\xb6\xc5P\x0b\xb5$\x1a\xd4Z" +
+	"\xe2\xd7?\xa2E\x1aE\x9b\x80\x06\x12i\x8a\x82i\xd3" +
+	"\x15J\xb4\x11\x84\x12\xaa\x18m\x88`\x0d\x92XX\x94" +
+	">s\xee\x9byo>\xbb\xfd\xc3\x7f\xfco\xe6\xfe~" +
+	"\xef\xdes\xcf\xf9\x9d\x8f\xbbjy\xf8f\xb6:|0" +
+	"\x02\xa0n\x0aG\xdc\x03s\x9b\xb6\xfd\xf4\xd7\x7f\xb8\x17" +
+	"\xd4\x01D\xf7\x8d\xea\xb7\xee\xb9\xf0\xfd_\xfd\x10BQ" +
+	"\x80\xe1\xe5|)*7\xf2(\x80\xb2\x96g\x01\xdd\xc7" +
+	".\xbd\xfa\xe2\x9b\xb3\xff\xf8:\xa4\x06\x9a\xb8aF\xe4" +
+	"\xed|1*\x15I\xd6\xf9n@\xf7\x85\xa9'>i" +
+	"\xed\xf8\xfb\xb7\xdbv\x0e#QN\xf2\xd7\x943\x92\xfc" +
+	"\xac$\xdf\xb6d\xe7_nz\xe6\xe2w@}/\xa2" +
+	"\xfb[g\xfb\xf0\x97\xb6\x15_\x87\xf7\xf0(\x92!\xa1" +
+	"\xc5\x08\xa8\xdc\x10\"#\xbe8\xf8\xfa\x83\xbft*\x0f" +
+	"\xb4\xef+M\xde\x1cZ\x84\x8a\x90?\xb5\xd0A\x04t" +
+	"\xa7O.\xfc\xec\x85O|\xf4H\xab\xcd\x92\xd1\x1fY" +
+	"\x8a\xca\xfb#Q\xe0\xee\xb1\xc1\xbf\xde{\xe3\xc3\xc5\x87" +
+	"\xba\xddl.\xbc\x04\x95x\x84\x8c\x0dG\xc8\xd8#+" +
+	"W\x9c\x9aP\xd7\xfc\xa4\xcd\x82\x1cF9\x9d\x1bY\x84" +
+	"JM\xd2+\x91\x87\x01\xdd\xeb\xcem?\xb8\xc8=z" +
+	"\xbc\x8b\x8b\x95p\xf4%%\x15\xa5_\xfdQ\xda\xfa\xe8" +
+	"\xdc\xe0[\xdb>u\xdd\x89n\xe1\xa8D\xb7\xa2r\xb7" +
+	"$\xef%\xf2\xbb\x89\xfb~\xf4\xf1\xd3\xb5\x93\x9dV " +
+	"\x1b>\x1b]\x82\xca\xdf$\xf9|\xf4U@\xf7\xf0\xdc" +
+	"\xc4\x1b\x07\xaf\xfa\xc6)H\x0d\xb0\x80\x0d8\xfcTl" +
+	"\x09*gb2\x16\xb1\x0f\x03\xbaK\xa7\xffxh\xc1" +
+	"\xaec\xb3\xdd\xec=\x13{M9'\xb9gcd\xef" +
+	"\xfa\xa7\xd7~ [9\xff\xfbnA\x1e\xbe!\xbe\x00" +
+	"\x95\\\x9c\xd8\xb7\xc4\x89\xbdw\xcb\x85\xb3+W\x9ez" +
+	"\xa5\xd3q\x0b\x01\x94\xef\xc5\x8f)G\x88=|8\xfe" +
+	"'\x06\xe8\x8eY\xb3/\x1f\xbd\xad\xf2\xe7\xee\xf43}" +
+	"?W\xce\xf6\x11\xfd\xf9>\x93\xe8\xbej\xda\xe8\x9c\xc2" +
+	"\xa2\xdc\x93xI9\x94 [\xbe\x96 \x7f\xdc_\xbb" +
+	"\xe9\xdak\xf83o\xf6P\\\xd2S\\\x92\x02x\xe0" +
+	"\xea\xab\xee\xdf\xff\xc2\x87.v\x11\x87r>\xf9\x8e\xf2" +
+	"\xcf$\xfd\xba\x90\xa4+N\x9ae}\xa7\xa5\xaf\xe0\x93" +
+	"Z\xd5\xa8\xae+\x8a\xdb+\xc2p&\x84f\x95\xcc\xdd" +
+	"FA\xd8\x891\xcd\xd1\xf2\x88j\x88\x87\x00B\x08\x90" +
+	"\xea/\x00\xa8}\x1c\xd5\xab\x19\xba\xc2\xb2Lk\xd4," +
+	"\x01\x0a\x8c\x00\xc3H\xcfM\x0b\xc2\x16\xd6.\xcd\xd1M" +
+	"\x83\x8f\x8f\xd1\x961\x7f\xcb\xeb\x97\x02\xa8\xd7pTW" +
+	"1L!\xa6\x91\x16\x97\xaf\x03P\x079\xaak\x18&" +
+	"4[/a?0\xec\x07\xcc\xda3SS\xfa\x9e\xc6" +
+	"_\xff<\xe6\x9d\x97\x1b\xca\x8d\x96\x85f\xccT}\xe3" +
+	"\x9bN\xb2\x82M\x1b\x07\xad\x1e\x02P\x97qT?\xc2" +
+	"\xd0\xb5\xeafBF7\x8d\xf11L\x06\xfe\x04\xc4$" +
+	"`F7JbO\xaf\xcb\xe6\x86rE\xe1\xccT\xe9" +
+	"\xe4\x15\xf6\xcc\xe4\xa4\xe0\xb6M\xceKb\x1a\x19y\x8f" +
+	"\x0e\x8bqT\xd3\x0c3\x8e\xf9yat\xdc\xa3\xd5o" +
+	"r\xbb\x82\xa8\x8di\x0e\xca\xcb\xbc\xcf\xbf\xccc\xb4\xd7" +
+	"#\x1c\xd5\xe3Mn{\x9c\x16\x1f\xe5\xa8>\xc90\xc5" +
+	"\x98w\xea\x13\x1b\x01\xd4\xe3\x1c\xd5Y\x86\xc8\xd3\xc8\x01" +
+	"R'\xb7\x02\xa8'8\xaa\xbfc\x98\x0aa\x1aC\x00" +
+	"\xa9g\x89x\x9a\xa3\xfa\"\xc3T\x98\xa51\x0c\x90z" +
+	"\x9e\"\xfe\x1cG\xf5\x15\x86\xa9\x08Oc\x04 un" +
+	"?\x80\xfa2G\xf5m\x86\x99\x8an\x8c|\xba\xe1\x93" +
+	"LE\xdb\x13\xfcs\xedjYwF\xcb6\x00\x04k" +
+	"\x8ef9y\xcb\x04^\xb51\x19\xd4\x1c\xcf\xc5\xae0" +
+	"Jy\xcb\xac\xcaO:Q\xdd\x982?\xa6\x8b2\xa0" +
+	"/\x09W+\x97\xcdI\xcd\xd1\xd14&,M/\x03" +
+	"\x0e\x00\xe69b2(\x10\x80\xb48\xaf\xe6k\x0d\xd9" +
+	"\xf4d\x96\x85\xed\x059\xda\xa9\xaf\xa1y\x94\x9c\xb1\xe9" +
+	"KL\x06\x8d\xc1\xbbUv\xa7f\x0b\xa97\xbfm\xd5" +
+	"\xaf{YY\xd8\xbe,\xfax\xa8\xcfu\xa5\x1192" +
+	"\xe2f\x8e\xea&\x86\xfdx\xc9\xf5\xac\x18\x1f\x01P\xc7" +
+	"8\xaay\x86\xfd\xec]\xd7S\xc6\xad\xc4\xdd\xc0Q\x9d" +
+	"`\x98\x991l\xe1@d\xdf\x94\xa6\x97g,\xd1i" +
+	"e\x0f\xc5\xd63/\xaf9\xd39\x19:^\xb5\xaf\xc4" +
+	"/\x1b\x83\xc4\xcbP\xfc\xca\x88\xc0\x90\xfa\xa0ci\x86" +
+	"=%,R@c\xadqZ\xa8\xc5\x11\xe3\x94\x8e\xa3" +
+	"\xa61\xa5[\x15Y]\xc64~eQ\x19j\x8aJ" +
+	"KRglGs\x04&\x82.\x04\x88\x89\xceP\x8c" +
+	"z\x7f\x0b\xa26#l'\xaf\xddQ\x8e\x9aZ\x89\x8e" +
+	"N\xfaGk\x94;;8\xaae\x8a\x85[\x8f\x85N" +
+	"g\x978\xaaU\x8a\xc5\xa5z,*\x14\xa1i\x8e\xaa" +
+	"\xc3\xb0\x9fS\x84(Ok\xe4\xa5*G\xf5N\x86\xae" +
+	"\xa3W\x84\xedh\x15\xc0*\xc6\x80a\x0c\x82\xa8Y\x9e" +
+	"\x1d\x98\x0cZQ]B\x96\xb0\xab\xa6a\x0b/\x9f\xfc" +
+	"\xbe\xd6]`M\x85Z\xfa\xb6\xe8D5GH\x87J" +
+	"+S#\xf4a*\xbe\x0e`_U\x18%\xdd\xb8=" +
+	"\xabM:\xfa.\xd1\xae\x87[\xeaIi\x1a\x89\x11\xe1" +
+	"9\xa6)&#\xf3\xc4d\x9f\xcc\xe9\x9ee\xa5{Z" +
+	"\xd4+?e0@\xdb\x81\x8b\x83\x03\xbb\x9d\xc7\xf5R" +
+	"g\xf6\xf5\xa8\xf6\xd8pU\xd6s,\x9d\xb4\xc6O@" +
+	"e;\x0e\x01\x14\xb7 \xc7b\x09\x9brP\xd1\xf0s" +
+	"\x00\xc5\x1d\x04\x94\xb1)\x0d\x15\x1d\xbf\x00P\x9c&\xc0" +
+	"!\x80\xff\xc7\x8b\xbeR\xc3\xfb\x00\x8a\x0e\x01w\x11\x10" +
+	"\xfa\xb7++\xb5\xb2\x17\x1f\x00(\xdeE\xc0W\x09\x08" +
+	"\xbf\xe3\xcaj\xad|\x05\xf7\x03\x14\xbfL\xc07\x09\x88" +
+	"\xcc\xb9\xb2b+\x87\xf0i\x80\xe2w\x09x\x90\x80\xe8" +
+	"\xdbnZ\x8e\xb7\x87\xe5\xe1? \xe0(\x01\xb1\xb7\xdc" +
+	"4\xc6\x00\x94\x87p#@\xf1\xc7\x04<J@\xfc\xa2" +
+	"\x9b\xc68\x80\xf23\xdc\x0aP|\x84\x80\xe3\x04,\xf8" +
+	"\x97\x9b\xc6\x05\x00\xca\xe3\x12\xf8\x05\x01'\x90aja" +
+	"2\x8d4\xfe<%wz\x92\xd6O\xd3z\x1fOc" +
+	"\x1f\x80\xf2\x1b,\x00\x14gi\xfd9\x0c*\x90k\xd7" +
+	"\xab\x1c$\xea\xe5\xd2\x9fb\xeb\xa2\xad\x13\x0a\x90\x15\x86" +
+	"\xd8\xad\x95{S&\xb0Q\xb0\xb1:\x1f\xab \x0c\x11" +
+	"\x9do7\xafQP&\xf9\xcf\x91V\xce8k\xafI" +
+	"2\xef\xfci\xb9\x95=\x0aYO\xb4\x98\x0c\x06\xd4F" +
+	"#\x1c\x12\xb2\xd0{\x89\xeb\x8f}\x01J\x06\xef\x06." +
+	"-\xee\x02\xcbt\x00.\xf7\xf6_8uX\x9b\x9c\x14" +
+	"UG\x94\x9aK,\x15~Q\xda`Ry\xe9\xa9\xf9" +
+	"\x8cW\xee\x10\xd5e\x81\xe4\xaf\x95\x92\xff \x05rY" +
+	"\x8b\xe4\xaf\x97\x92\x1f$`M\x8b\xe4WK\xd5\xad\"" +
+	"`}\x8b\xe4\xd7J\xc9\xaf'`C\x8b\xe4sR\xf2" +
+	"\x1b\x08\x98h\x91\xbc*%\x9f'`[\x8b\xe4?#" +
+	"%\x1f\xe4\x9b/\xf9\xf6|\xf3%_\x93B\xad\x12p" +
+	"g\x8b\xe4\xef\x90\xca\xdeC\xc0\x81\x16\xc9\xdf-\x01?" +
+	"\x11{K\xb8\xad\x97v\x93p\x0fJ\x9b\x84\xfd\xe7\xdf" +
+	"<\x12\xee\xcd\x0b$\xec?8\xff\x1f%\xdc9\xf9\xd7" +
+	"\x87\xef\xfa\x04\xd0\xd4\x86\xad+i\xc3\xd8\xf4\xb4\xa7n" +
+	"\xcc\xa8\x0bc\xd3\xeb+\xb5y\x04\xd8\x15\xbc\x11\x1a-" +
+	"Y\xbe\x00l\xbb1P]\xf6\xb5 9\xdc\x124\xc9" +
+	"%\x99\xd7\xfbs\x85`f\x93M\x92f\xf4[iq" +
+	"\x13GuK}\xc2\xa7\x19}3\xcd\x98y\xef\x82]" +
+	"_e]\xa6\xe6\xac\xec\xa8vcT&\xe6@W\xaf" +
+	"\xd6G\x82\xac\x9c\x09\xfe\x97o\xb7\xff\x06\x00\x00\xff\xff" +
+	"@\x09\xcb\x12"
 
 func init() {
 	schemas.Register(schema_a3bf9fed859570f0,
@@ -2615,8 +2637,8 @@ func init() {
 		0xc4264f5cf728f9ac,
 		0xc571cc47a792000f,
 		0xc790178ff054f9a1,
+		0xc8ba760a91de682a,
 		0xd3e56d3f1e39ca3c,
-		0xd58bfb363200f142,
 		0xe0c72f2fd9ed587d,
 		0xe16d5facdcc87244,
 		0xeb535c83335d74cf,
diff --git a/proto/colibri.capnp b/proto/colibri.capnp
index e00e0d950..54c33d711 100644
--- a/proto/colibri.capnp
+++ b/proto/colibri.capnp
@@ -24,20 +24,21 @@ struct PathEndProps {
     transfer @1 :Bool;  # allow stitching with another reservation
 }
 
-struct AllocationBeads {
+struct AllocationBead {
     allocBW @0 :UInt8;
     maxBW @1 :UInt8;
 }
 
 # Request to setup a segment reservation.
-# Setups travel inside a hop by hop colibri extension and thus have a SegmentReservationID and index externally.
+# Setups travel inside a hop by hop colibri extension and thus have a SegmentReservationID externally.
 struct SegmentSetupReqData {
     minBW @0 :UInt8;
     maxBW @1 :UInt8;
     splitCls @2 :UInt8;
     startProps @3 :PathEndProps;
     endProps @4 :PathEndProps;
-    allocationTrail @5 :List(AllocationBeads);  # updated on each AS along the path
+    infoField @5 :Data; # ExpirationTick, Index, BWCls, RLC, and PathType; expected 8 bytes
+    allocationTrail @6 :List(AllocationBead);  # updated on each AS along the path
 }
 
 # Response to a segment setup request.
@@ -95,9 +96,11 @@ struct E2ESetupData {
     }
 }
 
-# The reservation ID is needed because this request can be for a previously failed setup.
+# The reservation ID and index are needed because this request is for a different reservation than
+# the one referenced in the COLIBRI header.
 struct E2ECleanupData {
     reservationID @0 :E2EReservationID;
+    index @1 :UInt8;
 }
 
 # All possible requests between ASes.
diff --git a/tools/gomocks b/tools/gomocks
index 3fd54d206..0811372c0 100755
--- a/tools/gomocks
+++ b/tools/gomocks
@@ -10,6 +10,7 @@ MOCK_TARGETS = [
     ("go/cs/beaconing",
         "BeaconInserter,BeaconProvider,BeaconSender,RPC,SegmentProvider,SegmentStore"),
     ("go/cs/keepalive", "IfStatePusher,RevDropper"),
+    ("go/cs/reservationstorage/backend", "DB,Transaction"),
     ("go/cs/revocation", "Store"),
     ("go/cs/segutil", "Policy"),
     ("go/hidden_path_srv/internal/registration", "Validator"),


Test patch:
diff --git a/go/cs/reservation/conf/capacities_test.go b/go/cs/reservation/conf/capacities_test.go
new file mode 100644
index 000000000..b34dae84e
--- /dev/null
+++ b/go/cs/reservation/conf/capacities_test.go
@@ -0,0 +1,209 @@
+// Copyright 2020 ETH Zurich, Anapaya Systems
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package conf
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/scionproto/scion/go/lib/common"
+	"github.com/scionproto/scion/go/lib/xtest"
+)
+
+func TestJson(t *testing.T) {
+	// test that this number cannot be represented with float64
+	// (it will be used in one of the test cases)
+	var bigint uint64 = 4418489049307132905
+	require.NotEqual(t, bigint, uint64(float64(bigint)))
+
+	cases := map[string]struct {
+		filename string
+		cap      Capacities
+	}{
+		"using all keys": {
+			filename: "caps1.json",
+			cap: Capacities{c: capacities{
+				CapIn: map[common.IFIDType]uint64{1: 100, 2: 200, 3: 300},
+				CapEg: map[common.IFIDType]uint64{1: 100, 2: 200, 3: 40},
+				In2Eg: map[common.IFIDType]map[common.IFIDType]uint64{
+					1: {2: 10, 3: 20},
+					2: {1: 10, 3: 20},
+					3: {1: 10, 2: 20},
+				},
+			},
+			},
+		},
+		"big num no float": {
+			filename: "caps2.json",
+			cap: Capacities{c: capacities{
+				CapIn: map[common.IFIDType]uint64{1: bigint},
+			},
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		name, tc := name, tc
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			tc.cap.init()
+			buf, err := json.MarshalIndent(tc.cap, "", "  ")
+			buf = append(buf, '\n')
+			require.NoError(t, err)
+			expectedJSON := xtest.MustReadFromFile(t, tc.filename)
+			require.Equal(t, expectedJSON, buf)
+			c2 := Capacities{}
+			err = c2.UnmarshalJSON(expectedJSON)
+			require.NoError(t, err)
+			require.Equal(t, tc.cap, c2)
+		})
+	}
+}
+
+func TestValidation(t *testing.T) {
+	cases := map[string]struct {
+		okay bool
+		cap  Capacities
+	}{
+		"okay": {
+			okay: true,
+			cap: Capacities{c: capacities{
+				CapIn: map[common.IFIDType]uint64{1: 100, 2: 200, 3: 300},
+				CapEg: map[common.IFIDType]uint64{1: 100, 2: 200, 3: 40},
+				In2Eg: map[common.IFIDType]map[common.IFIDType]uint64{
+					1: {2: 10, 3: 20},
+					2: {1: 10, 3: 20},
+					3: {1: 10, 2: 20},
+				},
+			},
+			},
+		},
+		"too much ingress": {
+			okay: false,
+			cap: Capacities{c: capacities{
+				CapIn: map[common.IFIDType]uint64{1: 10},
+				In2Eg: map[common.IFIDType]map[common.IFIDType]uint64{
+					1: {2: 10, 3: 1},
+				},
+			},
+			},
+		},
+		"too much egress": {
+			okay: false,
+			cap: Capacities{c: capacities{
+				CapIn: map[common.IFIDType]uint64{1: 100, 2: 100, 3: 100},
+				CapEg: map[common.IFIDType]uint64{1: 100, 2: 100, 3: 100},
+				In2Eg: map[common.IFIDType]map[common.IFIDType]uint64{
+					1: {2: 90, 3: 1},
+					3: {2: 20, 1: 1},
+				},
+			},
+			},
+		},
+		"ingress to itself (reflection not allowed)": {
+			okay: false,
+			cap: Capacities{c: capacities{
+				CapIn: map[common.IFIDType]uint64{1: 100, 2: 100, 3: 100},
+				CapEg: map[common.IFIDType]uint64{1: 100, 2: 100, 3: 100},
+				In2Eg: map[common.IFIDType]map[common.IFIDType]uint64{
+					1: {1: 10, 3: 1},
+				},
+			},
+			},
+		},
+		"too few ingress capacities": {
+			okay: false,
+			cap: Capacities{c: capacities{
+				CapIn: map[common.IFIDType]uint64{1: 100, 3: 100},
+				CapEg: map[common.IFIDType]uint64{1: 100, 2: 100, 3: 100},
+				In2Eg: map[common.IFIDType]map[common.IFIDType]uint64{
+					1: {2: 0, 3: 0},
+					2: {1: 0, 3: 0},
+					3: {1: 0, 2: 0},
+				},
+			},
+			},
+		},
+		"too few egress capacities": {
+			okay: false,
+			cap: Capacities{c: capacities{
+				CapIn: map[common.IFIDType]uint64{1: 100, 2: 100, 3: 100},
+				CapEg: map[common.IFIDType]uint64{1: 100, 3: 100},
+				In2Eg: map[common.IFIDType]map[common.IFIDType]uint64{
+					1: {2: 0, 3: 0},
+					2: {1: 0, 3: 0},
+					3: {1: 0, 2: 0},
+				},
+			},
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		name, tc := name, tc
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			err := tc.cap.init()
+			if tc.okay {
+				require.NoError(t, err)
+			} else {
+				require.Error(t, err)
+			}
+		})
+	}
+}
+
+func TestCapacities(t *testing.T) {
+	c := &Capacities{c: capacities{
+		CapIn: map[common.IFIDType]uint64{1: 100, 2: 100, 3: 100},
+		CapEg: map[common.IFIDType]uint64{1: 100, 2: 100, 3: 100},
+		In2Eg: map[common.IFIDType]map[common.IFIDType]uint64{
+			1: {2: 12, 3: 13},
+			2: {1: 21, 3: 23},
+			3: {1: 31, 2: 32},
+		},
+	},
+	}
+	copy := cloneCapacities(c) // save a copy of the original
+	require.Equal(t, uint64(100), c.CapacityIngress(1))
+	require.Equal(t, uint64(0), c.CapacityIngress(5))
+
+	require.Equal(t, copy, c)
+}
+
+func cloneCapacities(c *Capacities) *Capacities {
+	ret := &Capacities{}
+	copy(ret.inIfs, c.inIfs)
+	copy(ret.egIfs, c.egIfs)
+	ret.c.CapIn = make(map[common.IFIDType]uint64)
+	for k, v := range c.c.CapIn {
+		ret.c.CapIn[k] = v
+	}
+	ret.c.CapEg = make(map[common.IFIDType]uint64)
+	for k, v := range c.c.CapEg {
+		ret.c.CapEg[k] = v
+	}
+	ret.c.In2Eg = make(map[common.IFIDType]map[common.IFIDType]uint64)
+	for k, v := range c.c.In2Eg {
+		m := make(map[common.IFIDType]uint64)
+		for k, v := range v {
+			m[k] = v
+		}
+		ret.c.In2Eg[k] = m
+	}
+	return ret
+}
diff --git a/go/cs/reservation/conf/testdata/caps1.json b/go/cs/reservation/conf/testdata/caps1.json
new file mode 100644
index 000000000..0fc0bdcf6
--- /dev/null
+++ b/go/cs/reservation/conf/testdata/caps1.json
@@ -0,0 +1,26 @@
+{
+  "ingress_kbps": {
+    "1": 100,
+    "2": 200,
+    "3": 300
+  },
+  "egress_kbps": {
+    "1": 100,
+    "2": 200,
+    "3": 40
+  },
+  "ingress_to_egress_kbps": {
+    "1": {
+      "2": 10,
+      "3": 20
+    },
+    "2": {
+      "1": 10,
+      "3": 20
+    },
+    "3": {
+      "1": 10,
+      "2": 20
+    }
+  }
+}
diff --git a/go/cs/reservation/conf/testdata/caps2.json b/go/cs/reservation/conf/testdata/caps2.json
new file mode 100644
index 000000000..a4ec915fd
--- /dev/null
+++ b/go/cs/reservation/conf/testdata/caps2.json
@@ -0,0 +1,7 @@
+{
+  "ingress_kbps": {
+    "1": 4418489049307132905
+  },
+  "egress_kbps": null,
+  "ingress_to_egress_kbps": null
+}
diff --git a/go/cs/reservation/e2e/request.go b/go/cs/reservation/e2e/request.go
index 3a0d45a42..87bd68bdb 100644
--- a/go/cs/reservation/e2e/request.go
+++ b/go/cs/reservation/e2e/request.go
@@ -35,10 +35,10 @@ type SetupReq interface {
 
 // BaseSetupReq is the common part of any e2e setup request.
 type BaseSetupReq struct {
-	Metadata    base.RequestMetadata // information about the request (forwarding path)
-	ID          reservation.E2EID    // the ID this request refers to
-	timestamp   time.Time            // the mandatory timestamp
-	reservation *Reservation         // nil if no reservation yet
+	base.RequestMetadata                   // information about the request (forwarding path)
+	ID                   reservation.E2EID // the ID this request refers to
+	timestamp            time.Time         // the mandatory timestamp
+	reservation          *Reservation      // nil if no reservation yet
 }
 
 func NewBaseSetupReq(path *spath.Path, ts time.Time,
@@ -56,9 +56,9 @@ func NewBaseSetupReq(path *spath.Path, ts time.Time,
 		return nil, serrors.WrapStr("cannot construct e2e request", err)
 	}
 	return &BaseSetupReq{
-		Metadata:  *metadata,
-		ID:        *e2eID,
-		timestamp: ts,
+		RequestMetadata: *metadata,
+		ID:              *e2eID,
+		timestamp:       ts,
 	}, nil
 }
 
diff --git a/go/cs/reservation/e2e/request_test.go b/go/cs/reservation/e2e/request_test.go
index f3af63599..aa41fafd2 100644
--- a/go/cs/reservation/e2e/request_test.go
+++ b/go/cs/reservation/e2e/request_test.go
@@ -70,7 +70,7 @@ func TestNewCleanupReqFromCtrlMsg(t *testing.T) {
 	p := newPath()
 	r, err := e2e.NewCleanupReqFromCtrlMsg(ctrlMsg, ts, p)
 	require.NoError(t, err)
-	require.Equal(t, *p, r.Metadata.Path)
+	require.Equal(t, p, r.Path())
 	require.Equal(t, ts, r.Timestamp())
 
 	buff := make([]byte, reservation.E2EIDLen)
@@ -160,7 +160,7 @@ func checkRequest(t *testing.T, e2eSetup *colibri_mgmt.E2ESetup, r e2e.SetupReq,
 
 	require.Equal(t, (*e2e.Reservation)(nil), base.Reservation())
 	require.Equal(t, ts, base.Timestamp())
-	require.Equal(t, *p, base.Metadata.Path)
+	require.Equal(t, p, base.Path())
 	buff := make([]byte, reservation.E2EIDLen)
 	_, err := base.ID.Read(buff)
 	require.NoError(t, err) // tested in the E2EID UT, should not fail
diff --git a/go/cs/reservation/reservationdbtest/reservationdbtest.go b/go/cs/reservation/reservationdbtest/reservationdbtest.go
index 310898a7e..848db9e9c 100644
--- a/go/cs/reservation/reservationdbtest/reservationdbtest.go
+++ b/go/cs/reservation/reservationdbtest/reservationdbtest.go
@@ -46,6 +46,7 @@ func TestDB(t *testing.T, db TestableDB) {
 		"get segment reservation from ID":        testGetSegmentRsvFromID,
 		"get segment reservations from src/dst":  testGetSegmentRsvsFromSrcDstIA,
 		"get segment reservation from path":      testGetSegmentRsvFromPath,
+		"get all segment reservations":           testGetAllSegmentRsvs,
 		"get segment reservation from IF pair":   testGetSegmentRsvsFromIFPair,
 		"delete segment reservation":             testDeleteSegmentRsv,
 		"delete expired indices":                 testDeleteExpiredIndices,
@@ -263,6 +264,34 @@ func testGetSegmentRsvFromPath(ctx context.Context, t *testing.T, db backend.DB)
 	require.Equal(t, r2, r)
 }
 
+func testGetAllSegmentRsvs(ctx context.Context, t *testing.T, db backend.DB) {
+	// empty
+	rsvs, err := db.GetAllSegmentRsvs(ctx)
+	require.NoError(t, err)
+	require.Empty(t, rsvs)
+	// insert in1,eg1 ; in2,eg1 ; in1,eg2
+	r1 := newTestReservation(t)
+	r1.Ingress = 11
+	r1.Egress = 12
+	err = db.NewSegmentRsv(ctx, r1)
+	require.NoError(t, err)
+	r2 := newTestReservation(t)
+	r2.Ingress = 21
+	r2.Egress = 12
+	err = db.NewSegmentRsv(ctx, r2)
+	require.NoError(t, err)
+	r3 := newTestReservation(t)
+	r3.Ingress = 11
+	r3.Egress = 22
+	err = db.NewSegmentRsv(ctx, r3)
+	require.NoError(t, err)
+	// retrieve them
+	rsvs, err = db.GetAllSegmentRsvs(ctx)
+	require.NoError(t, err)
+	expected := []*segment.Reservation{r1, r2, r3}
+	require.ElementsMatch(t, expected, rsvs)
+}
+
 func testGetSegmentRsvsFromIFPair(ctx context.Context, t *testing.T, db backend.DB) {
 	// insert in1,e1 ; in2,e1 ; in1,e2
 	r1 := newTestReservation(t)
diff --git a/go/cs/reservation/segment/admission/impl/stateless_test.go b/go/cs/reservation/segment/admission/impl/stateless_test.go
new file mode 100644
index 000000000..556d34677
--- /dev/null
+++ b/go/cs/reservation/segment/admission/impl/stateless_test.go
@@ -0,0 +1,523 @@
+// Copyright 2020 ETH Zurich, Anapaya Systems
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package impl
+
+import (
+	"context"
+	"testing"
+
+	"github.com/golang/mock/gomock"
+	"github.com/stretchr/testify/require"
+
+	base "github.com/scionproto/scion/go/cs/reservation"
+	"github.com/scionproto/scion/go/cs/reservation/segment"
+	"github.com/scionproto/scion/go/cs/reservationstorage/backend/mock_backend"
+	"github.com/scionproto/scion/go/lib/colibri/reservation"
+	"github.com/scionproto/scion/go/lib/common"
+	"github.com/scionproto/scion/go/lib/util"
+	"github.com/scionproto/scion/go/lib/xtest"
+)
+
+func TestSumMaxBlockedBW(t *testing.T) {
+	cases := map[string]struct {
+		blockedBW uint64
+		rsvsFcn   func() []*segment.Reservation
+		excludeID string
+	}{
+		"empty": {
+			blockedBW: 0,
+			rsvsFcn: func() []*segment.Reservation {
+				return nil
+			},
+			excludeID: "ff0000010001beefcafe",
+		},
+		"one reservation": {
+			blockedBW: reservation.BWCls(5).ToKbps(),
+			rsvsFcn: func() []*segment.Reservation {
+				rsv := testNewRsv(t, "ff00:1:1", "01234567", 1, 2, 5, 5, 5)
+				_, err := rsv.NewIndexAtSource(util.SecsToTime(3), 1, 1, 1, 1, reservation.CorePath)
+				require.NoError(t, err)
+				_, err = rsv.NewIndexAtSource(util.SecsToTime(3), 1, 1, 1, 1, reservation.CorePath)
+				require.NoError(t, err)
+				return []*segment.Reservation{rsv}
+			},
+			excludeID: "ff0000010001beefcafe",
+		},
+		"one reservation but excluded": {
+			blockedBW: 0,
+			rsvsFcn: func() []*segment.Reservation {
+				rsv := testNewRsv(t, "ff00:1:1", "beefcafe", 1, 2, 5, 5, 5)
+				_, err := rsv.NewIndexAtSource(util.SecsToTime(3), 1, 1, 1, 1, reservation.CorePath)
+				require.NoError(t, err)
+				_, err = rsv.NewIndexAtSource(util.SecsToTime(3), 1, 1, 1, 1, reservation.CorePath)
+				require.NoError(t, err)
+				return []*segment.Reservation{rsv}
+			},
+			excludeID: "ff0000010001beefcafe",
+		},
+		"many reservations": {
+			blockedBW: 309, // 181 + 128
+			rsvsFcn: func() []*segment.Reservation {
+				rsv := testNewRsv(t, "ff00:1:1", "beefcafe", 1, 2, 5, 5, 5)
+				_, err := rsv.NewIndexAtSource(util.SecsToTime(3), 1, 17, 7, 1,
+					reservation.CorePath)
+				require.NoError(t, err)
+				rsvs := []*segment.Reservation{rsv}
+
+				rsv = testNewRsv(t, "ff00:1:1", "01234567", 1, 2, 5, 5, 5)
+				_, err = rsv.NewIndexAtSource(util.SecsToTime(3), 1, 8, 8, 1, reservation.CorePath)
+				require.NoError(t, err)
+				_, err = rsv.NewIndexAtSource(util.SecsToTime(3), 1, 7, 7, 1, reservation.CorePath)
+				require.NoError(t, err)
+				rsvs = append(rsvs, rsv)
+
+				rsv = testNewRsv(t, "ff00:1:2", "01234567", 1, 2, 5, 5, 5)
+				_, err = rsv.NewIndexAtSource(util.SecsToTime(2), 1, 7, 7, 1, reservation.CorePath)
+				require.NoError(t, err)
+				rsvs = append(rsvs, rsv)
+
+				return rsvs
+			},
+			excludeID: "ff0000010001beefcafe",
+		},
+	}
+
+	for name, tc := range cases {
+		name, tc := name, tc
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			excludedID, err := reservation.SegmentIDFromRaw(xtest.MustParseHexString(tc.excludeID))
+			require.NoError(t, err)
+			sum := sumMaxBlockedBW(tc.rsvsFcn(), *excludedID)
+			require.Equal(t, tc.blockedBW, sum)
+		})
+	}
+}
+
+func TestAvailableBW(t *testing.T) {
+	req := newTestRequest(t, 1, 2, 5, 7)
+
+	cases := map[string]struct {
+		availBW uint64
+		delta   float64
+		req     *segment.SetupReq
+		setupDB func(db *mock_backend.MockDB)
+	}{
+		"empty DB": {
+			availBW: 1024,
+			delta:   1,
+			req:     req,
+			setupDB: func(db *mock_backend.MockDB) {
+				db.EXPECT().GetSegmentRsvsFromIFPair(gomock.Any(), &req.Ingress, nil).Return(
+					nil, nil)
+				db.EXPECT().GetSegmentRsvsFromIFPair(gomock.Any(), nil, &req.Egress).Return(
+					nil, nil)
+			},
+		},
+		"this reservation in DB": {
+			// as the only reservation in DB has the same ID as the request, the availableBW
+			// function should return the same value as with an empty DB.
+			availBW: 1024,
+			delta:   1,
+			req:     req,
+			setupDB: func(db *mock_backend.MockDB) {
+				db.EXPECT().GetSegmentRsvsFromIFPair(gomock.Any(), &req.Ingress, nil).Return(
+					[]*segment.Reservation{
+						testNewRsv(t, "ff00:1:1", "beefcafe", 1, 2, 5, 5, 5),
+					}, nil)
+				db.EXPECT().GetSegmentRsvsFromIFPair(gomock.Any(), nil, &req.Egress).Return(
+					[]*segment.Reservation{
+						testNewRsv(t, "ff00:1:1", "beefcafe", 1, 2, 5, 5, 5),
+					}, nil)
+			},
+		},
+		"other reservation in DB": {
+			availBW: 1024 - 64,
+			delta:   1,
+			req:     req,
+			setupDB: func(db *mock_backend.MockDB) {
+				db.EXPECT().GetSegmentRsvsFromIFPair(gomock.Any(), &req.Ingress, nil).Return(
+					[]*segment.Reservation{
+						testNewRsv(t, "ff00:1:1", "beefcafe", 1, 2, 5, 5, 5),
+						testNewRsv(t, "ff00:1:2", "beefcafe", 1, 2, 5, 5, 5),
+					}, nil)
+				db.EXPECT().GetSegmentRsvsFromIFPair(gomock.Any(), nil, &req.Egress).Return(
+					[]*segment.Reservation{
+						testNewRsv(t, "ff00:1:1", "beefcafe", 1, 2, 5, 5, 5),
+						testNewRsv(t, "ff00:1:2", "beefcafe", 1, 2, 5, 5, 5),
+					}, nil)
+			},
+		},
+		"change delta": {
+			availBW: (1024 - 64) / 2,
+			delta:   .5,
+			req:     req,
+			setupDB: func(db *mock_backend.MockDB) {
+				db.EXPECT().GetSegmentRsvsFromIFPair(gomock.Any(), &req.Ingress, nil).Return(
+					[]*segment.Reservation{
+						testNewRsv(t, "ff00:1:1", "beefcafe", 1, 2, 5, 5, 5),
+						testNewRsv(t, "ff00:1:2", "beefcafe", 1, 2, 5, 5, 5),
+					}, nil)
+				db.EXPECT().GetSegmentRsvsFromIFPair(gomock.Any(), nil, &req.Egress).Return(
+					[]*segment.Reservation{
+						testNewRsv(t, "ff00:1:1", "beefcafe", 1, 2, 5, 5, 5),
+						testNewRsv(t, "ff00:1:2", "beefcafe", 1, 2, 5, 5, 5),
+					}, nil)
+			},
+		},
+	}
+	for name, tc := range cases {
+		name, tc := name, tc
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			adm, finish := newTestAdmitter(t)
+			defer finish()
+
+			adm.Delta = tc.delta
+			ctx := context.Background()
+			db := adm.DB.(*mock_backend.MockDB)
+			tc.setupDB(db)
+			avail, err := adm.availableBW(ctx, tc.req)
+			require.NoError(t, err)
+			require.Equal(t, tc.availBW, avail)
+		})
+	}
+}
+
+func TestTubeRatio(t *testing.T) {
+	cases := map[string]struct {
+		tubeRatio      float64
+		req            *segment.SetupReq
+		setupDB        func(db *mock_backend.MockDB)
+		globalCapacity uint64
+		interfaces     []common.IFIDType
+	}{
+		"empty": {
+			tubeRatio: 1,
+			req:       newTestRequest(t, 1, 2, 5, 5),
+			setupDB: func(db *mock_backend.MockDB) {
+				rsvs := []*segment.Reservation{}
+				db.EXPECT().GetAllSegmentRsvs(gomock.Any()).AnyTimes().Return(rsvs, nil)
+			},
+			globalCapacity: 1024 * 1024,
+			interfaces:     []common.IFIDType{1, 2, 3},
+		},
+		"one source, one ingress": {
+			tubeRatio: 1,
+			req:       newTestRequest(t, 1, 2, 5, 5),
+			setupDB: func(db *mock_backend.MockDB) {
+				rsvs := []*segment.Reservation{
+					testNewRsv(t, "ff00:1:1", "00000001", 1, 2, 5, 5, 5),
+				}
+				db.EXPECT().GetAllSegmentRsvs(gomock.Any()).AnyTimes().Return(rsvs, nil)
+			},
+			globalCapacity: 1024 * 1024,
+			interfaces:     []common.IFIDType{1, 2, 3},
+		},
+		"one source, two ingress": {
+			tubeRatio: .5,
+			req:       newTestRequest(t, 1, 2, 3, 3), // 64Kbps
+			setupDB: func(db *mock_backend.MockDB) {
+				rsvs := []*segment.Reservation{
+					testNewRsv(t, "ff00:1:1", "00000001", 1, 2, 5, 3, 3), // 64Kbps
+					testNewRsv(t, "ff00:1:1", "00000002", 3, 2, 5, 5, 5), // 128Kbps
+				}
+				db.EXPECT().GetAllSegmentRsvs(gomock.Any()).AnyTimes().Return(rsvs, nil)
+			},
+			globalCapacity: 1024 * 1024,
+			interfaces:     []common.IFIDType{1, 2, 3},
+		},
+		"two sources, request already present": {
+			tubeRatio: .5,
+			req:       newTestRequest(t, 1, 2, 5, 5),
+			setupDB: func(db *mock_backend.MockDB) {
+				rsvs := []*segment.Reservation{
+					testNewRsv(t, "ff00:1:1", "beefcafe", 1, 2, 5, 9, 9), // will be ignored
+					testNewRsv(t, "ff00:1:1", "00000002", 3, 2, 5, 5, 5),
+				}
+				db.EXPECT().GetAllSegmentRsvs(gomock.Any()).AnyTimes().Return(rsvs, nil)
+			},
+			globalCapacity: 1024 * 1024,
+			interfaces:     []common.IFIDType{1, 2, 3},
+		},
+		"multiple sources, multiple ingress": {
+			tubeRatio: .75,
+			req:       newTestRequest(t, 1, 2, 5, 5),
+			setupDB: func(db *mock_backend.MockDB) {
+				rsvs := []*segment.Reservation{
+					testNewRsv(t, "ff00:1:1", "00000001", 1, 2, 5, 5, 5),
+					testNewRsv(t, "ff00:1:2", "00000001", 1, 2, 5, 5, 5),
+					testNewRsv(t, "ff00:1:1", "00000002", 3, 2, 5, 5, 5),
+				}
+				db.EXPECT().GetAllSegmentRsvs(gomock.Any()).AnyTimes().Return(rsvs, nil)
+			},
+			globalCapacity: 1024 * 1024,
+			interfaces:     []common.IFIDType{1, 2, 3},
+		},
+		"exceeding ingress capacity": {
+			tubeRatio: 10. / 13., // 10 / (10 + 0 + 3)
+			req:       newTestRequest(t, 1, 2, 5, 5),
+			setupDB: func(db *mock_backend.MockDB) {
+				rsvs := []*segment.Reservation{
+					testNewRsv(t, "ff00:1:1", "00000001", 1, 2, 5, 5, 5),
+					testNewRsv(t, "ff00:1:2", "00000001", 1, 2, 5, 5, 5),
+					testNewRsv(t, "ff00:1:1", "00000002", 3, 2, 5, 5, 5),
+				}
+				db.EXPECT().GetAllSegmentRsvs(gomock.Any()).AnyTimes().Return(rsvs, nil)
+			},
+			globalCapacity: 10,
+			interfaces:     []common.IFIDType{1, 2, 3},
+		},
+		"with many other irrelevant reservations": {
+			tubeRatio: .75,
+			req:       newTestRequest(t, 1, 2, 5, 5),
+			setupDB: func(db *mock_backend.MockDB) {
+				rsvs := []*segment.Reservation{
+					testNewRsv(t, "ff00:1:1", "00000001", 1, 2, 5, 5, 5),
+					testNewRsv(t, "ff00:1:2", "00000001", 1, 2, 5, 5, 5),
+					testNewRsv(t, "ff00:1:1", "00000002", 3, 2, 5, 5, 5),
+					testNewRsv(t, "ff00:1:3", "00000001", 4, 5, 5, 9, 9),
+					testNewRsv(t, "ff00:1:3", "00000002", 4, 5, 5, 9, 9),
+					testNewRsv(t, "ff00:1:4", "00000001", 5, 4, 5, 9, 9),
+					testNewRsv(t, "ff00:1:4", "00000002", 5, 4, 5, 9, 9),
+				}
+				db.EXPECT().GetAllSegmentRsvs(gomock.Any()).AnyTimes().Return(rsvs, nil)
+			},
+			globalCapacity: 1024 * 1024,
+			interfaces:     []common.IFIDType{1, 2, 3, 4, 5},
+		},
+	}
+
+	for name, tc := range cases {
+		name, tc := name, tc
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			adm, finish := newTestAdmitter(t)
+			defer finish()
+
+			adm.Capacities = &testCapacities{
+				Cap:    tc.globalCapacity,
+				Ifaces: tc.interfaces,
+			}
+			db := adm.DB.(*mock_backend.MockDB)
+			tc.setupDB(db)
+
+			ctx := context.Background()
+			demPerSrc, err := adm.computeTempDemands(ctx, tc.req.Ingress, tc.req)
+			require.NoError(t, err)
+			ratio, err := adm.tubeRatio(ctx, tc.req, demPerSrc)
+			require.NoError(t, err)
+			require.Equal(t, tc.tubeRatio, ratio)
+		})
+	}
+}
+
+func TestLinkRatio(t *testing.T) {
+	cases := map[string]struct {
+		linkRatio float64
+		req       *segment.SetupReq
+		setupDB   func(db *mock_backend.MockDB)
+	}{
+		"empty": {
+			linkRatio: 1.,
+			req:       testAddAllocTrail(newTestRequest(t, 1, 2, 5, 5), 5, 5),
+			setupDB: func(db *mock_backend.MockDB) {
+				rsvs := []*segment.Reservation{}
+				db.EXPECT().GetAllSegmentRsvs(gomock.Any()).AnyTimes().Return(rsvs, nil)
+			},
+		},
+		"same request": {
+			linkRatio: 1.,
+			req:       testAddAllocTrail(newTestRequest(t, 1, 2, 5, 5), 5, 5),
+			setupDB: func(db *mock_backend.MockDB) {
+				rsvs := []*segment.Reservation{
+					testNewRsv(t, "ff00:1:1", "beefcafe", 1, 2, 5, 5, 5),
+				}
+				db.EXPECT().GetAllSegmentRsvs(gomock.Any()).AnyTimes().Return(rsvs, nil)
+			},
+		},
+		"same source": {
+			linkRatio: .5,
+			req:       testAddAllocTrail(newTestRequest(t, 1, 2, 5, 5), 5, 5),
+			setupDB: func(db *mock_backend.MockDB) {
+				rsvs := []*segment.Reservation{
+					testNewRsv(t, "ff00:1:1", "beefcafe", 1, 2, 5, 5, 5),
+					testNewRsv(t, "ff00:1:1", "00000001", 1, 2, 5, 5, 5),
+				}
+				db.EXPECT().GetAllSegmentRsvs(gomock.Any()).AnyTimes().Return(rsvs, nil)
+			},
+		},
+		"different sources": {
+			linkRatio: 1. / 3.,
+			req:       testAddAllocTrail(newTestRequest(t, 1, 2, 5, 5), 5, 5),
+			setupDB: func(db *mock_backend.MockDB) {
+				rsvs := []*segment.Reservation{
+					testNewRsv(t, "ff00:1:2", "00000001", 1, 2, 5, 5, 5),
+					testNewRsv(t, "ff00:1:3", "00000001", 1, 2, 5, 5, 5),
+				}
+				db.EXPECT().GetAllSegmentRsvs(gomock.Any()).AnyTimes().Return(rsvs, nil)
+			},
+		},
+		"different egress interface": {
+			linkRatio: .5,
+			req:       testAddAllocTrail(newTestRequest(t, 1, 2, 5, 5), 5, 5),
+			setupDB: func(db *mock_backend.MockDB) {
+				rsvs := []*segment.Reservation{
+					testNewRsv(t, "ff00:1:1", "00000001", 1, 3, 5, 5, 5),
+					// testNewRsv(t, "ff00:1:3", "00000001", 1, 2, 5, 5, 5),
+				}
+				db.EXPECT().GetAllSegmentRsvs(gomock.Any()).AnyTimes().Return(rsvs, nil)
+			},
+		},
+		"smaller prevBW": {
+			linkRatio: 1. / 3.,
+			req:       testAddAllocTrail(newTestRequest(t, 1, 2, 5, 5), 3, 3), // 64 Kbps
+			setupDB: func(db *mock_backend.MockDB) {
+				rsvs := []*segment.Reservation{
+					testNewRsv(t, "ff00:1:1", "00000001", 1, 2, 5, 5, 5), // 128 Kbps
+				}
+				db.EXPECT().GetAllSegmentRsvs(gomock.Any()).AnyTimes().Return(rsvs, nil)
+			},
+		},
+		"bigger prevBW": {
+			linkRatio: 2. / 3.,
+			req:       testAddAllocTrail(newTestRequest(t, 1, 2, 5, 5), 7, 7), // 256 Kbps
+			setupDB: func(db *mock_backend.MockDB) {
+				rsvs := []*segment.Reservation{
+					testNewRsv(t, "ff00:1:1", "00000001", 1, 2, 5, 5, 5), // 128 Kbps
+				}
+				db.EXPECT().GetAllSegmentRsvs(gomock.Any()).AnyTimes().Return(rsvs, nil)
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		name, tc := name, tc
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			adm, finish := newTestAdmitter(t)
+			defer finish()
+
+			adm.Capacities = &testCapacities{
+				Cap:    1024 * 1024,
+				Ifaces: []common.IFIDType{1, 2, 3},
+			}
+			db := adm.DB.(*mock_backend.MockDB)
+			tc.setupDB(db)
+
+			ctx := context.Background()
+			demsPerSrc, err := adm.computeTempDemands(ctx, tc.req.Ingress, tc.req)
+			require.NoError(t, err)
+			linkRatio, err := adm.linkRatio(ctx, tc.req, demsPerSrc)
+			require.NoError(t, err)
+			require.Equal(t, tc.linkRatio, linkRatio)
+		})
+	}
+
+}
+
+type testCapacities struct {
+	Cap    uint64
+	Ifaces []common.IFIDType
+}
+
+var _ base.Capacities = (*testCapacities)(nil)
+
+func (c *testCapacities) IngressInterfaces() []common.IFIDType           { return c.Ifaces }
+func (c *testCapacities) EgressInterfaces() []common.IFIDType            { return c.Ifaces }
+func (c *testCapacities) Capacity(from, to common.IFIDType) uint64       { return c.Cap }
+func (c *testCapacities) CapacityIngress(ingress common.IFIDType) uint64 { return c.Cap }
+func (c *testCapacities) CapacityEgress(egress common.IFIDType) uint64   { return c.Cap }
+
+func newTestAdmitter(t *testing.T) (*StatelessAdmission, func()) {
+	mctlr := gomock.NewController(t)
+
+	db := mock_backend.NewMockDB(mctlr)
+	return &StatelessAdmission{
+		DB: db,
+		Capacities: &testCapacities{
+			Cap:    1024, // 1MBps
+			Ifaces: []common.IFIDType{1, 2},
+		},
+		Delta: 1,
+	}, mctlr.Finish
+}
+
+// newTestRequest creates a request ID ff00:1:1 beefcafe
+func newTestRequest(t *testing.T, ingress, egress common.IFIDType,
+	minBW, maxBW reservation.BWCls) *segment.SetupReq {
+
+	ID, err := reservation.SegmentIDFromRaw(xtest.MustParseHexString("ff0000010001beefcafe"))
+	require.NoError(t, err)
+	return &segment.SetupReq{
+		Request: segment.Request{
+			RequestMetadata: base.RequestMetadata{},
+			ID:              *ID,
+			Timestamp:       util.SecsToTime(1),
+			Ingress:         ingress,
+			Egress:          egress,
+		},
+		MinBW:     minBW,
+		MaxBW:     maxBW,
+		SplitCls:  2,
+		PathProps: reservation.StartLocal | reservation.EndLocal,
+	}
+}
+
+func testNewRsv(t *testing.T, srcAS string, suffix string, ingress, egress common.IFIDType,
+	minBW, maxBW, allocBW reservation.BWCls) *segment.Reservation {
+
+	ID, err := reservation.NewSegmentID(xtest.MustParseAS(srcAS),
+		xtest.MustParseHexString(suffix))
+	require.NoError(t, err)
+	rsv := &segment.Reservation{
+		ID: *ID,
+		Indices: segment.Indices{
+			segment.Index{
+				Idx:        10,
+				Expiration: util.SecsToTime(2),
+				MinBW:      minBW,
+				MaxBW:      maxBW,
+				AllocBW:    allocBW,
+			},
+		},
+		Ingress:      ingress,
+		Egress:       egress,
+		PathType:     reservation.UpPath,
+		PathEndProps: reservation.StartLocal | reservation.EndLocal | reservation.EndTransfer,
+		TrafficSplit: 2,
+	}
+	err = rsv.SetIndexConfirmed(10)
+	require.NoError(t, err)
+	err = rsv.SetIndexActive(10)
+	require.NoError(t, err)
+	return rsv
+}
+
+// testAddAllocTrail adds an allocation trail to a reservation. The beads parameter represents
+// the trail like: alloc0,max0,alloc1,max1,...
+func testAddAllocTrail(req *segment.SetupReq, beads ...reservation.BWCls) *segment.SetupReq {
+	if len(beads)%2 != 0 {
+		panic("the beads must be even")
+	}
+	for i := 0; i < len(beads); i += 2 {
+		beads := reservation.AllocationBead{
+			AllocBW: beads[i],
+			MaxBW:   beads[i+1],
+		}
+		req.AllocTrail = append(req.AllocTrail, beads)
+	}
+	return req
+}
diff --git a/go/cs/reservation/segment/request_test.go b/go/cs/reservation/segment/request_test.go
index e134c8c29..bf11cc561 100644
--- a/go/cs/reservation/segment/request_test.go
+++ b/go/cs/reservation/segment/request_test.go
@@ -41,7 +41,7 @@ func TestNewSetupReqFromCtrlMsg(t *testing.T) {
 	id := newID()
 	r, err = segment.NewSetupReqFromCtrlMsg(ctrlMsg, ts, id, p)
 	require.NoError(t, err)
-	require.Equal(t, *p, r.Metadata.Path)
+	require.Equal(t, p, r.Path())
 	checkRequest(t, ctrlMsg, r, ts)
 	require.Equal(t, common.IFIDType(1), r.Ingress)
 	require.Equal(t, common.IFIDType(2), r.Egress)
@@ -141,7 +141,8 @@ func newSetup() *colibri_mgmt.SegmentSetup {
 			Local:    false,
 			Transfer: true,
 		},
-		AllocationTrail: []*colibri_mgmt.AllocationBeads{
+		InfoField: xtest.MustParseHexString("16ebdb4f0d042500"),
+		AllocationTrail: []*colibri_mgmt.AllocationBead{
 			{
 				AllocBW: 5,
 				MaxBW:   6,
@@ -204,17 +205,18 @@ func newID() *colibri_mgmt.SegmentReservationID {
 func checkRequest(t *testing.T, segSetup *colibri_mgmt.SegmentSetup, r *segment.SetupReq,
 	ts time.Time) {
 
+	t.Helper()
 	require.Equal(t, (*segment.Reservation)(nil), r.Reservation)
 	require.Equal(t, ts, r.Timestamp)
-	require.Equal(t, segSetup.MinBW, r.MinBW)
-	require.Equal(t, segSetup.MaxBW, r.MaxBW)
-	require.Equal(t, segSetup.SplitCls, r.SplitCls)
+	require.Equal(t, segSetup.MinBW, uint8(r.MinBW))
+	require.Equal(t, segSetup.MaxBW, uint8(r.MaxBW))
+	require.Equal(t, segSetup.SplitCls, uint8(r.SplitCls))
 	require.Equal(t, reservation.NewPathEndProps(
 		segSetup.StartProps.Local, segSetup.StartProps.Transfer,
 		segSetup.EndProps.Local, segSetup.EndProps.Transfer), r.PathProps)
 	require.Len(t, r.AllocTrail, len(segSetup.AllocationTrail))
 	for i := range segSetup.AllocationTrail {
-		require.Equal(t, segSetup.AllocationTrail[i].AllocBW, r.AllocTrail[i].AllocBW)
-		require.Equal(t, segSetup.AllocationTrail[i].MaxBW, r.AllocTrail[i].MaxBW)
+		require.Equal(t, segSetup.AllocationTrail[i].AllocBW, uint8(r.AllocTrail[i].AllocBW))
+		require.Equal(t, segSetup.AllocationTrail[i].MaxBW, uint8(r.AllocTrail[i].MaxBW))
 	}
 }
diff --git a/go/cs/reservation/segment/reservation_test.go b/go/cs/reservation/segment/reservation_test.go
index a9a7853b7..308b63536 100644
--- a/go/cs/reservation/segment/reservation_test.go
+++ b/go/cs/reservation/segment/reservation_test.go
@@ -135,15 +135,13 @@ func TestIndex(t *testing.T) {
 	idx, _ := r.NewIndexAtSource(expTime, 0, 0, 0, 0, reservation.CorePath)
 	r.NewIndexAtSource(expTime, 0, 0, 0, 0, reservation.CorePath)
 	require.Len(t, r.Indices, 3)
-	index, err := r.Index(idx)
-	require.NoError(t, err)
+	index := r.Index(idx)
 	require.Equal(t, &r.Indices[1], index)
-	_, err = r.Index(reservation.IndexNumber(4))
-	require.Error(t, err)
+	index = r.Index(reservation.IndexNumber(4))
+	require.Nil(t, index)
 	r.SetIndexConfirmed(idx)
 	r.SetIndexActive(idx)
-	index, err = r.Index(idx)
-	require.NoError(t, err)
+	index = r.Index(idx)
 	require.Equal(t, &r.Indices[0], index)
 }
 
@@ -226,3 +224,15 @@ func TestRemoveIndex(t *testing.T) {
 	err = r.Validate()
 	require.NoError(t, err)
 }
+
+func TestMaxBlockedBW(t *testing.T) {
+	r := segmenttest.NewReservation()
+	r.Indices = r.Indices[:0]
+	require.Equal(t, uint64(0), r.MaxBlockedBW())
+	r.NewIndexAtSource(util.SecsToTime(1), 1, 1, 1, 1, reservation.CorePath)
+	require.Equal(t, reservation.BWCls(1).ToKbps(), r.MaxBlockedBW())
+	r.NewIndexAtSource(util.SecsToTime(1), 1, 1, 1, 1, reservation.CorePath)
+	require.Equal(t, reservation.BWCls(1).ToKbps(), r.MaxBlockedBW())
+	r.Indices[0].AllocBW = 11
+	require.Equal(t, reservation.BWCls(11).ToKbps(), r.MaxBlockedBW())
+}
diff --git a/go/cs/reservationstore/store_test.go b/go/cs/reservationstore/store_test.go
new file mode 100644
index 000000000..300b98c90
--- /dev/null
+++ b/go/cs/reservationstore/store_test.go
@@ -0,0 +1,26 @@
+// Copyright 2020 ETH Zurich, Anapaya Systems
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package reservationstore
+
+import (
+	"testing"
+
+	"github.com/scionproto/scion/go/cs/reservationstorage"
+)
+
+func TestStore(t *testing.T) {
+	var s reservationstorage.Store = &Store{}
+	_ = s
+}
diff --git a/go/lib/colibri/reservation/types_test.go b/go/lib/colibri/reservation/types_test.go
index 6e03d846e..8e8089b87 100644
--- a/go/lib/colibri/reservation/types_test.go
+++ b/go/lib/colibri/reservation/types_test.go
@@ -15,6 +15,7 @@
 package reservation
 
 import (
+	"fmt"
 	"testing"
 	"time"
 
@@ -43,6 +44,24 @@ func TestSegmentIDRead(t *testing.T) {
 	require.Equal(t, xtest.MustParseHexString("ffaa00001101facecafe"), raw)
 }
 
+func TestSegmentIDString(t *testing.T) {
+	cases := []struct {
+		ID  SegmentID
+		Str string
+	}{
+		{ID: mustParseSegmentID("ff0000001101facecafe"), Str: "ff00:0:1101-facecafe"},
+		{ID: mustParseSegmentID("ff000000110100000000"), Str: "ff00:0:1101-00000000"},
+	}
+	for i, c := range cases {
+		name := fmt.Sprintf("case %d", i)
+		t.Run(name, func(t *testing.T) {
+			c := c
+			t.Parallel()
+			require.Equal(t, c.Str, c.ID.String())
+		})
+	}
+}
+
 func TestE2EIDFromRaw(t *testing.T) {
 	raw := xtest.MustParseHexString("ffaa00001101facecafedeadbeeff00d")
 	id, err := E2EIDFromRaw(raw)
@@ -88,6 +107,82 @@ func TestValidateBWCls(t *testing.T) {
 	require.Error(t, err)
 }
 
+func TestBWClsToKbps(t *testing.T) {
+	cases := map[BWCls]uint64{
+		0:  11,
+		1:  16,
+		2:  22,
+		5:  64,
+		13: 1024,
+		63: 32 * 1024 * 1024 * 1024, // 32 TBps
+	}
+	for cls, bw := range cases {
+		name := fmt.Sprintf("case for %d", cls)
+		t.Run(name, func(t *testing.T) {
+			cls := cls
+			bw := bw
+			t.Parallel()
+			require.Equal(t, bw, cls.ToKbps())
+		})
+	}
+}
+
+func TestBWClsFromBW(t *testing.T) {
+	cases := map[uint64]BWCls{
+		0:                       0,
+		16:                      1,
+		22:                      2,
+		64:                      5,
+		1024:                    13,
+		32 * 1024 * 1024 * 1024: 63,
+		21:                      2,
+		4096:                    17,
+		4000:                    17,
+		4097:                    18,
+	}
+	for bw, cls := range cases {
+		name := fmt.Sprintf("case for %d", bw)
+		t.Run(name, func(t *testing.T) {
+			bw := bw
+			cls := cls
+			t.Parallel()
+			require.Equal(t, cls, BWClsFromBW(bw))
+		})
+	}
+}
+
+func TestMaxBWCls(t *testing.T) {
+	cases := []struct{ a, b, max BWCls }{
+		{a: 1, b: 1, max: 1},
+		{a: 0, b: 1, max: 1},
+		{a: 255, b: 1, max: 255},
+	}
+	for i, c := range cases {
+		name := fmt.Sprintf("case %d", i)
+		t.Run(name, func(t *testing.T) {
+			c := c
+			t.Parallel()
+			require.Equal(t, c.max, MaxBWCls(c.a, c.b))
+		})
+	}
+}
+
+func TestMinBWCls(t *testing.T) {
+	cases := []struct{ a, b, min BWCls }{
+		{a: 1, b: 1, min: 1},
+		{a: 0, b: 1, min: 0},
+		{a: 255, b: 0, min: 0},
+	}
+	for i, c := range cases {
+		name := fmt.Sprintf("case %d", i)
+		t.Run(name, func(t *testing.T) {
+			c := c
+			t.Parallel()
+			require.Equal(t, c.min, MinBWCls(c.a, c.b))
+		})
+	}
+}
+
 func TestValidateRLC(t *testing.T) {
 	for i := 0; i < 64; i++ {
 		c := RLC(i)
@@ -202,6 +297,15 @@ func TestInfoFieldRead(t *testing.T) {
 	require.Equal(t, rawReference, raw)
 }
 
+func TestInfoFieldToRaw(t *testing.T) {
+	val := newInfoField()
+	reference := &val
+	rawReference := newInfoFieldRaw()
+	require.Equal(t, rawReference, reference.ToRaw())
+	reference = nil
+	require.Equal(t, ([]byte)(nil), reference.ToRaw())
+}
+
 func TestValidatePathEndProperties(t *testing.T) {
 	for i := 0; i < 4; i++ {
 		pep := PathEndProps(i)
@@ -226,6 +330,84 @@ func TestValidatePathEndProperties(t *testing.T) {
 	require.Error(t, err)
 }
 
+func TestValidatePathEndPropsWithPathType(t *testing.T) {
+	cases := []struct {
+		PT    PathType
+		EP    PathEndProps
+		Valid bool
+	}{
+		// core path
+		{CorePath, StartLocal | EndLocal, true},
+		{CorePath, StartLocal | EndLocal | EndTransfer, true},
+		{CorePath, StartTransfer | EndTransfer, true},
+		{CorePath, StartLocal, true},
+		{CorePath, StartTransfer, true},
+		{CorePath, EndLocal, false},
+		{CorePath, 0, false},
+		// up path
+		{UpPath, StartLocal, true},
+		{UpPath, StartLocal | EndLocal | EndTransfer, true},
+		{UpPath, 0, false},
+		{UpPath, StartTransfer, false},
+		{UpPath, StartTransfer | StartLocal, false},
+		// down path
+		{DownPath, EndLocal, true},
+		{DownPath, EndLocal | StartLocal | StartTransfer, true},
+		{DownPath, 0, false},
+		{DownPath, EndTransfer, false},
+		{DownPath, EndTransfer | EndLocal, false},
+		// peering up path
+		{PeeringUpPath, StartLocal | EndLocal, true},
+		{PeeringUpPath, StartLocal | EndLocal | EndTransfer, true},
+		{PeeringUpPath, 0, false},
+		{PeeringUpPath, StartLocal, false},
+		{PeeringUpPath, StartLocal | StartTransfer | EndLocal, false},
+		{PeeringUpPath, StartTransfer | EndLocal, false},
+		{PeeringUpPath, EndLocal, false},
+		// peering down path
+		{PeeringDownPath, EndLocal | StartLocal, true},
+		{PeeringDownPath, EndLocal | StartLocal | StartTransfer, true},
+		{PeeringDownPath, 0, false},
+		{PeeringDownPath, EndLocal, false},
+		{PeeringDownPath, EndLocal | EndTransfer | StartLocal, false},
+		{PeeringDownPath, EndTransfer | StartLocal, false},
+		{PeeringDownPath, StartLocal, false},
+	}
+	for i, c := range cases {
+		name := fmt.Sprintf("iteration %d", i)
+		t.Run(name, func(t *testing.T) {
+			c := c
+			t.Parallel()
+			err := c.EP.ValidateWithPathType(c.PT)
+			if c.Valid {
+				require.NoError(t, err)
+			} else {
+				require.Error(t, err)
+			}
+		})
+	}
+}
+
+func TestAllocationBeadsMinMax(t *testing.T) {
+	cases := []struct {
+		Trail AllocationBeads
+		Min   BWCls
+	}{
+		{newAllocationBeads(), 0},
+		{newAllocationBeads(0, 1), 1},
+		{newAllocationBeads(0, 3, 0, 1), 1},
+		{newAllocationBeads(0, 3, 0, 255), 3},
+	}
+	for i, c := range cases {
+		name := fmt.Sprintf("iteration %d", i)
+		t.Run(name, func(t *testing.T) {
+			c := c
+			t.Parallel()
+			require.Equal(t, c.Min, c.Trail.MinMax())
+		})
+	}
+}
+
 func TestValidateToken(t *testing.T) {
 	tok := newToken()
 	err := tok.Validate()
@@ -314,3 +496,23 @@ func newToken() Token {
 func newTokenRaw() []byte {
 	return xtest.MustParseHexString("16ebdb4f0d042500003f001002bad1ce003f001002facade")
 }
+
+func mustParseSegmentID(s string) SegmentID {
+	id, err := SegmentIDFromRaw(xtest.MustParseHexString(s))
+	if err != nil {
+		panic(err)
+	}
+	return *id
+}
+
+// newAllocationBeads (1,2,3,4) returns two beads {alloc: 1, max: 2}, {alloc:3, max:4}
+func newAllocationBeads(beads ...BWCls) AllocationBeads {
+	if len(beads)%2 != 0 {
+		panic("must have an even number of parameters")
+	}
+	ret := make(AllocationBeads, len(beads)/2)
+	for i := 0; i < len(beads); i += 2 {
+		ret[i/2] = AllocationBead{AllocBW: beads[i], MaxBW: beads[i+1]}
+	}
+	return ret
+}
diff --git a/go/lib/ctrl/colibri_mgmt/colibri_mgmt_test.go b/go/lib/ctrl/colibri_mgmt/colibri_mgmt_test.go
index 0e67d8ebb..9d0e9fd84 100644
--- a/go/lib/ctrl/colibri_mgmt/colibri_mgmt_test.go
+++ b/go/lib/ctrl/colibri_mgmt/colibri_mgmt_test.go
@@ -55,7 +55,8 @@ func TestSerializeRequest(t *testing.T) {
 				Local:    false,
 				Transfer: true,
 			},
-			AllocationTrail: []*colibri_mgmt.AllocationBeads{
+			InfoField: xtest.MustParseHexString("0123456789abcdef"),
+			AllocationTrail: []*colibri_mgmt.AllocationBead{
 				{
 					AllocBW: 5,
 					MaxBW:   6,
@@ -235,7 +236,7 @@ func TestSerializeResponse(t *testing.T) {
 							Local:    false,
 							Transfer: true,
 						},
-						AllocationTrail: []*colibri_mgmt.AllocationBeads{
+						AllocationTrail: []*colibri_mgmt.AllocationBead{
 							{
 								AllocBW: 5,
 								MaxBW:   6,