Repository: scionproto/scion
Pull request #3787 (2020-06-16 09:22:50)

COLIBRI backend continuation
- [x] getter DB functions
- [x] UTs
- [x] Rebase after #3778 (first proper commit is `a30df` "refactor, move private function to bottom")

<!-- 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/3787)
<!-- Reviewable:end -->



Patch:
diff --git a/go/cs/reservation/BUILD.bazel b/go/cs/reservation/BUILD.bazel
index 3ba3e23bd..bb3398f78 100644
--- a/go/cs/reservation/BUILD.bazel
+++ b/go/cs/reservation/BUILD.bazel
@@ -1,4 +1,4 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 
 go_library(
     name = "go_default_library",
@@ -14,3 +14,13 @@ go_library(
         "//go/lib/spath:go_default_library",
     ],
 )
+
+go_test(
+    name = "go_default_test",
+    srcs = ["index_test.go"],
+    embed = [":go_default_library"],
+    deps = [
+        "//go/lib/colibri/reservation:go_default_library",
+        "@com_github_stretchr_testify//require:go_default_library",
+    ],
+)
diff --git a/go/cs/reservation/index.go b/go/cs/reservation/index.go
index ab8fe6a51..7e642a3ad 100644
--- a/go/cs/reservation/index.go
+++ b/go/cs/reservation/index.go
@@ -25,6 +25,8 @@ type IndicesInterface interface {
 	Len() int
 	GetIndexNumber(i int) reservation.IndexNumber
 	GetExpiration(i int) time.Time
+	GetAllocBW(i int) reservation.BWCls
+	GetToken(i int) *reservation.Token
 }
 
 // ValidateIndices checks that the indices follow consecutive index numbers, their expiration
@@ -62,6 +64,21 @@ func ValidateIndices(indices IndicesInterface) error {
 		}
 		lastExpiration = indices.GetExpiration(i)
 		lastIndexNumber = indices.GetIndexNumber(i)
+		token := indices.GetToken(i)
+		if token != nil {
+			if token.Idx != lastIndexNumber {
+				return serrors.New("inconsistent token", "token_index_number", token.Idx,
+					"expected", lastIndexNumber)
+			}
+			if token.ExpirationTick != reservation.TickFromTime(lastExpiration) {
+				return serrors.New("inconsistent token", "token_expiration_tick",
+					token.ExpirationTick, "expected", reservation.TickFromTime(lastExpiration))
+			}
+			if token.BWCls != indices.GetAllocBW(i) {
+				return serrors.New("inconsistent token", "token_bw_class", token.BWCls,
+					"expected", indices.GetAllocBW(i))
+			}
+		}
 	}
 	return nil
 }
diff --git a/go/cs/reservation/segment/BUILD.bazel b/go/cs/reservation/segment/BUILD.bazel
index c18b3ab40..e2870d4b3 100644
--- a/go/cs/reservation/segment/BUILD.bazel
+++ b/go/cs/reservation/segment/BUILD.bazel
@@ -26,12 +26,14 @@ go_test(
     name = "go_default_test",
     srcs = [
         "export_test.go",
+        "index_test.go",
         "path_test.go",
         "request_test.go",
         "reservation_test.go",
     ],
     embed = [":go_default_library"],
     deps = [
+        "//go/cs/reservation:go_default_library",
         "//go/cs/reservation/segmenttest:go_default_library",
         "//go/lib/colibri/reservation:go_default_library",
         "//go/lib/common:go_default_library",
diff --git a/go/cs/reservation/segment/index.go b/go/cs/reservation/segment/index.go
index ebfa4bb7f..9821241f4 100644
--- a/go/cs/reservation/segment/index.go
+++ b/go/cs/reservation/segment/index.go
@@ -15,6 +15,7 @@
 package segment
 
 import (
+	"sort"
 	"time"
 
 	base "github.com/scionproto/scion/go/cs/reservation"
@@ -68,7 +69,21 @@ type Index struct {
 	MinBW      reservation.BWCls
 	MaxBW      reservation.BWCls
 	AllocBW    reservation.BWCls
-	Token      reservation.Token
+	Token      *reservation.Token
+}
+
+// NewIndex creates a new Index without yet linking it to any reservation.
+func NewIndex(idx reservation.IndexNumber, expiration time.Time, state IndexState,
+	minBW, maxBW, allocBW reservation.BWCls, token *reservation.Token) *Index {
+	return &Index{
+		Idx:        idx,
+		Expiration: expiration,
+		state:      state,
+		MinBW:      minBW,
+		MaxBW:      maxBW,
+		AllocBW:    allocBW,
+		Token:      token,
+	}
 }
 
 // State returns the read-only state.
@@ -84,3 +99,26 @@ var _ base.IndicesInterface = (*Indices)(nil)
 func (idxs Indices) Len() int                                     { return len(idxs) }
 func (idxs Indices) GetIndexNumber(i int) reservation.IndexNumber { return idxs[i].Idx }
 func (idxs Indices) GetExpiration(i int) time.Time                { return idxs[i].Expiration }
+func (idxs Indices) GetAllocBW(i int) reservation.BWCls           { return idxs[i].AllocBW }
+func (idxs Indices) GetToken(i int) *reservation.Token            { return idxs[i].Token }
+
+// Sort sorts these Indices according to their index number modulo 16, e.g. [14, 15, 0, 1].
+func (idxs *Indices) Sort() {
+	if len(*idxs) < 2 {
+		return
+	}
+	sort.Slice(*idxs, func(i, j int) bool {
+		a, b := (*idxs)[i], (*idxs)[j]
+		distance := b.Idx.Sub(a.Idx)
+		return a.Expiration.Before(b.Expiration) ||
+			(a.Expiration.Equal(b.Expiration) && distance < 3)
+	})
+	// find a discontinuity and rotate
+	i := 1
+	for ; i < len(*idxs); i++ {
+		if (*idxs)[i-1].Idx.Add(1) != (*idxs)[i].Idx.Add(0) {
+			break
+		}
+	}
+	*idxs = append((*idxs)[i:], (*idxs)[:i]...)
+}
diff --git a/go/cs/reservation/segment/path.go b/go/cs/reservation/segment/path.go
index 190df4521..979701710 100644
--- a/go/cs/reservation/segment/path.go
+++ b/go/cs/reservation/segment/path.go
@@ -16,7 +16,9 @@ package segment
 
 import (
 	"encoding/binary"
+	"fmt"
 	"io"
+	"strings"
 
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
@@ -119,13 +121,21 @@ func (p Path) Read(buff []byte) (int, error) {
 // ToRaw returns a buffer representing this Path.
 func (p Path) ToRaw() []byte {
 	if len(p) == 0 {
-		return []byte{}
+		return nil
 	}
 	buff := make([]byte, p.Len())
 	p.Read(buff)
 	return buff
 }
 
+func (p Path) String() string {
+	strs := make([]string, len(p))
+	for i, s := range p {
+		strs[i] = s.String()
+	}
+	return strings.Join(strs, ">")
+}
+
 // PathStep is one hop of the Path. For a source AS Ingress will be invalid. Conversely for dst.
 type PathStep struct {
 	Ingress common.IFIDType
@@ -140,3 +150,7 @@ type PathStepWithIA struct {
 
 // PathStepWithIALen amounts for Ingress+Egress+IA.
 const PathStepWithIALen = 8 + 8 + 8
+
+func (s *PathStepWithIA) String() string {
+	return fmt.Sprintf("%d %s %d", s.Ingress, s.IA.String(), s.Egress)
+}
diff --git a/go/cs/reservation/segment/reservation.go b/go/cs/reservation/segment/reservation.go
index 238f0104f..44b8abd8a 100644
--- a/go/cs/reservation/segment/reservation.go
+++ b/go/cs/reservation/segment/reservation.go
@@ -93,25 +93,65 @@ func (r *Reservation) ActiveIndex() *Index {
 	return &r.Indices[r.activeIndex]
 }
 
-// NewIndex creates a new index in this reservation and returns a pointer to it.
-// Parameters of this index can be changed using the pointer, except for the state.
-func (r *Reservation) NewIndex(expTime time.Time) (reservation.IndexNumber, error) {
+// TODO(juagargi) we need two ways of creating an index: from the source AS or from transit AS.
+
+// NewIndexAtSource creates a new index. The associated token is created from the arguments, and
+// automatically linked to the index. This function should be called only from the
+// AS originating the reservation.
+// The expiration times must always be greater or equal than those in previous indices.
+func (r *Reservation) NewIndexAtSource(expTime time.Time, minBW, maxBW, allocBW reservation.BWCls,
+	rlc reservation.RLC, pathType reservation.PathType) (reservation.IndexNumber, error) {
+
 	idx := reservation.IndexNumber(0)
 	if len(r.Indices) > 0 {
 		idx = r.Indices[len(r.Indices)-1].Idx.Add(1)
 	}
+	tok := &reservation.Token{
+		InfoField: reservation.InfoField{
+			Idx:            idx,
+			ExpirationTick: reservation.TickFromTime(expTime),
+			BWCls:          allocBW,
+			RLC:            rlc,
+			PathType:       pathType,
+		},
+	}
+	index := NewIndex(idx, expTime, IndexTemporary, minBW, maxBW, allocBW, tok)
+	return r.addIndex(index)
+}
+
+// NewIndexFromToken creates a new index. The token argument is used to populate several
+// fields of the index. The token is not stored (on-path ASes don't need the token).
+// This function should be called from an AS that is on the reservation path
+// but not the originating one.
+func (r *Reservation) NewIndexFromToken(tok *reservation.Token, minBW, maxBW reservation.BWCls) (
+	reservation.IndexNumber, error) {
+
+	if tok == nil {
+		return 0, serrors.New("token is nil")
+	}
+	index := NewIndex(tok.Idx, tok.ExpirationTick.ToTime(), IndexTemporary, minBW, maxBW,
+		tok.BWCls, nil)
+	return r.addIndex(index)
+}
+
+func (r *Reservation) addIndex(index *Index) (reservation.IndexNumber, error) {
 	newIndices := make(Indices, len(r.Indices)+1)
 	copy(newIndices, r.Indices)
-	newIndices[len(newIndices)-1] = Index{
-		Expiration: expTime,
-		Idx:        idx,
-		state:      IndexTemporary,
-	}
+	newIndices[len(newIndices)-1] = *index
 	if err := base.ValidateIndices(newIndices); err != nil {
 		return 0, err
 	}
 	r.Indices = newIndices
-	return r.Indices[len(r.Indices)-1].Idx, nil
+	return index.Idx, nil
+}
+
+// Index finds the Index with that IndexNumber and returns a pointer to it.
+func (r *Reservation) Index(idx reservation.IndexNumber) (*Index, error) {
+	sliceIndex, err := base.FindIndex(r.Indices, idx)
+	if err != nil {
+		return nil, err
+	}
+	return &r.Indices[sliceIndex], nil
 }
 
 // SetIndexConfirmed sets the index as IndexPending (confirmed but not active). If the requested
@@ -138,7 +178,8 @@ func (r *Reservation) SetIndexActive(idx reservation.IndexNumber) error {
 	if r.activeIndex == sliceIndex {
 		return nil // already active
 	}
-	if r.Indices[sliceIndex].state != IndexPending {
+	// valid states are Pending (nominal) and Active (reconstructing from DB needs this)
+	if r.Indices[sliceIndex].state != IndexPending && r.Indices[sliceIndex].state != IndexActive {
 		return serrors.New("attempt to activate a non confirmed index", "index_number", idx,
 			"state", r.Indices[sliceIndex].state)
 	}
diff --git a/go/cs/reservation/sqlite/db.go b/go/cs/reservation/sqlite/db.go
index a8e11bcfe..90a7350c8 100644
--- a/go/cs/reservation/sqlite/db.go
+++ b/go/cs/reservation/sqlite/db.go
@@ -18,6 +18,9 @@ import (
 	"context"
 	"database/sql"
 	"encoding/binary"
+	"encoding/hex"
+	"fmt"
+	"strings"
 	"sync"
 	"time"
 
@@ -114,61 +117,92 @@ type executor struct {
 	db db.Sqler
 }
 
-func (x *executor) GetSegmentRsvFromID(ctx context.Context, ID reservation.SegmentID,
-	idx *reservation.IndexNumber) (*segment.Reservation, error) {
+func (x *executor) GetSegmentRsvFromID(ctx context.Context, ID *reservation.SegmentID) (
+	*segment.Reservation, error) {
 
-	return nil, nil
+	params := []interface{}{
+		ID.ASID,
+		binary.BigEndian.Uint32(ID.Suffix[:]),
+	}
+	rsvs, err := getSegReservations(ctx, x.db, "WHERE id_as = ? AND id_suffix = ?", params)
+	if err != nil {
+		return nil, err
+	}
+	switch len(rsvs) {
+	case 0:
+		return nil, nil
+	case 1:
+		return rsvs[0], nil
+	default:
+		return nil, db.NewDataError("more than 1 segment reservation found for an ID", nil,
+			"count", len(rsvs), "id.asid", ID.ASID, "id.suffix", hex.EncodeToString(ID.Suffix[:]))
+	}
 }
 
-// GetSegmentRsvFromSrcDstAS returns all reservations that start at src AS and end in dst AS.
-func (x *executor) GetSegmentRsvFromSrcDstAS(ctx context.Context, srcIA, dstIA addr.IA) (
+// GetSegmentRsvsFromSrcDstIA returns all reservations that start at src AS and end in dst AS.
+func (x *executor) GetSegmentRsvsFromSrcDstIA(ctx context.Context, srcIA, dstIA addr.IA) (
 	[]*segment.Reservation, error) {
 
-	x.Lock()
-	defer x.Unlock()
-
-	query := `SELECT r.reservation_id, r.ingress, r.egress
-	FROM seg_reservation r
-	WHERE r.src_as = ?1 AND r.dst_as = ?2`
-	rows, err := x.db.QueryContext(ctx, query, srcIA.IAInt(), dstIA.IAInt())
-	if err != nil {
-		return nil, db.NewReadError("error obtaining segment reservations", err)
+	conditions := make([]string, 0, 2)
+	params := make([]interface{}, 0, 2)
+	if !srcIA.IsZero() {
+		conditions = append(conditions, "src_ia = ?")
+		params = append(params, srcIA.IAInt())
 	}
-	defer rows.Close()
-	for rows.Next() {
-		if err := rows.Scan(); err != nil {
-			return nil, db.NewReadError("error reading segment reservation", err)
-		}
+	if !dstIA.IsZero() {
+		conditions = append(conditions, "dst_ia = ?")
+		params = append(params, dstIA.IAInt())
 	}
-	return nil, nil
+	if len(conditions) == 0 {
+		return nil, serrors.New("no src or dst ia provided")
+	}
+	condition := fmt.Sprintf("WHERE %s", strings.Join(conditions, " AND "))
+	return getSegReservations(ctx, x.db, condition, params)
 }
 
 // GetSegmentRsvFromPath searches for a segment reservation with the specified path.
-func (x *executor) GetSegmentRsvFromPath(ctx context.Context, path *segment.Path) (
+func (x *executor) GetSegmentRsvFromPath(ctx context.Context, path segment.Path) (
 	*segment.Reservation, error) {
 
-	return nil, nil
+	rsvs, err := getSegReservations(ctx, x.db, "WHERE path = ?", []interface{}{path.ToRaw()})
+	if err != nil {
+		return nil, err
+	}
+	switch len(rsvs) {
+	case 0:
+		return nil, nil
+	case 1:
+		return rsvs[0], nil
+	default:
+		return nil, db.NewDataError("more than 1 segment reservation found for a path", nil,
+			"path", path.String())
+	}
 }
 
 // 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) (
+func (x *executor) GetSegmentRsvsFromIFPair(ctx context.Context, ingress, egress *common.IFIDType) (
 	[]*segment.Reservation, error) {
 
-	return nil, nil
-}
-
-func insertNewSegReservation(ctx context.Context, x db.Sqler, rsv *segment.Reservation,
-	suffix uint32) error {
-	const query = `INSERT INTO seg_reservation (id_as, id_suffix, ingress ,egress,
-		path, src_as, dst_as) VALUES ($1, $2, $3, $4, $5, $6, $7)`
-	_, err := x.ExecContext(ctx, query, rsv.Path.GetSrcIA().A, suffix,
-		rsv.Ingress, rsv.Egress,
-		rsv.Path.ToRaw(), rsv.Path.GetSrcIA().IAInt(), rsv.Path.GetDstIA().IAInt())
-	return err
+	conditions := make([]string, 0, 2)
+	params := make([]interface{}, 0, 2)
+	if ingress != nil {
+		conditions = append(conditions, "ingress = ?")
+		params = append(params, *ingress)
+	}
+	if egress != nil {
+		conditions = append(conditions, "egress = ?")
+		params = append(params, *egress)
+	}
+	if len(conditions) == 0 {
+		return nil, serrors.New("no ingress or egress provided")
+	}
+	condition := fmt.Sprintf("WHERE %s", strings.Join(conditions, " AND "))
+	return getSegReservations(ctx, x.db, condition, params)
 }
 
 // NewSegmentRsv creates a new segment reservation in the DB, with an unused reservation ID.
+// The reservation must contain at least one index.
 // The created ID is set in the reservation pointer argument.
 func (x *executor) NewSegmentRsv(ctx context.Context, rsv *segment.Reservation) error {
 	var err error
@@ -195,31 +229,18 @@ func (x *executor) NewSegmentRsv(ctx context.Context, rsv *segment.Reservation)
 	return db.NewTxError("error inserting segment reservation after 3 retries", err)
 }
 
-// SetActiveIndex updates the active index for the segment reservation.
-func (x *executor) SetSegmentActiveIndex(ctx context.Context, rsv segment.Reservation,
-	idx reservation.IndexNumber) error {
-
-	return nil
-}
-
-// NewSegmentRsvIndex stores a new index for a segment reservation.
-func (x *executor) NewSegmentIndex(ctx context.Context, rsv *segment.Reservation,
-	idx reservation.IndexNumber) error {
-
-	return nil
-}
-
-// UpdateSegmentRsvIndex updates an index of a segment reservation.
-func (x *executor) UpdateSegmentIndex(ctx context.Context, rsv *segment.Reservation,
-	idx reservation.IndexNumber) error {
-
-	return nil
-}
-
-// DeleteSegmentIndex removes the index from the DB. Used in cleanup.
-func (x *executor) DeleteSegmentIndex(ctx context.Context, rsv *segment.Reservation,
-	idx reservation.IndexNumber) error {
-
+func (x *executor) PersistSegmentRsv(ctx context.Context, rsv *segment.Reservation) error {
+	err := db.DoInTx(ctx, x.db, func(ctx context.Context, tx *sql.Tx) error {
+		err := deleteSegmentRsv(ctx, tx, &rsv.ID)
+		if err != nil {
+			return err
+		}
+		suffix := binary.BigEndian.Uint32(rsv.ID.Suffix[:])
+		return insertNewSegReservation(ctx, tx, rsv, suffix)
+	})
+	if err != nil {
+		return db.NewTxError("error persisting reservation", err)
+	}
 	return nil
 }
 
@@ -230,8 +251,8 @@ func (x *executor) DeleteExpiredIndices(ctx context.Context, now time.Time) (int
 }
 
 // DeleteSegmentRsv removes the segment reservation
-func (x *executor) DeleteSegmentRsv(ctx context.Context, ID reservation.SegmentID) error {
-	return nil
+func (x *executor) DeleteSegmentRsv(ctx context.Context, ID *reservation.SegmentID) error {
+	return deleteSegmentRsv(ctx, x.db, ID)
 }
 
 // GetE2ERsvFromID finds the end to end resevation given its ID.
@@ -267,10 +288,10 @@ func (x *executor) DeleteE2EIndex(ctx context.Context, rsv *e2e.Reservation,
 // newSuffix finds a segment reservation ID suffix not being used at the moment. Should be called
 // inside a transaction so the suffix is not used in the meantime, or fail.
 func newSuffix(ctx context.Context, x db.Sqler, ASID addr.AS) (uint32, error) {
-	query := `SELECT MIN(id_suffix)+1 FROM (
-		SELECT 0 AS id_suffix UNION ALL
-		SELECT id_suffix FROM seg_reservation WHERE id_as = $1
-		) WHERE id_suffix+1 NOT IN (SELECT id_suffix FROM seg_reservation WHERE id_as = $1);`
+	const query = `SELECT MIN(id_suffix)+1 FROM (
+			SELECT 0 AS id_suffix UNION ALL
+			SELECT id_suffix FROM seg_reservation WHERE id_as = $1
+		) WHERE id_suffix+1 NOT IN (SELECT id_suffix FROM seg_reservation WHERE id_as = $1)`
 	var suffix uint32
 	err := x.QueryRowContext(ctx, query, uint64(ASID)).Scan(&suffix)
 	switch {
@@ -281,3 +302,155 @@ func newSuffix(ctx context.Context, x db.Sqler, ASID addr.AS) (uint32, error) {
 	}
 	return suffix, nil
 }
+
+func insertNewSegReservation(ctx context.Context, x db.Sqler, rsv *segment.Reservation,
+	suffix uint32) error {
+
+	activeIndex := -1
+	if rsv.ActiveIndex() != nil {
+		activeIndex = int(rsv.ActiveIndex().Idx)
+	}
+	const query = `INSERT INTO seg_reservation (id_as, id_suffix, ingress, egress,
+		path, end_props, traffic_split, src_ia, dst_ia,active_index)
+		VALUES (?, ?,?,?,?,?,?,?,?,?)`
+	res, err := x.ExecContext(ctx, query, rsv.ID.ASID, suffix,
+		rsv.Ingress, rsv.Egress, rsv.Path.ToRaw(), rsv.PathEndProps, rsv.TrafficSplit,
+		rsv.Path.GetSrcIA().IAInt(), rsv.Path.GetDstIA().IAInt(), activeIndex)
+	if err != nil {
+		return err
+	}
+	if len(rsv.Indices) > 0 {
+		rsvRowID, err := res.LastInsertId()
+		if err != nil {
+			return db.NewTxError("cannot obtain last insertion row id", err)
+		}
+		const queryIndexTmpl = `INSERT INTO seg_index (reservation, index_number, expiration, state,
+		min_bw, max_bw, alloc_bw, token) VALUES (?,?,?,?,?,?,?,?)`
+		params := make([]interface{}, 0, 8*len(rsv.Indices))
+		for _, index := range rsv.Indices {
+			params = append(params, rsvRowID, index.Idx,
+				uint32(index.Expiration.Unix()), index.State(), index.MinBW, index.MaxBW,
+				index.AllocBW, index.Token.ToRaw())
+		}
+		q := queryIndexTmpl + strings.Repeat(",(?,?,?,?,?,?,?,?)", len(rsv.Indices)-1)
+		_, err = x.ExecContext(ctx, q, params...)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+type rsvFields struct {
+	RowID        int
+	AsID         uint64
+	Suffix       uint32
+	Ingress      common.IFIDType
+	Egress       common.IFIDType
+	Path         []byte
+	EndProps     int
+	TrafficSplit int
+	ActiveIndex  int
+}
+
+func getSegReservations(ctx context.Context, x db.Sqler, condition string, params []interface{}) (
+	[]*segment.Reservation, error) {
+
+	const queryTmpl = `SELECT row_id,id_as,id_suffix,ingress,egress,path,
+		end_props,traffic_split,active_index
+		FROM seg_reservation %s`
+	query := fmt.Sprintf(queryTmpl, condition)
+
+	rows, err := x.QueryContext(ctx, query, params...)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+
+	reservationFields := []*rsvFields{}
+	for rows.Next() {
+		var f rsvFields
+		err := rows.Scan(&f.RowID, &f.AsID, &f.Suffix, &f.Ingress, &f.Egress, &f.Path,
+			&f.EndProps, &f.TrafficSplit, &f.ActiveIndex)
+		if err != nil {
+			return nil, err
+		}
+		reservationFields = append(reservationFields, &f)
+	}
+	reservations := []*segment.Reservation{}
+	for _, rf := range reservationFields {
+		rsv, err := buildSegRsvFromFields(ctx, x, rf)
+		if err != nil {
+			return nil, err
+		}
+		reservations = append(reservations, rsv)
+	}
+	return reservations, nil
+}
+
+// builds a segment.Reservation in memory from the fields and indices.
+func buildSegRsvFromFields(ctx context.Context, x db.Sqler, fields *rsvFields) (
+	*segment.Reservation, error) {
+
+	indices, err := getSegIndices(ctx, x, fields.RowID)
+	if err != nil {
+		return nil, err
+	}
+	rsv := segment.NewReservation()
+	rsv.ID.ASID = addr.AS(fields.AsID)
+	binary.BigEndian.PutUint32(rsv.ID.Suffix[:], fields.Suffix)
+	rsv.Ingress = fields.Ingress
+	rsv.Egress = fields.Egress
+	p, err := segment.NewPathFromRaw(fields.Path)
+	if err != nil {
+		return nil, err
+	}
+	rsv.Path = p
+	rsv.PathEndProps = reservation.PathEndProps(fields.EndProps)
+	rsv.TrafficSplit = reservation.SplitCls(fields.TrafficSplit)
+	rsv.Indices = *indices
+	if fields.ActiveIndex != -1 {
+		if err := rsv.SetIndexActive(reservation.IndexNumber(fields.ActiveIndex)); err != nil {
+			return nil, err
+		}
+	}
+	return rsv, nil
+}
+
+// the rowID argument is the reservation row ID the indices belong to.
+func getSegIndices(ctx context.Context, x db.Sqler, rowID int) (*segment.Indices, error) {
+	const query = `SELECT index_number,expiration,state,min_bw,max_bw,alloc_bw,token
+		FROM seg_index WHERE reservation=?`
+	rows, err := x.QueryContext(ctx, query, rowID)
+	if err != nil {
+		return nil, db.NewReadError("cannot list indices", err)
+	}
+
+	indices := segment.Indices{}
+	var idx, expiration, state, minBW, maxBW, allocBW int32
+	var token []byte
+	for rows.Next() {
+		err := rows.Scan(&idx, &expiration, &state, &minBW, &maxBW, &allocBW, &token)
+		if err != nil {
+			return nil, db.NewReadError("could not get index values", err)
+		}
+		tok, err := reservation.TokenFromRaw(token)
+		if err != nil {
+			return nil, db.NewReadError("invalid stored token", err)
+		}
+		index := segment.NewIndex(reservation.IndexNumber(idx),
+			time.Unix(int64(expiration), 0), segment.IndexState(state), reservation.BWCls(minBW),
+			reservation.BWCls(maxBW), reservation.BWCls(allocBW), tok)
+		indices = append(indices, *index)
+	}
+	// sort indices so they are consecutive modulo 16
+	indices.Sort()
+	return &indices, nil
+}
+
+func deleteSegmentRsv(ctx context.Context, x db.Sqler, rsvID *reservation.SegmentID) error {
+	const query = `DELETE FROM seg_reservation WHERE id_as = ? AND id_suffix = ?`
+	suffix := binary.BigEndian.Uint32(rsvID.Suffix[:])
+	_, err := x.ExecContext(ctx, query, rsvID.ASID, suffix)
+	return err
+}
diff --git a/go/cs/reservation/sqlite/schema.go b/go/cs/reservation/sqlite/schema.go
index 02e1fe5e2..4d1fa9b20 100644
--- a/go/cs/reservation/sqlite/schema.go
+++ b/go/cs/reservation/sqlite/schema.go
@@ -20,7 +20,6 @@ const (
 	// to prevent data corruption between incompatible database schemas.
 	SchemaVersion = 1
 	// Schema is the SQLite database layout.
-	// TODO(juagargi) explain the DB structure here or in the design markdown.
 	// TODO(juagargi) create appropriate SQL indices.
 	Schema = `CREATE TABLE seg_reservation (
 		row_id	INTEGER,
@@ -29,10 +28,14 @@ const (
 		ingress	INTEGER NOT NULL,
 		egress	INTEGER NOT NULL,
 		path	BLOB,
-		src_as INTEGER,
-		dst_as INTEGER,
+		end_props	INTEGER NOT NULL,
+		traffic_split	INTEGER NOT NULL,
+		src_ia INTEGER,
+		dst_ia INTEGER,
+		active_index	INTEGER NOT NULL,
 		PRIMARY KEY(row_id),
-		UNIQUE(id_as,id_suffix)
+		UNIQUE(id_as,id_suffix),
+		UNIQUE(path)
 	);
 	CREATE TABLE seg_index (
 		reservation	INTEGER NOT NULL,
@@ -43,7 +46,7 @@ const (
 		max_bw	INTEGER NOT NULL,
 		alloc_bw	INTEGER NOT NULL,
 		token	BLOB,
-		PRIMARY KEY(Reservation,index_number),
+		PRIMARY KEY(reservation,index_number),
 		FOREIGN KEY(reservation) REFERENCES seg_reservation(row_id) ON DELETE CASCADE
 	);
 	CREATE TABLE e2e_reservation (
diff --git a/go/cs/reservationstorage/backend/db.go b/go/cs/reservationstorage/backend/db.go
index 2afdf2467..0ca2e542d 100644
--- a/go/cs/reservationstorage/backend/db.go
+++ b/go/cs/reservationstorage/backend/db.go
@@ -30,11 +30,11 @@ import (
 
 // ReserverOnly has the methods available to the AS that starts the reservation.
 type ReserverOnly interface {
-	// GetSegmentRsvFromSrcDstAS returns all reservations that start at src AS and end in dst AS.
-	GetSegmentRsvFromSrcDstAS(ctx context.Context, srcIA, dstIA addr.IA) (
+	// GetSegmentRsvsFromSrcDstIA returns all reservations that start at src AS and end in dst AS.
+	GetSegmentRsvsFromSrcDstIA(ctx context.Context, srcIA, dstIA addr.IA) (
 		[]*segment.Reservation, error)
 	// GetSegmentRsvFromPath searches for a segment reservation with the specified path.
-	GetSegmentRsvFromPath(ctx context.Context, path *segment.Path) (
+	GetSegmentRsvFromPath(ctx context.Context, path segment.Path) (
 		*segment.Reservation, error)
 
 	// NewSegmentRsv creates a new segment reservation in the DB, with an unused reservation ID.
@@ -46,32 +46,20 @@ type ReserverOnly interface {
 type TransitOnly interface {
 	// 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) (
+	GetSegmentRsvsFromIFPair(ctx context.Context, ingress, egress *common.IFIDType) (
 		[]*segment.Reservation, error)
 }
 
 // ReserverAndTransit contains the functionality for any AS that has a COLIBRI service.
 type ReserverAndTransit interface {
 	// GetSegmentRsvFromID will return the reservation with that ID.
-	// If an IndexNumber is specified it will populate its indices only with that one.
-	// If the ID is not found, or the index (if specified) is not found, an error will be returned.
 	// Used by setup/renew req/resp. and any request.
-	GetSegmentRsvFromID(ctx context.Context, ID reservation.SegmentID,
-		idx *reservation.IndexNumber) (*segment.Reservation, error)
-	// SetActiveIndex updates the active index. Used in index confirmation.
-	SetSegmentActiveIndex(ctx context.Context, rsv segment.Reservation,
-		idx reservation.IndexNumber) error
-	// NewSegmentRsvIndex stores a new index for a segment reservation. Used in setup/renew.
-	NewSegmentIndex(ctx context.Context, rsv *segment.Reservation,
-		idx reservation.IndexNumber) error
-	// UpdateSegmentRsvIndex updates an index of a segment rsv. Used in setup/renew response.
-	UpdateSegmentIndex(ctx context.Context, rsv *segment.Reservation,
-		idx reservation.IndexNumber) error
-	// DeleteSegmentIndex removes the index from the DB. Used in cleanup.
-	DeleteSegmentIndex(ctx context.Context, rsv *segment.Reservation,
-		idx reservation.IndexNumber) error
+	GetSegmentRsvFromID(ctx context.Context, ID *reservation.SegmentID) (
+		*segment.Reservation, error)
+	// PersistSegmentRsv ensures the DB contains the reservation as represented in rsv.
+	PersistSegmentRsv(ctx context.Context, rsv *segment.Reservation) error
 	// DeleteSegmentRsv removes the segment reservation. Used in teardown.
-	DeleteSegmentRsv(ctx context.Context, ID reservation.SegmentID) error
+	DeleteSegmentRsv(ctx context.Context, ID *reservation.SegmentID) error
 
 	// DeleteExpiredIndices will remove expired indices from the DB. If a reservation is left
 	// without any index after removing the expired ones, it will also be removed.
diff --git a/go/lib/colibri/reservation/types.go b/go/lib/colibri/reservation/types.go
index 6910e9b75..4cd4ffb64 100644
--- a/go/lib/colibri/reservation/types.go
+++ b/go/lib/colibri/reservation/types.go
@@ -135,6 +135,10 @@ func TickFromTime(t time.Time) Tick {
 	return Tick(util.TimeToSecs(t) / 4)
 }
 
+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 .
 type BWCls uint8
 
@@ -347,6 +351,9 @@ func (t *Token) Validate() error {
 
 // TokenFromRaw builds a Token from the passed bytes buffer.
 func TokenFromRaw(raw []byte) (*Token, error) {
+	if raw == nil {
+		return nil, nil
+	}
 	rawHFs := len(raw) - InfoFieldLen
 	if rawHFs < 0 || rawHFs%spath.HopFieldLength != 0 {
 		return nil, serrors.New("buffer too small", "min_size", InfoFieldLen,
@@ -359,7 +366,9 @@ func TokenFromRaw(raw []byte) (*Token, error) {
 	}
 	t := Token{
 		InfoField: *inf,
-		HopFields: make([]spath.HopField, numHFs),
+	}
+	if numHFs > 0 {
+		t.HopFields = make([]spath.HopField, numHFs)
 	}
 	for i := 0; i < numHFs; i++ {
 		offset := InfoFieldLen + i*spath.HopFieldLength
@@ -374,6 +383,9 @@ func TokenFromRaw(raw []byte) (*Token, error) {
 
 // Len returns the number of bytes of this token if serialized.
 func (t *Token) Len() int {
+	if t == nil {
+		return 0
+	}
 	return InfoFieldLen + len(t.HopFields)*spath.HopFieldLength
 }
 
@@ -393,3 +405,12 @@ func (t *Token) Read(b []byte) (int, error) {
 	}
 	return offset, nil
 }
+
+// ToRaw returns the serial representation of the Token.
+func (t *Token) ToRaw() []byte {
+	buff := make([]byte, t.Len())
+	if t != nil {
+		t.Read(buff) // safely ignore errors as they can only come from buffer size
+	}
+	return buff
+}


Test patch:
diff --git a/go/cs/reservation/e2e/index.go b/go/cs/reservation/e2e/index.go
index 6540c8f79..61a55a608 100644
--- a/go/cs/reservation/e2e/index.go
+++ b/go/cs/reservation/e2e/index.go
@@ -27,7 +27,7 @@ type Index struct {
 	Idx        reservation.IndexNumber
 	Expiration time.Time
 	AllocBW    reservation.BWCls // also present in the token
-	Token      reservation.Token
+	Token      *reservation.Token
 }
 
 type Indices []Index
@@ -37,3 +37,5 @@ var _ base.IndicesInterface = (*Indices)(nil)
 func (idxs Indices) Len() int                                     { return len(idxs) }
 func (idxs Indices) GetIndexNumber(i int) reservation.IndexNumber { return idxs[i].Idx }
 func (idxs Indices) GetExpiration(i int) time.Time                { return idxs[i].Expiration }
+func (idxs Indices) GetAllocBW(i int) reservation.BWCls           { return idxs[i].AllocBW }
+func (idxs Indices) GetToken(i int) *reservation.Token            { return idxs[i].Token }
diff --git a/go/cs/reservation/index_test.go b/go/cs/reservation/index_test.go
new file mode 100644
index 000000000..89b65dc19
--- /dev/null
+++ b/go/cs/reservation/index_test.go
@@ -0,0 +1,150 @@
+// 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 (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/scionproto/scion/go/lib/colibri/reservation"
+)
+
+func TestValidateIndices(t *testing.T) {
+	idxs := make(Indices, 0)
+	// up to 3 indices per expiration time
+	expTime := time.Unix(1, 0)
+	idx, err := idxs.NewIndex(expTime)
+	require.NoError(t, err)
+	require.Equal(t, reservation.IndexNumber(0), idx)
+	idx, err = idxs.NewIndex(expTime)
+	require.NoError(t, err)
+	require.Equal(t, reservation.IndexNumber(1), idx)
+	idx, err = idxs.NewIndex(expTime)
+	require.NoError(t, err)
+	require.Equal(t, reservation.IndexNumber(2), idx)
+	idxs = idxs[:1]
+	// exp time is less
+	_, err = idxs.NewIndex(expTime.Add(-1 * time.Millisecond))
+	require.Error(t, err)
+	require.Len(t, idxs, 1)
+	// exp time is same
+	idx, err = idxs.NewIndex(expTime)
+	require.NoError(t, err)
+	require.Len(t, idxs, 2)
+	require.True(t, idxs[1].Idx == idx)
+	require.True(t, idxs[1].Expiration == expTime)
+	require.Equal(t, reservation.IndexNumber(1), idx)
+	idx, err = idxs.NewIndex(expTime)
+	require.NoError(t, err)
+	require.Len(t, idxs, 3)
+	require.True(t, idxs[2].Idx == idx)
+	require.True(t, idxs[2].Expiration == expTime)
+	require.Equal(t, reservation.IndexNumber(2), idx)
+	// too many indices for the same exp time
+	_, err = idxs.NewIndex(expTime)
+	require.Error(t, err)
+	require.Len(t, idxs, 3)
+	// exp time is greater
+	expTime = expTime.Add(time.Second)
+	idx, err = idxs.NewIndex(expTime)
+	require.NoError(t, err)
+	require.Len(t, idxs, 4)
+	require.True(t, idxs[3].Idx == idx)
+	require.True(t, idxs[3].Expiration == expTime)
+	require.Equal(t, reservation.IndexNumber(3), idx)
+	// index number rollover
+	idxs = Indices{}
+	idxs.NewIndex(expTime)
+	require.Len(t, idxs, 1)
+	idxs[0].Idx = idxs[0].Idx.Sub(1)
+	idx, err = idxs.NewIndex(expTime)
+	require.NoError(t, err)
+	require.True(t, idxs[1].Idx == idx)
+	require.True(t, idxs[1].Expiration == expTime)
+	require.Equal(t, reservation.IndexNumber(0), idx)
+	// more than 16 indices
+	idxs = Indices{}
+	for i := 0; i < 16; i++ {
+		expTime := time.Unix(int64(i), 0)
+		_, err = idxs.NewIndex(expTime)
+		require.NoError(t, err)
+	}
+	require.Len(t, idxs, 16)
+	_, err = idxs.NewIndex(expTime.Add(time.Hour))
+	require.Error(t, err)
+	// exp time is before
+	idxs = Indices{}
+	expTime = time.Unix(1, 0)
+	idxs.NewIndex(expTime)
+	idxs.NewIndex(expTime)
+	idxs[1].Expiration = expTime.Add(-1 * time.Second)
+	err = ValidateIndices(idxs)
+	require.Error(t, err)
+	// non consecutive indices
+	idxs = Indices{}
+	expTime = time.Unix(1, 0)
+	idxs.NewIndex(expTime)
+	idxs.NewIndex(expTime)
+	idxs[1].Idx = 2
+	err = ValidateIndices(idxs)
+	require.Error(t, err)
+	// more than three indices per exp time
+	idxs = Indices{}
+	idxs.NewIndex(expTime)
+	idxs.NewIndex(expTime)
+	idxs.NewIndex(expTime)
+	require.Len(t, idxs, 3)
+	err = ValidateIndices(idxs)
+	require.NoError(t, err)
+	_, err = idxs.NewIndex(expTime)
+	require.Error(t, err)
+}
+
+type Index struct {
+	Idx        reservation.IndexNumber
+	Expiration time.Time
+}
+
+type Indices []Index
+
+var _ IndicesInterface = (*Indices)(nil)
+
+func (idxs Indices) Len() int                                     { return len(idxs) }
+func (idxs Indices) GetIndexNumber(i int) reservation.IndexNumber { return idxs[i].Idx }
+func (idxs Indices) GetExpiration(i int) time.Time                { return idxs[i].Expiration }
+func (idxs Indices) GetAllocBW(i int) reservation.BWCls           { return reservation.BWCls(0) }
+func (idxs Indices) GetToken(i int) *reservation.Token            { return nil }
+
+func (idxs *Indices) NewIndex(expTime time.Time) (reservation.IndexNumber, error) {
+	idx := reservation.IndexNumber(0)
+	if len(*idxs) > 0 {
+		idx = (*idxs)[len(*idxs)-1].Idx.Add(1)
+	}
+	index := Index{
+		Idx:        idx,
+		Expiration: expTime,
+	}
+	newIndices := make(Indices, len(*idxs)+1)
+	copy(newIndices, *idxs)
+	newIndices[len(newIndices)-1] = index
+	err := ValidateIndices(newIndices)
+	if err != nil {
+		return reservation.IndexNumber(0), err
+	}
+	*idxs = newIndices
+	return idx, nil
+}
diff --git a/go/cs/reservation/reservationdbtest/BUILD.bazel b/go/cs/reservation/reservationdbtest/BUILD.bazel
index fce894ec7..14bf3e034 100644
--- a/go/cs/reservation/reservationdbtest/BUILD.bazel
+++ b/go/cs/reservation/reservationdbtest/BUILD.bazel
@@ -9,6 +9,9 @@ go_library(
         "//go/cs/reservation/segment:go_default_library",
         "//go/cs/reservation/segmenttest: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/xtest:go_default_library",
         "@com_github_stretchr_testify//require:go_default_library",
     ],
diff --git a/go/cs/reservation/reservationdbtest/reservationdbtest.go b/go/cs/reservation/reservationdbtest/reservationdbtest.go
index c97eba3dc..1afe63a59 100644
--- a/go/cs/reservation/reservationdbtest/reservationdbtest.go
+++ b/go/cs/reservation/reservationdbtest/reservationdbtest.go
@@ -17,12 +17,16 @@ package reservationdbtest
 import (
 	"context"
 	"testing"
+	"time"
 
 	"github.com/stretchr/testify/require"
 
 	"github.com/scionproto/scion/go/cs/reservation/segment"
 	"github.com/scionproto/scion/go/cs/reservation/segmenttest"
 	"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/xtest"
 )
 
@@ -33,7 +37,13 @@ type TestableDB interface {
 
 func TestDB(t *testing.T, db TestableDB) {
 	tests := map[string]func(context.Context, *testing.T, backend.DB){
-		"insert segment reservations create ID": testNewSegmentReservation,
+		"insert segment reservations create ID": testNewSegmentRsv,
+		"persist segment reservation":           testPersistSegmentRsv,
+		"get segment reservation from ID":       testGetSegmentRsvFromID,
+		"get segment reservations from src/dst": testGetSegmentRsvsFromSrcDstIA,
+		"get segment reservation from path":     testGetSegmentRsvFromPath,
+		"get segment reservation from IF pair":  testGetSegmentRsvsFromIFPair,
+		"delete segment reservation":            testDeleteSegmentRsv,
 	}
 	for name, test := range tests {
 		t.Run(name, func(t *testing.T) {
@@ -44,12 +54,296 @@ func TestDB(t *testing.T, db TestableDB) {
 	}
 }
 
-func testNewSegmentReservation(ctx context.Context, t *testing.T, db backend.DB) {
-	r := segment.NewReservation()
-	r.Egress = 1
+func testNewSegmentRsv(ctx context.Context, t *testing.T, db backend.DB) {
+	r := newTestReservation(t)
 	r.Path = segmenttest.NewPathFromComponents(0, "1-ff00:0:1", 1, 1, "1-ff00:0:2", 0)
-	r.ID.ASID = xtest.MustParseAS("ff00:0:1")
+	r.Indices = segment.Indices{}
+	// no indices
 	err := db.NewSegmentRsv(ctx, r)
 	require.NoError(t, err)
 	require.Equal(t, xtest.MustParseHexString("00000001"), r.ID.Suffix[:])
+	rsv, err := db.GetSegmentRsvFromID(ctx, &r.ID)
+	require.NoError(t, err)
+	require.Equal(t, r, rsv)
+	// at least one index, and change path
+	token := newToken()
+	_, err = r.NewIndexFromToken(token, 0, 0)
+	require.NoError(t, err)
+	r.Path = segmenttest.NewPathFromComponents(1, "1-ff00:0:1", 2, 1, "1-ff00:0:2", 0)
+	err = db.NewSegmentRsv(ctx, r)
+	require.NoError(t, err)
+	require.Equal(t, xtest.MustParseHexString("00000002"), r.ID.Suffix[:])
+	rsv, err = db.GetSegmentRsvFromID(ctx, &r.ID)
+	require.NoError(t, err)
+	require.Equal(t, r, rsv)
+	// same path should fail
+	err = db.NewSegmentRsv(ctx, r)
+	require.Error(t, err)
+	// different ASID should start with the lowest suffix
+	r = newTestReservation(t)
+	r.ID.ASID = xtest.MustParseAS("ff00:1234:1")
+	err = db.NewSegmentRsv(ctx, r)
+	require.NoError(t, err)
+	require.Equal(t, xtest.MustParseHexString("00000001"), r.ID.Suffix[:])
+	rsv, err = db.GetSegmentRsvFromID(ctx, &r.ID)
+	require.NoError(t, err)
+	require.Equal(t, r, rsv)
+}
+
+func testPersistSegmentRsv(ctx context.Context, t *testing.T, db backend.DB) {
+	r := newTestReservation(t)
+	for i := int64(1); i < 10; i++ {
+		_, err := r.NewIndexAtSource(time.Unix(i, 0), 0, 0, 0, 0, reservation.CorePath)
+		require.NoError(t, err)
+	}
+	require.Len(t, r.Indices, 10)
+	err := db.NewSegmentRsv(ctx, r)
+	require.NoError(t, err)
+	rsv, err := db.GetSegmentRsvFromID(ctx, &r.ID)
+	require.NoError(t, err)
+	require.Equal(t, r, rsv)
+	// now remove one index
+	err = r.RemoveIndex(0)
+	require.NoError(t, err)
+	err = db.PersistSegmentRsv(ctx, r)
+	require.NoError(t, err)
+	rsv, err = db.GetSegmentRsvFromID(ctx, &r.ID)
+	require.NoError(t, err)
+	require.Equal(t, r, rsv)
+	// change ID
+	r.ID.ASID = xtest.MustParseAS("ff00:1:12")
+	copy(r.ID.Suffix[:], xtest.MustParseHexString("beefcafe"))
+	err = db.PersistSegmentRsv(ctx, r)
+	require.NoError(t, err)
+	rsv, err = db.GetSegmentRsvFromID(ctx, &r.ID)
+	require.NoError(t, err)
+	require.Equal(t, r, rsv)
+	// change attributes
+	r.Ingress = 3
+	r.Egress = 4
+	r.Path = segmenttest.NewPathFromComponents(3, "1-ff00:0:1", 11, 1, "1-ff00:0:2", 0)
+	err = db.PersistSegmentRsv(ctx, r)
+	require.NoError(t, err)
+	rsv, err = db.GetSegmentRsvFromID(ctx, &r.ID)
+	require.NoError(t, err)
+	require.Equal(t, r, rsv)
+	// remove 7 more indices, remains 1 index
+	err = r.RemoveIndex(8)
+	require.NoError(t, err)
+	r.Indices[0].Expiration = time.Unix(12345, 0)
+	r.Indices[0].MinBW = 10
+	r.Indices[0].MaxBW = 11
+	r.Indices[0].AllocBW = 12
+	r.Indices[0].Token = newToken() // change the token
+	r.Indices[0].Token.BWCls = 8
+	err = r.SetIndexConfirmed(r.Indices[0].Idx)
+	require.NoError(t, err)
+	err = r.SetIndexActive(r.Indices[0].Idx)
+	require.NoError(t, err)
+	err = db.PersistSegmentRsv(ctx, r)
+	require.NoError(t, err)
+	rsv, err = db.GetSegmentRsvFromID(ctx, &r.ID)
+	require.NoError(t, err)
+	require.Equal(t, r, rsv)
+}
+
+func testGetSegmentRsvFromID(ctx context.Context, t *testing.T, db backend.DB) {
+	r := newTestReservation(t)
+	err := db.NewSegmentRsv(ctx, r)
+	require.NoError(t, err)
+	// create new index
+	expTime := time.Unix(1, 0)
+	_, err = r.NewIndexAtSource(expTime, 0, 0, 0, 0, reservation.CorePath)
+	require.NoError(t, err)
+	err = db.PersistSegmentRsv(ctx, r)
+	require.NoError(t, err)
+	r2, err := db.GetSegmentRsvFromID(ctx, &r.ID)
+	require.NoError(t, err)
+	require.Equal(t, r, r2)
+	// 14 more indices for a total of 16
+	require.Len(t, r.Indices, 2)
+	for i := 2; i < 16; i++ {
+		expTime = time.Unix(int64(i), 0)
+		_, err = r.NewIndexAtSource(expTime, reservation.BWCls(i), reservation.BWCls(i),
+			reservation.BWCls(i), 0, reservation.CorePath)
+		require.NoError(t, err)
+	}
+	require.Len(t, r.Indices, 16)
+	err = db.PersistSegmentRsv(ctx, r)
+	require.NoError(t, err)
+	r2, err = db.GetSegmentRsvFromID(ctx, &r.ID)
+	require.NoError(t, err)
+	require.Equal(t, r, r2)
+	// wrong ID
+	ID := r.ID
+	ID.ASID++
+	r2, err = db.GetSegmentRsvFromID(ctx, &ID)
+	require.NoError(t, err)
+	require.Nil(t, r2)
+}
+
+func testGetSegmentRsvsFromSrcDstIA(ctx context.Context, t *testing.T, db backend.DB) {
+	r := newTestReservation(t)
+	r.Path = segmenttest.NewPathFromComponents(0, "1-ff00:0:1", 1, 1, "1-ff00:0:2", 0)
+	err := db.NewSegmentRsv(ctx, r)
+	require.NoError(t, err)
+	rsvs, err := db.GetSegmentRsvsFromSrcDstIA(ctx, r.Path.GetSrcIA(), r.Path.GetDstIA())
+	require.NoError(t, err)
+	require.Len(t, rsvs, 1)
+	require.Equal(t, r, rsvs[0])
+	// another reservation with same source and destination
+	r2 := newTestReservation(t)
+	r2.Path = segmenttest.NewPathFromComponents(0, "1-ff00:0:1", 1, 2, "1-ff00:0:2", 0)
+	err = db.NewSegmentRsv(ctx, r2)
+	require.NoError(t, err)
+	rsvs, err = db.GetSegmentRsvsFromSrcDstIA(ctx, r.Path.GetSrcIA(), r.Path.GetDstIA())
+	require.NoError(t, err)
+	require.Len(t, rsvs, 2)
+	// compare without order
+	require.ElementsMatch(t, rsvs, []*segment.Reservation{r, r2})
+	// one more with same source different destination
+	r3 := newTestReservation(t)
+	r3.Path = segmenttest.NewPathFromComponents(0, "1-ff00:0:1", 1, 1, "1-ff00:0:3", 0)
+	err = db.NewSegmentRsv(ctx, r3)
+	require.NoError(t, err)
+	rsvs, err = db.GetSegmentRsvsFromSrcDstIA(ctx, r.Path.GetSrcIA(), r.Path.GetDstIA())
+	require.NoError(t, err)
+	require.Len(t, rsvs, 2)
+	require.ElementsMatch(t, rsvs, []*segment.Reservation{r, r2})
+	rsvs, err = db.GetSegmentRsvsFromSrcDstIA(ctx, r.Path.GetSrcIA(), addr.IA{})
+	require.NoError(t, err)
+	require.Len(t, rsvs, 3)
+	require.ElementsMatch(t, rsvs, []*segment.Reservation{r, r2, r3})
+	// another reservation with unique source but same destination as r3
+	r4 := newTestReservation(t)
+	r4.Path = segmenttest.NewPathFromComponents(0, "1-ff00:0:4", 1, 1, "1-ff00:0:3", 0)
+	err = db.NewSegmentRsv(ctx, r4)
+	require.NoError(t, err)
+	rsvs, err = db.GetSegmentRsvsFromSrcDstIA(ctx, r.Path.GetSrcIA(), r.Path.GetDstIA())
+	require.NoError(t, err)
+	require.Len(t, rsvs, 2)
+	require.ElementsMatch(t, rsvs, []*segment.Reservation{r, r2})
+	rsvs, err = db.GetSegmentRsvsFromSrcDstIA(ctx, r.Path.GetSrcIA(), addr.IA{})
+	require.NoError(t, err)
+	require.Len(t, rsvs, 3)
+	require.ElementsMatch(t, rsvs, []*segment.Reservation{r, r2, r3})
+	rsvs, err = db.GetSegmentRsvsFromSrcDstIA(ctx, addr.IA{}, r.Path.GetDstIA())
+	require.NoError(t, err)
+	require.Len(t, rsvs, 2)
+	require.ElementsMatch(t, rsvs, []*segment.Reservation{r, r2})
+	rsvs, err = db.GetSegmentRsvsFromSrcDstIA(ctx, addr.IA{}, r3.Path.GetDstIA())
+	require.NoError(t, err)
+	require.Len(t, rsvs, 2)
+	require.ElementsMatch(t, rsvs, []*segment.Reservation{r3, r4})
+}
+
+func testGetSegmentRsvFromPath(ctx context.Context, t *testing.T, db backend.DB) {
+	r1 := newTestReservation(t)
+	r1.Path = segmenttest.NewPathFromComponents(0, "1-ff00:0:1", 1, 1, "1-ff00:0:2", 0)
+	err := db.NewSegmentRsv(ctx, r1)
+	require.NoError(t, err)
+	r2 := newTestReservation(t)
+	r2.Path = segmenttest.NewPathFromComponents(0, "1-ff00:0:1", 1, 1, "1-ff00:0:3", 0)
+	err = db.NewSegmentRsv(ctx, r2)
+	require.NoError(t, err)
+	// retrieve
+	r, err := db.GetSegmentRsvFromPath(ctx, r1.Path)
+	require.NoError(t, err)
+	require.Equal(t, r1, r)
+	r, err = db.GetSegmentRsvFromPath(ctx, r2.Path)
+	require.NoError(t, err)
+	require.Equal(t, r2, r)
+}
+
+func testGetSegmentRsvsFromIFPair(ctx context.Context, t *testing.T, db backend.DB) {
+	// insert in1,e1 ; in2,e1 ; in1,e2
+	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)
+	// query with a specific pair
+	rsvs, err := db.GetSegmentRsvsFromIFPair(ctx, &r1.Ingress, &r1.Egress)
+	require.NoError(t, err)
+	require.Len(t, rsvs, 1)
+	expected := []*segment.Reservation{r1}
+	require.ElementsMatch(t, expected, rsvs)
+	// any ingress
+	rsvs, err = db.GetSegmentRsvsFromIFPair(ctx, nil, &r1.Egress)
+	require.NoError(t, err)
+	require.Len(t, rsvs, 2)
+	expected = []*segment.Reservation{r1, r2}
+	require.ElementsMatch(t, expected, rsvs)
+	// any egress
+	rsvs, err = db.GetSegmentRsvsFromIFPair(ctx, &r1.Ingress, nil)
+	require.NoError(t, err)
+	require.Len(t, rsvs, 2)
+	expected = []*segment.Reservation{r1, r3}
+	require.ElementsMatch(t, expected, rsvs)
+	// no matches
+	var inexistentIngress common.IFIDType = 222
+	rsvs, err = db.GetSegmentRsvsFromIFPair(ctx, &inexistentIngress, nil)
+	require.NoError(t, err)
+	require.Len(t, rsvs, 0)
+	// bad query
+	_, err = db.GetSegmentRsvsFromIFPair(ctx, nil, nil)
+	require.Error(t, err)
+}
+
+func testDeleteSegmentRsv(ctx context.Context, t *testing.T, db backend.DB) {
+	r := newTestReservation(t)
+	err := db.NewSegmentRsv(ctx, r)
+	require.NoError(t, err)
+	err = db.DeleteSegmentRsv(ctx, &r.ID)
+	require.NoError(t, err)
+	rsv, err := db.GetSegmentRsvFromID(ctx, &r.ID)
+	require.NoError(t, err)
+	require.Nil(t, rsv)
+	// with no indices
+	r = newTestReservation(t)
+	r.Indices = segment.Indices{}
+	err = db.NewSegmentRsv(ctx, r)
+	require.NoError(t, err)
+	err = db.DeleteSegmentRsv(ctx, &r.ID)
+	require.NoError(t, err)
+	rsv, err = db.GetSegmentRsvFromID(ctx, &r.ID)
+	require.NoError(t, err)
+	require.Nil(t, rsv)
+}
+
+// newToken just returns a token that can be serialized. This one has two HopFields.
+func newToken() *reservation.Token {
+	t, err := reservation.TokenFromRaw(xtest.MustParseHexString(
+		"0000000000040500003f001002bad1ce003f001002facade"))
+	if err != nil {
+		panic("invalid serialized token")
+	}
+	return t
+}
+
+func newTestReservation(t *testing.T) *segment.Reservation {
+	t.Helper()
+	r := segment.NewReservation()
+	r.Path = segment.Path{}
+	r.ID.ASID = xtest.MustParseAS("ff00:0:1")
+	r.Ingress = 0
+	r.Egress = 1
+	r.TrafficSplit = 3
+	r.PathEndProps = reservation.EndLocal | reservation.StartLocal
+	expTime := time.Unix(1, 0)
+	_, err := r.NewIndexAtSource(expTime, 1, 3, 2, 5, reservation.CorePath)
+	require.NoError(t, err)
+	err = r.SetIndexConfirmed(0)
+	require.NoError(t, err)
+	return r
 }
diff --git a/go/cs/reservation/segment/index_test.go b/go/cs/reservation/segment/index_test.go
new file mode 100644
index 000000000..4dfa00faf
--- /dev/null
+++ b/go/cs/reservation/segment/index_test.go
@@ -0,0 +1,71 @@
+// 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_test
+
+import (
+	"testing"
+	"time"
+
+	"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/lib/colibri/reservation"
+)
+
+func TestIndicesSort(t *testing.T) {
+	indices := newIndices(2, 3, 1)
+	indices.Sort()
+	require.Len(t, indices, 3)
+	checkIndicesSorted(t, indices)
+	// one element
+	indices = newIndices(2)
+	indices.Sort()
+	require.Len(t, indices, 1)
+	require.Equal(t, 2, int(indices[0].Idx))
+	// empty
+	indices = segment.Indices{}
+	indices.Sort()
+	require.Len(t, indices, 0)
+	// wrap around
+	indices = newIndices(0, 1, 15)
+	indices.Sort()
+	require.Len(t, indices, 3)
+	checkIndicesSorted(t, indices)
+	// full 16 elements
+	indices = newIndices(14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)
+	indices.Sort()
+	require.Len(t, indices, 16)
+	checkIndicesSorted(t, indices)
+}
+
+func newIndices(idxs ...int) segment.Indices {
+	indices := make(segment.Indices, len(idxs))
+	for i, idx := range idxs {
+		expTime := time.Unix(int64(i/3+1), 0)
+		idx := segment.NewIndex(reservation.IndexNumber(idx), expTime,
+			segment.IndexPending, reservation.BWCls(1), reservation.BWCls(1), reservation.BWCls(1),
+			nil)
+		indices[i] = *idx
+	}
+	return indices
+}
+
+func checkIndicesSorted(t *testing.T, idxs segment.Indices) {
+	t.Helper()
+	// validate according to valid indices criteria
+	err := base.ValidateIndices(idxs)
+	require.NoError(t, err)
+}
diff --git a/go/cs/reservation/segment/path_test.go b/go/cs/reservation/segment/path_test.go
index f3d83b94a..a46e867a8 100644
--- a/go/cs/reservation/segment/path_test.go
+++ b/go/cs/reservation/segment/path_test.go
@@ -174,3 +174,8 @@ func TestToFromBinary(t *testing.T) {
 	p = make(segment.Path, 0)
 	require.Empty(t, p.ToRaw())
 }
+
+func TestString(t *testing.T) {
+	p := segmenttest.NewPathFromComponents(0, "1-ff00:0:1", 1, 1, "1-ff00:0:2", 0)
+	require.Equal(t, "0 1-ff00:0:1 1>1 1-ff00:0:2 0", p.String())
+}
diff --git a/go/cs/reservation/segment/reservation_test.go b/go/cs/reservation/segment/reservation_test.go
index 1ee97f7ac..915713a85 100644
--- a/go/cs/reservation/segment/reservation_test.go
+++ b/go/cs/reservation/segment/reservation_test.go
@@ -25,150 +25,101 @@ import (
 	"github.com/scionproto/scion/go/lib/colibri/reservation"
 )
 
-func TestNewIndex(t *testing.T) {
-	// TODO(juagargi) move out of segment and to cs/reservation test
+func TestNewIndexAtSource(t *testing.T) {
 	r := segmenttest.NewReservation()
 	require.Len(t, r.Indices, 0)
 	expTime := time.Unix(1, 0)
-	idx, err := r.NewIndex(expTime)
+	idx, err := r.NewIndexAtSource(expTime, 1, 3, 2, 5, reservation.CorePath)
 	require.NoError(t, err)
 	require.Len(t, r.Indices, 1)
-	require.True(t, r.Indices[0].Idx == idx)
-	require.True(t, r.Indices[0].Expiration == expTime)
 	require.Equal(t, reservation.IndexNumber(0), idx)
-
-	// up to 3 indices per expiration time
-	idx, err = r.NewIndex(expTime)
-	require.NoError(t, err)
-	require.Equal(t, reservation.IndexNumber(1), idx)
-	idx, err = r.NewIndex(expTime)
-	require.NoError(t, err)
-	require.Equal(t, reservation.IndexNumber(2), idx)
-	r.Indices = r.Indices[:1]
-
-	// exp time is less
-	_, err = r.NewIndex(expTime.Add(-1 * time.Millisecond))
-	require.Error(t, err)
-	require.Len(t, r.Indices, 1)
-
-	// exp time is same
-	idx, err = r.NewIndex(expTime)
+	require.Equal(t, idx, r.Indices[0].Idx)
+	require.Equal(t, expTime, r.Indices[0].Expiration)
+	require.Equal(t, segment.IndexTemporary, r.Indices[0].State())
+	require.Equal(t, reservation.BWCls(1), r.Indices[0].MinBW)
+	require.Equal(t, reservation.BWCls(3), r.Indices[0].MaxBW)
+	require.Equal(t, reservation.BWCls(2), r.Indices[0].AllocBW)
+	require.NotNil(t, r.Indices[0].Token)
+	tok := &reservation.Token{
+		InfoField: reservation.InfoField{
+			ExpirationTick: reservation.TickFromTime(expTime),
+			BWCls:          2,
+			RLC:            5,
+			Idx:            idx,
+			PathType:       reservation.CorePath,
+		},
+	}
+	require.Equal(t, tok, r.Indices[0].Token)
+	// add a second index
+	idx, err = r.NewIndexAtSource(expTime, 1, 3, 2, 5, reservation.CorePath)
 	require.NoError(t, err)
 	require.Len(t, r.Indices, 2)
-	require.True(t, r.Indices[1].Idx == idx)
-	require.True(t, r.Indices[1].Expiration == expTime)
 	require.Equal(t, reservation.IndexNumber(1), idx)
-	idx, err = r.NewIndex(expTime)
+	require.Equal(t, idx, r.Indices[1].Idx)
+	// remove first index and add another one
+	r.Indices = r.Indices[1:]
+	idx, err = r.NewIndexAtSource(expTime, 1, 3, 2, 5, reservation.CorePath)
 	require.NoError(t, err)
-	require.Len(t, r.Indices, 3)
-	require.True(t, r.Indices[2].Idx == idx)
-	require.True(t, r.Indices[2].Expiration == expTime)
+	require.Len(t, r.Indices, 2)
 	require.Equal(t, reservation.IndexNumber(2), idx)
+	require.Equal(t, idx, r.Indices[1].Idx)
+}
 
-	// too many indices for the same exp time
-	_, err = r.NewIndex(expTime)
-	require.Error(t, err)
-	require.Len(t, r.Indices, 3)
-
-	// exp time is greater
-	expTime = expTime.Add(time.Second)
-	idx, err = r.NewIndex(expTime)
-	require.NoError(t, err)
-	require.Len(t, r.Indices, 4)
-	require.True(t, r.Indices[3].Idx == idx)
-	require.True(t, r.Indices[3].Expiration == expTime)
-	require.Equal(t, reservation.IndexNumber(3), idx)
-
-	// index number rollover
-	r = segmenttest.NewReservation()
-	r.NewIndex(expTime)
-	require.Len(t, r.Indices, 1)
-	r.Indices[0].Idx = r.Indices[0].Idx.Sub(1)
-	idx, err = r.NewIndex(expTime)
+func TestNewIndexFromToken(t *testing.T) {
+	r := segmenttest.NewReservation()
+	require.Len(t, r.Indices, 0)
+	expTime := time.Unix(1, 0)
+	tok := &reservation.Token{
+		InfoField: reservation.InfoField{
+			ExpirationTick: reservation.TickFromTime(expTime),
+			BWCls:          2,
+			RLC:            5,
+			Idx:            reservation.IndexNumber(6),
+			PathType:       reservation.CorePath,
+		},
+	}
+	idx, err := r.NewIndexFromToken(tok, 1, 3)
 	require.NoError(t, err)
-	require.True(t, r.Indices[1].Idx == idx)
-	require.True(t, r.Indices[1].Expiration == expTime)
-	require.Equal(t, reservation.IndexNumber(0), idx)
+	require.Equal(t, tok.Idx, idx)
+	require.Equal(t, tok.ExpirationTick.ToTime(), r.Indices[0].Expiration)
+	require.Equal(t, segment.IndexTemporary, r.Indices[0].State())
+	require.Equal(t, reservation.BWCls(1), r.Indices[0].MinBW)
+	require.Equal(t, reservation.BWCls(3), r.Indices[0].MaxBW)
+	require.Equal(t, tok.BWCls, r.Indices[0].AllocBW)
+	require.Nil(t, r.Indices[0].Token)
+	// nil token
+	_, err = r.NewIndexFromToken(nil, 0, 0)
+	require.Error(t, err)
 }
+
 func TestReservationValidate(t *testing.T) {
 	r := segmenttest.NewReservation()
 	err := r.Validate()
 	require.NoError(t, err)
-
-	// more than 16 indices
-	for i := 0; i < 16; i++ {
-		expTime := time.Unix(int64(i), 0)
-		r.NewIndex(expTime)
-	}
-	require.Len(t, r.Indices, 16)
-	err = r.Validate()
-	require.NoError(t, err)
-	r.Indices = append(r.Indices, r.Indices[15])
-	r.Indices[16].Idx.Add(1) // rollover
-	err = r.Validate()
-	require.Error(t, err)
-
 	// wrong path
 	r.Path = segment.Path{}
 	err = r.Validate()
 	require.Error(t, err)
-
-	r = segmenttest.NewReservation()
-	r.SetActiveIndexForTesting(0)
-	err = r.Validate()
-	require.Error(t, err)
-
-	// exp time is before
-	r = segmenttest.NewReservation()
-	expTime := time.Unix(1, 0)
-	r.NewIndex(expTime)
-	r.NewIndex(expTime)
-	r.Indices[1].Expiration = expTime.Add(-1 * time.Second)
-	err = r.Validate()
-	require.Error(t, err)
-
-	// non consecutive indices
-	r.Indices = r.Indices[:1] // remove tainted index
-	r.NewIndex(expTime)
-	err = r.Validate()
-	require.NoError(t, err)
-	r.Indices[1].Idx = 2
-	err = r.Validate()
-	require.Error(t, err)
-
-	// more than three indices per exp time
-	r.Indices = r.Indices[:1]
-	r.NewIndex(expTime)
-	r.NewIndex(expTime)
-	err = r.Validate()
-	require.NoError(t, err)
-	_, err = r.NewIndex(expTime.Add(time.Second))
-	require.NoError(t, err)
-	r.Indices[3].Expiration = expTime
-	r.Indices[3].Idx = 3
-	err = r.Validate()
-	require.Error(t, err)
-
 	// more than one active index
-	r.Indices = r.Indices[:1]
-	r.NewIndex(expTime)
+	expTime := time.Unix(1, 0)
+	r = segmenttest.NewReservation()
+	r.NewIndexAtSource(expTime, 0, 0, 0, 0, reservation.CorePath)
+	r.NewIndexAtSource(expTime, 0, 0, 0, 0, reservation.CorePath)
+	require.Len(t, r.Indices, 2)
 	r.Indices[0].SetStateForTesting(segment.IndexActive)
 	r.Indices[1].SetStateForTesting(segment.IndexActive)
 	err = r.Validate()
 	require.Error(t, err)
-
 	// ID not set
 	r = segmenttest.NewReservation()
 	r.ID = reservation.SegmentID{}
 	err = r.Validate()
 	require.Error(t, err)
-
 	// starts in this AS but ingress nonzero
 	r = segmenttest.NewReservation()
 	r.Ingress = 1
 	err = r.Validate()
 	require.Error(t, err)
-
 	// Does not start in this AS but ingress empty
 	r = segmenttest.NewReservation()
 	r.Path = nil
@@ -176,10 +127,29 @@ func TestReservationValidate(t *testing.T) {
 	require.Error(t, err)
 }
 
+func TestIndex(t *testing.T) {
+	r := segmenttest.NewReservation()
+	expTime := time.Unix(1, 0)
+	r.NewIndexAtSource(expTime, 0, 0, 0, 0, reservation.CorePath)
+	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)
+	require.Equal(t, &r.Indices[1], index)
+	_, err = r.Index(reservation.IndexNumber(4))
+	require.Error(t, err)
+	r.SetIndexConfirmed(idx)
+	r.SetIndexActive(idx)
+	index, err = r.Index(idx)
+	require.NoError(t, err)
+	require.Equal(t, &r.Indices[0], index)
+}
+
 func TestSetIndexConfirmed(t *testing.T) {
 	r := segmenttest.NewReservation()
 	expTime := time.Unix(1, 0)
-	id, _ := r.NewIndex(expTime)
+	id, _ := r.NewIndexAtSource(expTime, 0, 0, 0, 0, reservation.CorePath)
 	require.Equal(t, segment.IndexTemporary, r.Indices[0].State())
 	err := r.SetIndexConfirmed(id)
 	require.NoError(t, err)
@@ -196,7 +166,7 @@ func TestSetIndexActive(t *testing.T) {
 	expTime := time.Unix(1, 0)
 
 	// index not confirmed
-	idx, _ := r.NewIndex(expTime)
+	idx, _ := r.NewIndexAtSource(expTime, 0, 0, 0, 0, reservation.CorePath)
 	err := r.SetIndexActive(idx)
 	require.Error(t, err)
 
@@ -212,8 +182,8 @@ func TestSetIndexActive(t *testing.T) {
 	require.NoError(t, err)
 
 	// remove previous indices
-	r.NewIndex(expTime)
-	idx, _ = r.NewIndex(expTime)
+	r.NewIndexAtSource(expTime, 0, 0, 0, 0, reservation.CorePath)
+	idx, _ = r.NewIndexAtSource(expTime, 0, 0, 0, 0, reservation.CorePath)
 	require.Len(t, r.Indices, 3)
 	require.Equal(t, 0, r.GetActiveIndexForTesting())
 	r.SetIndexConfirmed(idx)
@@ -227,14 +197,14 @@ func TestSetIndexActive(t *testing.T) {
 func TestRemoveIndex(t *testing.T) {
 	r := segmenttest.NewReservation()
 	expTime := time.Unix(1, 0)
-	idx, _ := r.NewIndex(expTime)
+	idx, _ := r.NewIndexAtSource(expTime, 0, 0, 0, 0, reservation.CorePath)
 	err := r.RemoveIndex(idx)
 	require.NoError(t, err)
 	require.Len(t, r.Indices, 0)
 
 	// remove second index
-	idx, _ = r.NewIndex(expTime)
-	idx2, _ := r.NewIndex(expTime)
+	idx, _ = r.NewIndexAtSource(expTime, 0, 0, 0, 0, reservation.CorePath)
+	idx2, _ := r.NewIndexAtSource(expTime, 0, 0, 0, 0, reservation.CorePath)
 	err = r.RemoveIndex(idx)
 	require.NoError(t, err)
 	require.Len(t, r.Indices, 1)
@@ -244,9 +214,9 @@ func TestRemoveIndex(t *testing.T) {
 
 	// remove also removes older indices
 	expTime = expTime.Add(time.Second)
-	r.NewIndex(expTime)
-	idx, _ = r.NewIndex(expTime)
-	idx2, _ = r.NewIndex(expTime)
+	r.NewIndexAtSource(expTime, 0, 0, 0, 0, reservation.CorePath)
+	idx, _ = r.NewIndexAtSource(expTime, 0, 0, 0, 0, reservation.CorePath)
+	idx2, _ = r.NewIndexAtSource(expTime, 0, 0, 0, 0, reservation.CorePath)
 	require.Len(t, r.Indices, 4)
 	err = r.RemoveIndex(idx)
 	require.NoError(t, err)
diff --git a/go/cs/reservation/sqlite/db_test.go b/go/cs/reservation/sqlite/db_test.go
index 4ae32288f..d4d3a6175 100644
--- a/go/cs/reservation/sqlite/db_test.go
+++ b/go/cs/reservation/sqlite/db_test.go
@@ -71,7 +71,8 @@ func TestRaceForSuffix(t *testing.T) {
 	require.NoError(t, err)
 	require.Equal(t, uint32(3), suffix2)
 	rsv := &segment.Reservation{
-		ID: reservation.SegmentID{ASID: asid},
+		ID:      reservation.SegmentID{ASID: asid},
+		Indices: segment.Indices{segment.Index{}},
 	}
 	err = insertNewSegReservation(ctx, db.db, rsv, suffix1)
 	require.NoError(t, err)
@@ -87,17 +88,20 @@ func BenchmarkNewSuffix100K(b *testing.B) { benchmarkNewSuffix(b, 100000) }
 func BenchmarkNewSuffix1M(b *testing.B)   { benchmarkNewSuffix(b, 1000000) }
 
 func newDB(t testing.TB) *Backend {
+	t.Helper()
 	db, err := New("file::memory:")
 	require.NoError(t, err)
 	return db
 }
 
 func addSegRsvRows(t testing.TB, b *Backend, asid addr.AS, firstSuffix, lastSuffix uint32) {
+	t.Helper()
 	ctx := context.Background()
 	query := `INSERT INTO seg_reservation (id_as, id_suffix, ingress, egress, path,
-		src_as, dst_as) VALUES ($1, $2, $3, $4, $5, $6, $7)`
+		end_props, traffic_split, src_ia, dst_ia, active_index)
+		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, -1)`
 	for suffix := firstSuffix; suffix <= lastSuffix; suffix++ {
-		_, err := b.db.ExecContext(ctx, query, asid, suffix, 0, 0, nil, nil, nil)
+		_, err := b.db.ExecContext(ctx, query, asid, suffix, 0, 0, nil, 0, 0, nil, nil)
 		require.NoError(t, err)
 	}
 }
diff --git a/go/lib/colibri/reservation/types_test.go b/go/lib/colibri/reservation/types_test.go
index de984f286..6e03d846e 100644
--- a/go/lib/colibri/reservation/types_test.go
+++ b/go/lib/colibri/reservation/types_test.go
@@ -69,6 +69,14 @@ func TestTickFromTime(t *testing.T) {
 	require.Equal(t, Tick(1), TickFromTime(time.Unix(4, 0)))
 }
 
+func TestTickToTime(t *testing.T) {
+	require.Equal(t, time.Unix(0, 0), Tick(0).ToTime())
+	require.Equal(t, time.Unix(4, 0), Tick(1).ToTime())
+	require.Equal(t, time.Unix(0, 0), TickFromTime(time.Unix(0, 0)).ToTime())
+	require.Equal(t, time.Unix(0, 0), TickFromTime(time.Unix(3, 999999)).ToTime())
+	require.Equal(t, time.Unix(4, 0), TickFromTime(time.Unix(4, 0)).ToTime())
+}
+
 func TestValidateBWCls(t *testing.T) {
 	for i := 0; i < 64; i++ {
 		c := BWCls(i)
@@ -108,6 +116,13 @@ func TestIndexNumberArithmetic(t *testing.T) {
 	require.Equal(t, IndexNumber(0), x)
 	x = idx.Sub(IndexNumber(2))
 	require.Equal(t, IndexNumber(15), x)
+	// distance from 2 to 0 = 0 - 2 = 14 mod 16
+	x = IndexNumber(2)
+	distance := IndexNumber(0).Sub(x)
+	require.Equal(t, IndexNumber(14), distance)
+	// distance from 2 to 4
+	distance = IndexNumber(4).Sub(x)
+	require.Equal(t, IndexNumber(2), distance)
 }
 
 func TestValidatePathType(t *testing.T) {
@@ -255,6 +270,12 @@ func TestTokenRead(t *testing.T) {
 	require.Error(t, err)
 }
 
+func TestTokenToRaw(t *testing.T) {
+	tok := newToken()
+	raw := newTokenRaw()
+	require.Equal(t, raw, tok.ToRaw())
+}
+
 func newInfoField() InfoField {
 	return InfoField{
 		ExpirationTick: 384555855,