Repository: scionproto/scion
Pull request #3763 (2020-05-12 11:31:47)

colibri: application level types requests
Add functions to convert between application level types and control messages from Capnp.

<!-- 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/3763)
<!-- Reviewable:end -->



Patch:
diff --git a/go/cs/reservation/segment/BUILD.bazel b/go/cs/reservation/segment/BUILD.bazel
index 1e6795702..2fdf38857 100644
--- a/go/cs/reservation/segment/BUILD.bazel
+++ b/go/cs/reservation/segment/BUILD.bazel
@@ -15,6 +15,7 @@ go_library(
         "//go/lib/addr:go_default_library",
         "//go/lib/colibri/reservation:go_default_library",
         "//go/lib/common:go_default_library",
+        "//go/lib/ctrl/colibri_mgmt:go_default_library",
         "//go/lib/serrors:go_default_library",
     ],
 )
@@ -24,12 +25,15 @@ go_test(
     srcs = [
         "export_test.go",
         "path_test.go",
+        "request_test.go",
         "reservation_test.go",
     ],
     embed = [":go_default_library"],
     deps = [
         "//go/cs/reservation/segmenttest:go_default_library",
         "//go/lib/colibri/reservation:go_default_library",
+        "//go/lib/ctrl/colibri_mgmt:go_default_library",
+        "//go/lib/xtest:go_default_library",
         "@com_github_stretchr_testify//require:go_default_library",
     ],
 )
diff --git a/go/cs/reservation/segment/request.go b/go/cs/reservation/segment/request.go
index d171786c6..235594e9d 100644
--- a/go/cs/reservation/segment/request.go
+++ b/go/cs/reservation/segment/request.go
@@ -18,6 +18,8 @@ import (
 	"time"
 
 	"github.com/scionproto/scion/go/lib/colibri/reservation"
+	"github.com/scionproto/scion/go/lib/ctrl/colibri_mgmt"
+	"github.com/scionproto/scion/go/lib/serrors"
 )
 
 // SetupReq is a segment reservation setup request. It contains a reference to the reservation
@@ -29,18 +31,87 @@ type SetupReq struct {
 	MinBW       uint8
 	MaxBW       uint8
 	SplitCls    uint8
-	StartProps  reservation.PathEndProps
-	EndProps    reservation.PathEndProps
+	PathProps   reservation.PathEndProps
 	AllocTrail  []reservation.AllocationBead
 }
 
+func NewRequestFromCtrlMsg(setup *colibri_mgmt.SegmentSetup, timestamp time.Time) *SetupReq {
+	s := SetupReq{
+		Timestamp:  timestamp,
+		MinBW:      setup.MinBW,
+		MaxBW:      setup.MaxBW,
+		SplitCls:   setup.SplitCls,
+		AllocTrail: make([]reservation.AllocationBead, 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,
+		}
+	}
+	return &s
+}
+
+// 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,
+		StartProps: colibri_mgmt.PathEndProps{
+			Local:    (r.PathProps & reservation.StartLocal) != 0,
+			Transfer: (r.PathProps & reservation.StartTransfer) != 0,
+		},
+		EndProps: colibri_mgmt.PathEndProps{
+			Local:    (r.PathProps & reservation.EndLocal) != 0,
+			Transfer: (r.PathProps & reservation.EndTransfer) != 0,
+		},
+		AllocationTrail: make([]*colibri_mgmt.AllocationBeads, len(r.AllocTrail)),
+	}
+	for i, bead := range r.AllocTrail {
+		msg.AllocationTrail[i] = &colibri_mgmt.AllocationBeads{
+			AllocBW: bead.AllocBW,
+			MaxBW:   bead.MaxBW,
+		}
+	}
+	return msg
+}
+
 // SetupTelesReq represents a telescopic segment setup.
 type SetupTelesReq struct {
 	SetupReq
 	BaseID reservation.SegmentID
 }
 
-func SetupReqFromWire(raw []byte) (*SetupReq, error) {
-	// TODO(juagargi)
-	return nil, nil
+func NewTelesRequestFromCtrlMsg(setup *colibri_mgmt.SegmentTelesSetup, timestamp time.Time) (
+	*SetupTelesReq, error) {
+
+	if setup.BaseID == nil || setup.Setup == nil {
+		return nil, serrors.New("illegal ctrl telescopic setup received", "base_id", setup.BaseID,
+			"segment_setup", setup.Setup)
+	}
+	s := SetupTelesReq{
+		SetupReq: *NewRequestFromCtrlMsg(setup.Setup, timestamp),
+	}
+	id, err := reservation.SegmentIDFromRawBuffers(setup.BaseID.ASID, setup.BaseID.Suffix)
+	if err != nil {
+		return nil, err
+	}
+	s.BaseID = *id
+	return &s, nil
+}
+
+// ToCtrlMsg creates a new segment telescopic setup control message from this request.
+func (r *SetupTelesReq) ToCtrlMsg() *colibri_mgmt.SegmentTelesSetup {
+	buff := make([]byte, reservation.SegmentIDLen)
+	r.BaseID.Read(buff)
+	return &colibri_mgmt.SegmentTelesSetup{
+		Setup: r.SetupReq.ToCtrlMsg(),
+		BaseID: &colibri_mgmt.SegmentReservationID{
+			ASID:   buff[:6],
+			Suffix: buff[6:],
+		},
+	}
 }
diff --git a/go/lib/colibri/reservation/BUILD.bazel b/go/lib/colibri/reservation/BUILD.bazel
index b274a4c4c..9ddf6723d 100644
--- a/go/lib/colibri/reservation/BUILD.bazel
+++ b/go/lib/colibri/reservation/BUILD.bazel
@@ -18,5 +18,9 @@ go_test(
     name = "go_default_test",
     srcs = ["types_test.go"],
     embed = [":go_default_library"],
-    deps = ["//go/lib/xtest:go_default_library"],
+    deps = [
+        "//go/lib/spath:go_default_library",
+        "//go/lib/xtest:go_default_library",
+        "@com_github_stretchr_testify//require:go_default_library",
+    ],
 )
diff --git a/go/lib/colibri/reservation/types.go b/go/lib/colibri/reservation/types.go
index cea6dd581..8c78edc73 100644
--- a/go/lib/colibri/reservation/types.go
+++ b/go/lib/colibri/reservation/types.go
@@ -43,6 +43,15 @@ func NewSegmentID(AS addr.AS, suffix []byte) (*SegmentID, error) {
 	return &id, nil
 }
 
+// SegmentIDFromRawBuffers constructs a SegmentID from two separate buffers.
+func SegmentIDFromRawBuffers(ASID, suffix []byte) (*SegmentID, error) {
+	if len(ASID) < 6 || len(suffix) < 4 {
+		return nil, serrors.New("buffers too small", "length_ASID", len(ASID),
+			"length_suffix", len(suffix))
+	}
+	return NewSegmentID(addr.AS(common.Order.Uint64(append([]byte{0, 0}, ASID[:6]...))), suffix[:4])
+}
+
 // SegmentIDFromRaw constructs a SegmentID parsing a raw buffer.
 func SegmentIDFromRaw(raw []byte) (
 	*SegmentID, error) {
@@ -51,11 +60,7 @@ func SegmentIDFromRaw(raw []byte) (
 		return nil, serrors.New("buffer too small", "actual", len(raw),
 			"min", SegmentIDLen)
 	}
-	id := SegmentID{
-		ASID: addr.AS(common.Order.Uint64(append([]byte{0, 0}, raw[0:6]...))),
-	}
-	copy(id.Suffix[:], raw[6:10])
-	return &id, nil
+	return SegmentIDFromRawBuffers(raw[:6], raw[6:])
 }
 
 func (id *SegmentID) Read(raw []byte) (int, error) {
@@ -78,7 +83,7 @@ type E2EID struct {
 
 const E2EIDLen = 16
 
-// NewSegmentID returns a new SegmentID
+// NewE2EID returns a new E2EID
 func NewE2EID(AS addr.AS, suffix []byte) (*E2EID, error) {
 	if len(suffix) != 10 {
 		return nil, serrors.New("wrong suffix length, should be 10", "actual_len", len(suffix))
@@ -88,16 +93,21 @@ func NewE2EID(AS addr.AS, suffix []byte) (*E2EID, error) {
 	return &id, nil
 }
 
+// E2EIDFromRawBuffers constructs a E2DID from two separate buffers.
+func E2EIDFromRawBuffers(ASID, suffix []byte) (*E2EID, error) {
+	if len(ASID) < 6 || len(suffix) < 10 {
+		return nil, serrors.New("buffers too small", "length_ASID", len(ASID),
+			"length_suffix", len(suffix))
+	}
+	return NewE2EID(addr.AS(common.Order.Uint64(append([]byte{0, 0}, ASID[:6]...))), suffix[:10])
+}
+
 // E2EIDFromRaw constructs an E2EID parsing a buffer.
 func E2EIDFromRaw(raw []byte) (*E2EID, error) {
 	if len(raw) < E2EIDLen {
 		return nil, serrors.New("buffer too small", "actual", len(raw), "min", E2EIDLen)
 	}
-	id := E2EID{
-		ASID: addr.AS(common.Order.Uint64(append([]byte{0, 0}, raw[0:6]...))),
-	}
-	copy(id.Suffix[:], raw[6:16])
-	return &id, nil
+	return E2EIDFromRawBuffers(raw[:6], raw[6:])
 }
 
 func (id *E2EID) Read(raw []byte) (int, error) {
@@ -253,10 +263,11 @@ func InfoFieldFromRaw(raw []byte) (*InfoField, error) {
 	return &info, nil
 }
 
-// Read serializes this InfoField into an array of InfoFieldLen bytes.
+// Read serializes this InfoField into a sequence of InfoFieldLen bytes.
 func (f *InfoField) Read(b []byte) (int, error) {
 	if len(b) < InfoFieldLen {
-		return 0, serrors.New("buffer too short", "size", len(b))
+		return 0, serrors.New("buffer too small", "min_size", InfoFieldLen,
+			"current_size", len(b))
 	}
 	common.Order.PutUint32(b[:4], uint32(f.ExpirationTick))
 	b[4] = byte(f.BWCls)
@@ -290,6 +301,23 @@ func (pep PathEndProps) Validate() error {
 	return nil
 }
 
+func NewPathEndProps(startLocal, startTransfer, endLocal, endTransfer bool) PathEndProps {
+	var props PathEndProps
+	if startLocal {
+		props |= StartLocal
+	}
+	if startTransfer {
+		props |= StartTransfer
+	}
+	if endLocal {
+		props |= EndLocal
+	}
+	if endTransfer {
+		props |= EndTransfer
+	}
+	return props
+}
+
 // 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 {
@@ -305,5 +333,57 @@ type Token struct {
 
 // Validate will return an error for invalid values. It will not check the hop fields' validity.
 func (t *Token) Validate() error {
+	if len(t.HopFields) == 0 {
+		return serrors.New("token without hop fields")
+	}
 	return t.InfoField.Validate()
 }
+
+// TokenFromRaw builds a Token from the passed bytes buffer.
+func TokenFromRaw(raw []byte) (*Token, error) {
+	rawHFs := len(raw) - InfoFieldLen
+	if rawHFs < 0 || rawHFs%spath.HopFieldLength != 0 {
+		return nil, serrors.New("buffer too small", "min_size", InfoFieldLen,
+			"current_size", len(raw))
+	}
+	numHFs := rawHFs / spath.HopFieldLength
+	inf, err := InfoFieldFromRaw(raw[:InfoFieldLen])
+	if err != nil {
+		return nil, err
+	}
+	t := Token{
+		InfoField: *inf,
+		HopFields: make([]spath.HopField, numHFs),
+	}
+	for i := 0; i < numHFs; i++ {
+		offset := InfoFieldLen + i*spath.HopFieldLength
+		hf, err := spath.HopFFromRaw(raw[offset : offset+spath.HopFieldLength])
+		if err != nil {
+			return nil, err
+		}
+		t.HopFields[i] = *hf
+	}
+	return &t, nil
+}
+
+// Len returns the number of bytes of this token if serialized.
+func (t *Token) Len() int {
+	return InfoFieldLen + len(t.HopFields)*spath.HopFieldLength
+}
+
+// Read serializes this Token to the passed buffer.
+func (t *Token) Read(b []byte) (int, error) {
+	length := t.Len()
+	if len(b) < length {
+		return 0, serrors.New("buffer too small", "min_size", length, "current_size", len(b))
+	}
+	offset, err := t.InfoField.Read(b[:InfoFieldLen])
+	if err != nil {
+		return 0, err
+	}
+	for i := 0; i < len(t.HopFields); i++ {
+		t.HopFields[i].Write(b[offset : offset+spath.HopFieldLength])
+		offset += spath.HopFieldLength
+	}
+	return offset, nil
+}
diff --git a/go/lib/ctrl/colibri_mgmt/reservation_ids.go b/go/lib/ctrl/colibri_mgmt/reservation_ids.go
index 28ab49e43..1673360f1 100644
--- a/go/lib/ctrl/colibri_mgmt/reservation_ids.go
+++ b/go/lib/ctrl/colibri_mgmt/reservation_ids.go
@@ -29,7 +29,7 @@ func (id *SegmentReservationID) ProtoId() proto.ProtoIdType {
 
 type E2EReservationID struct {
 	ASID   []byte `capnp:"asid"`
-	Suffix []byte // 16 bytes long
+	Suffix []byte // 10 bytes long
 }
 
 func (id *E2EReservationID) ProtoId() proto.ProtoIdType {


Test patch:
diff --git a/go/cs/reservation/e2e/BUILD.bazel b/go/cs/reservation/e2e/BUILD.bazel
index c96a3ef8a..a4e6fc4b9 100644
--- a/go/cs/reservation/e2e/BUILD.bazel
+++ b/go/cs/reservation/e2e/BUILD.bazel
@@ -13,19 +13,26 @@ go_library(
         "//go/cs/reservation:go_default_library",
         "//go/cs/reservation/segment:go_default_library",
         "//go/lib/colibri/reservation:go_default_library",
+        "//go/lib/ctrl/colibri_mgmt:go_default_library",
         "//go/lib/serrors:go_default_library",
+        "//go/proto:go_default_library",
     ],
 )
 
 go_test(
     name = "go_default_test",
-    srcs = ["reservation_test.go"],
+    srcs = [
+        "request_test.go",
+        "reservation_test.go",
+    ],
     embed = [":go_default_library"],
     deps = [
         "//go/cs/reservation/segment:go_default_library",
         "//go/cs/reservation/segmenttest:go_default_library",
         "//go/lib/colibri/reservation:go_default_library",
+        "//go/lib/ctrl/colibri_mgmt:go_default_library",
         "//go/lib/xtest:go_default_library",
+        "//go/proto:go_default_library",
         "@com_github_stretchr_testify//require:go_default_library",
     ],
 )
diff --git a/go/cs/reservation/e2e/request.go b/go/cs/reservation/e2e/request.go
index 1fb8ecdc1..681bafef5 100644
--- a/go/cs/reservation/e2e/request.go
+++ b/go/cs/reservation/e2e/request.go
@@ -14,8 +14,134 @@
 
 package e2e
 
-// "github.com/scionproto/scion/go/lib/colibri/reservation"
+import (
+	"time"
 
-type SetupReq struct {
-	//
+	"github.com/scionproto/scion/go/lib/colibri/reservation"
+	"github.com/scionproto/scion/go/lib/ctrl/colibri_mgmt"
+	"github.com/scionproto/scion/go/lib/serrors"
+	"github.com/scionproto/scion/go/proto"
+)
+
+// SetupReq is the interface for an e2e setup request.
+// Currently it's implemented by either SuccessSetupReq or FailureSetupReq.
+type SetupReq interface {
+	Reservation() *Reservation
+	Timestamp() time.Time
+	ToCtrlMsg() (*colibri_mgmt.E2ESetup, error)
+}
+
+// BaseSetupReq is the common part of any e2e setup request.
+type BaseSetupReq struct {
+	reservation *Reservation
+	timestamp   time.Time
+}
+
+func (r *BaseSetupReq) Timestamp() time.Time      { return r.timestamp }
+func (r *BaseSetupReq) Reservation() *Reservation { return r.reservation }
+
+// SuccessSetupReq is a successful e2e resevation setup request.
+type SuccessSetupReq struct {
+	BaseSetupReq
+	ID    reservation.E2EID
+	Token reservation.Token
+}
+
+var _ SetupReq = (*SuccessSetupReq)(nil)
+
+func (r *SuccessSetupReq) ToCtrlMsg() (*colibri_mgmt.E2ESetup, error) {
+	id := make([]byte, reservation.E2EIDLen)
+	_, err := r.ID.Read(id)
+	if err != nil {
+		return nil, err
+	}
+	token := make([]byte, r.Token.Len())
+	_, err = r.Token.Read(token)
+	if err != nil {
+		return nil, err
+	}
+	msg := &colibri_mgmt.E2ESetup{
+		Which: proto.E2ESetupData_Which_success,
+		Success: &colibri_mgmt.E2ESetupSuccess{
+			ReservationID: &colibri_mgmt.E2EReservationID{
+				ASID:   id[:6],
+				Suffix: id[6:],
+			},
+			Token: token,
+		},
+	}
+	return msg, nil
+}
+
+// FailureSetupReq is a failing e2e resevation setup request.
+type FailureSetupReq struct {
+	BaseSetupReq
+	ErrorCode  int
+	InfoField  reservation.InfoField
+	MaxBWTrail []reservation.BWCls
+}
+
+var _ SetupReq = (*FailureSetupReq)(nil)
+
+func (r *FailureSetupReq) ToCtrlMsg() (*colibri_mgmt.E2ESetup, error) {
+	inf := make([]byte, reservation.InfoFieldLen)
+	_, err := r.InfoField.Read(inf)
+	if err != nil {
+		return nil, err
+	}
+	trail := make([]uint8, len(r.MaxBWTrail))
+	for i, bw := range r.MaxBWTrail {
+		trail[i] = uint8(bw)
+	}
+	msg := &colibri_mgmt.E2ESetup{
+		Which: proto.E2ESetupData_Which_failure,
+		Failure: &colibri_mgmt.E2ESetupFailure{
+			ErrorCode: uint8(r.ErrorCode),
+			InfoField: inf,
+			MaxBWs:    trail,
+		},
+	}
+	return msg, nil
+}
+
+// NewRequestFromCtrlMsg will return a SuccessSetupReq or FailSetupReq depending on the
+// success flag of the ctrl message.
+func NewRequestFromCtrlMsg(setup *colibri_mgmt.E2ESetup, ts time.Time) (SetupReq, error) {
+	var s SetupReq
+	switch {
+	case setup.Success != nil:
+		id, err := reservation.E2EIDFromRawBuffers(setup.Success.ReservationID.ASID,
+			setup.Success.ReservationID.Suffix)
+		if err != nil {
+			return nil, err
+		}
+		tok, err := reservation.TokenFromRaw(setup.Success.Token)
+		if err != nil {
+			return nil, err
+		}
+		s = &SuccessSetupReq{
+			BaseSetupReq: BaseSetupReq{timestamp: ts},
+			ID:           *id,
+			Token:        *tok,
+		}
+	case setup.Failure != nil:
+		ifield, err := reservation.InfoFieldFromRaw(setup.Failure.InfoField)
+		if err != nil {
+			return nil, err
+		}
+		bwTrail := make([]reservation.BWCls, len(setup.Failure.MaxBWs))
+		for i, bw := range setup.Failure.MaxBWs {
+			bwTrail[i] = reservation.BWCls(bw)
+		}
+		s = &FailureSetupReq{
+			BaseSetupReq: BaseSetupReq{timestamp: ts},
+			ErrorCode:    int(setup.Failure.ErrorCode),
+			InfoField:    *ifield,
+			MaxBWTrail:   bwTrail,
+		}
+	default:
+		return nil, serrors.New("invalid E2E setup request received, neither successful or failed",
+			"success_ptr", setup.Success, "failure_ptr", setup.Failure)
+	}
+	return s, nil
 }
diff --git a/go/cs/reservation/e2e/request_test.go b/go/cs/reservation/e2e/request_test.go
new file mode 100644
index 000000000..e5ea74dff
--- /dev/null
+++ b/go/cs/reservation/e2e/request_test.go
@@ -0,0 +1,123 @@
+// 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 e2e_test
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/scionproto/scion/go/cs/reservation/e2e"
+	"github.com/scionproto/scion/go/lib/colibri/reservation"
+	"github.com/scionproto/scion/go/lib/ctrl/colibri_mgmt"
+	"github.com/scionproto/scion/go/lib/xtest"
+	"github.com/scionproto/scion/go/proto"
+)
+
+func TestNewRequestFromCtrlMsg(t *testing.T) {
+	setup := newE2ESetupSuccess()
+	ts := time.Unix(1, 0)
+	r, err := e2e.NewRequestFromCtrlMsg(setup, ts)
+	require.NoError(t, err)
+	checkRequest(t, setup, r, ts)
+
+	setup = newE2ESetupFailure()
+	r, err = e2e.NewRequestFromCtrlMsg(setup, ts)
+	require.NoError(t, err)
+	checkRequest(t, setup, r, ts)
+}
+
+func TestRequestToCtrlMsg(t *testing.T) {
+	setup := newE2ESetupSuccess()
+	ts := time.Unix(1, 0)
+	r, _ := e2e.NewRequestFromCtrlMsg(setup, ts)
+	anotherSetup, err := r.ToCtrlMsg()
+	require.NoError(t, err)
+	require.Equal(t, setup, anotherSetup)
+
+	setup = newE2ESetupFailure()
+	r, _ = e2e.NewRequestFromCtrlMsg(setup, ts)
+	anotherSetup, err = r.ToCtrlMsg()
+	require.NoError(t, err)
+	require.Equal(t, setup, anotherSetup)
+}
+
+func newE2ESetupSuccess() *colibri_mgmt.E2ESetup {
+	return &colibri_mgmt.E2ESetup{
+		Which: proto.E2ESetupData_Which_success,
+		Success: &colibri_mgmt.E2ESetupSuccess{
+			ReservationID: &colibri_mgmt.E2EReservationID{
+				ASID:   xtest.MustParseHexString("ff00cafe0001"),
+				Suffix: xtest.MustParseHexString("0123456789abcdef0123"),
+			},
+			Token: xtest.MustParseHexString("16ebdb4f0d042500003f001002bad1ce003f001002facade"),
+		},
+	}
+}
+
+func newE2ESetupFailure() *colibri_mgmt.E2ESetup {
+	return &colibri_mgmt.E2ESetup{
+		Which: proto.E2ESetupData_Which_failure,
+		Failure: &colibri_mgmt.E2ESetupFailure{
+			ErrorCode: 42,
+			InfoField: xtest.MustParseHexString("16ebdb4f0d042500"),
+			MaxBWs:    []uint8{1, 2},
+		},
+	}
+}
+
+func checkRequest(t *testing.T, e2eSetup *colibri_mgmt.E2ESetup, r e2e.SetupReq, ts time.Time) {
+	var base *e2e.BaseSetupReq
+	var successSetup *e2e.SuccessSetupReq
+	var failureSetup *e2e.FailureSetupReq
+	switch s := r.(type) {
+	case *e2e.SuccessSetupReq:
+		base = &s.BaseSetupReq
+		successSetup = s
+	case *e2e.FailureSetupReq:
+		base = &s.BaseSetupReq
+		failureSetup = s
+	default:
+		require.FailNow(t, "invalid type for request", "request type: %T", r)
+	}
+
+	require.Equal(t, (*e2e.Reservation)(nil), base.Reservation())
+	require.Equal(t, ts, base.Timestamp())
+	if successSetup != nil {
+		buff := make([]byte, len(e2eSetup.Success.Token))
+		_, err := successSetup.Token.Read(buff)
+		require.NoError(t, err) // tested in the Token UT, should not fail
+		require.Equal(t, e2eSetup.Success.Token, buff)
+		buff = make([]byte, reservation.E2EIDLen)
+		_, err = successSetup.ID.Read(buff)
+		require.NoError(t, err) // tested in the E2EID UT, should not fail
+		require.Equal(t, e2eSetup.Success.ReservationID.ASID, buff[:6])
+		require.Equal(t, e2eSetup.Success.ReservationID.Suffix, buff[6:])
+	}
+	if failureSetup != nil {
+		require.Equal(t, int(e2eSetup.Failure.ErrorCode), failureSetup.ErrorCode)
+		buff := make([]byte, reservation.InfoFieldLen)
+		_, err := failureSetup.InfoField.Read(buff)
+		require.NoError(t, err) // tested in the InfoField UT, should not fail
+		require.Equal(t, e2eSetup.Failure.InfoField, buff)
+		trail := make([]uint8, len(failureSetup.MaxBWTrail))
+		for i := range trail {
+			trail[i] = uint8(failureSetup.MaxBWTrail[i])
+		}
+		require.Equal(t, e2eSetup.Failure.MaxBWs, trail)
+
+	}
+}
diff --git a/go/cs/reservation/segment/request_test.go b/go/cs/reservation/segment/request_test.go
new file mode 100644
index 000000000..9132cc7fb
--- /dev/null
+++ b/go/cs/reservation/segment/request_test.go
@@ -0,0 +1,111 @@
+// 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"
+
+	"github.com/scionproto/scion/go/cs/reservation/segment"
+	"github.com/scionproto/scion/go/lib/colibri/reservation"
+	"github.com/scionproto/scion/go/lib/ctrl/colibri_mgmt"
+	"github.com/scionproto/scion/go/lib/xtest"
+)
+
+func TestNewRequestFromCtrlMsg(t *testing.T) {
+	segSetup := newSegSetup()
+	ts := time.Unix(1, 0)
+	r := segment.NewRequestFromCtrlMsg(segSetup, ts)
+	checkRequest(t, segSetup, r, ts)
+}
+
+func TestRequestToCtrlMsg(t *testing.T) {
+	segSetup := newSegSetup()
+	ts := time.Unix(1, 0)
+	r := segment.NewRequestFromCtrlMsg(segSetup, ts)
+	anotherSegSetup := r.ToCtrlMsg()
+	require.Equal(t, segSetup, anotherSegSetup)
+}
+
+func TestNewTelesRequestFromCtrlMsg(t *testing.T) {
+	telesReq := newSegTelesSetup()
+	ts := time.Unix(1, 0)
+	r, err := segment.NewTelesRequestFromCtrlMsg(telesReq, ts)
+	require.NoError(t, err)
+
+	checkRequest(t, telesReq.Setup, &r.SetupReq, ts)
+	require.Equal(t, xtest.MustParseAS("ff00:cafe:1"), r.BaseID.ASID)
+	require.Equal(t, xtest.MustParseHexString("deadbeef"), r.BaseID.Suffix[:])
+}
+
+func TestTelesRequestToCtrlMsg(t *testing.T) {
+	segSetup := newSegTelesSetup()
+	ts := time.Unix(1, 0)
+	r, _ := segment.NewTelesRequestFromCtrlMsg(segSetup, ts)
+	anotherSegSetup := r.ToCtrlMsg()
+	require.Equal(t, segSetup, anotherSegSetup)
+}
+
+func newSegSetup() *colibri_mgmt.SegmentSetup {
+	return &colibri_mgmt.SegmentSetup{
+		MinBW:    1,
+		MaxBW:    2,
+		SplitCls: 3,
+		StartProps: colibri_mgmt.PathEndProps{
+			Local:    true,
+			Transfer: false,
+		},
+		EndProps: colibri_mgmt.PathEndProps{
+			Local:    false,
+			Transfer: true,
+		},
+		AllocationTrail: []*colibri_mgmt.AllocationBeads{
+			{
+				AllocBW: 5,
+				MaxBW:   6,
+			},
+		},
+	}
+}
+
+func newSegTelesSetup() *colibri_mgmt.SegmentTelesSetup {
+	return &colibri_mgmt.SegmentTelesSetup{
+		Setup: newSegSetup(),
+		BaseID: &colibri_mgmt.SegmentReservationID{
+			ASID:   xtest.MustParseHexString("ff00cafe0001"),
+			Suffix: xtest.MustParseHexString("deadbeef"),
+		},
+	}
+}
+
+func checkRequest(t *testing.T, segSetup *colibri_mgmt.SegmentSetup, r *segment.SetupReq,
+	ts time.Time) {
+
+	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, 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)
+	}
+}
diff --git a/go/lib/colibri/reservation/types_test.go b/go/lib/colibri/reservation/types_test.go
index 6dc03cfcb..de984f286 100644
--- a/go/lib/colibri/reservation/types_test.go
+++ b/go/lib/colibri/reservation/types_test.go
@@ -15,29 +15,20 @@
 package reservation
 
 import (
-	"bytes"
-	"encoding/hex"
 	"testing"
 	"time"
 
+	"github.com/stretchr/testify/require"
+
+	"github.com/scionproto/scion/go/lib/spath"
 	"github.com/scionproto/scion/go/lib/xtest"
 )
 
 func TestSegmentIDFromRaw(t *testing.T) {
-	raw := xtest.MustParseHexString("ffaa00001101facecafe")
-	id, err := SegmentIDFromRaw(raw)
-	if err != nil {
-		t.Fatalf("Unexpected error: %v", err)
-	}
-	refASID := xtest.MustParseAS("ffaa:0:1101")
-	if id.ASID != refASID {
-		t.Fatalf("Bad ASID. Got %x expected %x", id.ASID, refASID)
-	}
-	refSuffix := xtest.MustParseHexString("facecafe")
-	if bytes.Compare(id.Suffix[:], refSuffix) != 0 {
-		t.Fatalf("Bad Suffix. Got %s expected %s",
-			hex.EncodeToString(id.Suffix[:]), hex.EncodeToString(refSuffix))
-	}
+	id, err := SegmentIDFromRaw(xtest.MustParseHexString("ffaa00001101facecafe"))
+	require.NoError(t, err)
+	require.Equal(t, xtest.MustParseAS("ffaa:0:1101"), id.ASID)
+	require.Equal(t, xtest.MustParseHexString("facecafe"), id.Suffix[:])
 }
 
 func TestSegmentIDRead(t *testing.T) {
@@ -47,34 +38,17 @@ func TestSegmentIDRead(t *testing.T) {
 	copy(reference.Suffix[:], xtest.MustParseHexString("facecafe"))
 	raw := make([]byte, SegmentIDLen)
 	n, err := reference.Read(raw)
-	if err != nil {
-		t.Fatalf("Unexpect error: %v", err)
-	}
-	if n != SegmentIDLen {
-		t.Fatalf("Unexpected read size %d. Expected %d", n, SegmentIDLen)
-	}
-	rawReference := xtest.MustParseHexString("ffaa00001101facecafe")
-	if bytes.Compare(raw, rawReference) != 0 {
-		t.Fatalf("Serialized SegmentID is different: %s expected %s",
-			hex.EncodeToString(raw), hex.EncodeToString(rawReference))
-	}
+	require.NoError(t, err)
+	require.Equal(t, SegmentIDLen, n)
+	require.Equal(t, xtest.MustParseHexString("ffaa00001101facecafe"), raw)
 }
 
 func TestE2EIDFromRaw(t *testing.T) {
 	raw := xtest.MustParseHexString("ffaa00001101facecafedeadbeeff00d")
 	id, err := E2EIDFromRaw(raw)
-	if err != nil {
-		t.Fatalf("Unexpected error: %v", err)
-	}
-	refASID := xtest.MustParseAS("ffaa:0:1101")
-	if id.ASID != refASID {
-		t.Fatalf("Bad ASID. Got %x expected %x", id.ASID, refASID)
-	}
-	refSuffix := xtest.MustParseHexString("facecafedeadbeeff00d")
-	if bytes.Compare(id.Suffix[:], refSuffix) != 0 {
-		t.Fatalf("Bad Suffix. Got %s expected %s",
-			hex.EncodeToString(id.Suffix[:]), hex.EncodeToString(refSuffix))
-	}
+	require.NoError(t, err)
+	require.Equal(t, xtest.MustParseAS("ffaa:0:1101"), id.ASID)
+	require.Equal(t, xtest.MustParseHexString("facecafedeadbeeff00d"), id.Suffix[:])
 }
 
 func TestE2EIDRead(t *testing.T) {
@@ -84,80 +58,56 @@ func TestE2EIDRead(t *testing.T) {
 	copy(reference.Suffix[:], xtest.MustParseHexString("facecafedeadbeeff00d"))
 	raw := make([]byte, E2EIDLen)
 	n, err := reference.Read(raw)
-	if err != nil {
-		t.Fatalf("Unexpect error: %v", err)
-	}
-	if n != E2EIDLen {
-		t.Fatalf("Unexpected read size %d. Expected %d", n, E2EIDLen)
-	}
-	rawReference := xtest.MustParseHexString("ffaa00001101facecafedeadbeeff00d")
-	if bytes.Compare(raw, rawReference) != 0 {
-		t.Fatalf("Serialized E2EID is different: %s expected %s",
-			hex.EncodeToString(raw), hex.EncodeToString(rawReference))
-	}
+	require.NoError(t, err)
+	require.Equal(t, E2EIDLen, n)
+	require.Equal(t, xtest.MustParseHexString("ffaa00001101facecafedeadbeeff00d"), raw)
 }
 
 func TestTickFromTime(t *testing.T) {
-	if tick := TickFromTime(time.Unix(0, 0)); tick != 0 {
-		t.Fatalf("Wrong tick %v, expected 0", tick)
-	}
-	if tick := TickFromTime(time.Unix(3, 999999)); tick != 0 {
-		t.Fatalf("Wrong tick %v, expected 0", tick)
-	}
-	if tick := TickFromTime(time.Unix(4, 0)); tick != 1 {
-		t.Fatalf("Wrong tick %v, expected 0", tick)
-	}
+	require.Equal(t, Tick(0), TickFromTime(time.Unix(0, 0)))
+	require.Equal(t, Tick(0), TickFromTime(time.Unix(3, 999999)))
+	require.Equal(t, Tick(1), TickFromTime(time.Unix(4, 0)))
 }
 
 func TestValidateBWCls(t *testing.T) {
 	for i := 0; i < 64; i++ {
 		c := BWCls(i)
-		if err := c.Validate(); err != nil {
-			t.Fatalf("Unexpected error at i = %d: %v", i, err)
-		}
+		err := c.Validate()
+		require.NoError(t, err)
 	}
 	c := BWCls(64)
-	if err := c.Validate(); err == nil {
-		t.Fatalf("Expected validation error but did not get one")
-	}
+	err := c.Validate()
+	require.Error(t, err)
 }
 
 func TestValidateRLC(t *testing.T) {
 	for i := 0; i < 64; i++ {
 		c := RLC(i)
-		if err := c.Validate(); err != nil {
-			t.Fatalf("Unexpected error at i = %d: %v", i, err)
-		}
+		err := c.Validate()
+		require.NoError(t, err)
 	}
 	c := RLC(64)
-	if err := c.Validate(); err == nil {
-		t.Fatalf("Expected validation error but did not get one")
-	}
+	err := c.Validate()
+	require.Error(t, err)
 }
 
 func TestValidateIndexNumber(t *testing.T) {
 	for i := 0; i < 16; i++ {
 		idx := IndexNumber(i)
-		if err := idx.Validate(); err != nil {
-			t.Fatalf("Unexpected error at i = %d: %v", i, err)
-		}
+		err := idx.Validate()
+		require.NoError(t, err)
 	}
 	idx := IndexNumber(16)
-	if err := idx.Validate(); err == nil {
-		t.Fatal("Expected validation error but did not get one")
-	}
+	err := idx.Validate()
+	require.Error(t, err)
 }
 
 func TestIndexNumberArithmetic(t *testing.T) {
 	var idx IndexNumber = 1
 	x := idx.Add(IndexNumber(15))
-	if x != IndexNumber(0) {
-		t.Fatalf("Unexpected result %v", x)
-	}
+	require.Equal(t, IndexNumber(0), x)
 	x = idx.Sub(IndexNumber(2))
-	if x != IndexNumber(15) {
-		t.Fatalf("Unexpected result %v", x)
-	}
+	require.Equal(t, IndexNumber(15), x)
 }
 
 func TestValidatePathType(t *testing.T) {
@@ -171,18 +121,16 @@ func TestValidatePathType(t *testing.T) {
 	}
 	for _, vt := range validTypes {
 		pt := PathType(vt)
-		if err := pt.Validate(); err != nil {
-			t.Fatalf("Unexpected error with type %v: %v", vt, err)
-		}
+		err := pt.Validate()
+		require.NoError(t, err)
 	}
 	pt := PathType(UnknownPath)
-	if err := pt.Validate(); err == nil {
-		t.Fatalf("Expected validation error but did not get one")
-	}
+	err := pt.Validate()
+	require.Error(t, err)
+
 	pt = PathType(CorePath + 1)
-	if err := pt.Validate(); err == nil {
-		t.Fatalf("Expected validation error but did not get one")
-	}
+	err = pt.Validate()
+	require.Error(t, err)
 }
 
 func TestValidateInfoField(t *testing.T) {
@@ -193,107 +141,155 @@ func TestValidateInfoField(t *testing.T) {
 		Idx:            0,
 		PathType:       CorePath,
 	}
-	if err := infoField.Validate(); err != nil {
-		t.Fatalf("Unexpected error %v", err)
-	}
+	err := infoField.Validate()
+	require.NoError(t, err)
+
 	otherIF := infoField
 	otherIF.BWCls = 64
-	if err := otherIF.Validate(); err == nil {
-		t.Fatalf("Expected validation error but did not get one")
-	}
+	err = otherIF.Validate()
+	require.Error(t, err)
+
 	otherIF = infoField
 	otherIF.RLC = 64
-	if err := otherIF.Validate(); err == nil {
-		t.Fatalf("Expected validation error but did not get one")
-	}
+	err = otherIF.Validate()
+	require.Error(t, err)
+
 	otherIF = infoField
 	otherIF.Idx = 16
-	if err := otherIF.Validate(); err == nil {
-		t.Fatalf("Expected validation error but did not get one")
-	}
+	err = otherIF.Validate()
+	require.Error(t, err)
+
 	otherIF = infoField
 	otherIF.PathType = CorePath + 1
-	if err := otherIF.Validate(); err == nil {
-		t.Fatalf("Expected validation error but did not get one")
-	}
-}
-
-var rawReference = xtest.MustParseHexString("16ebdb4f0d042500")
-var reference = InfoField{
-	ExpirationTick: 384555855,
-	BWCls:          13,
-	RLC:            4,
-	Idx:            2,
-	PathType:       E2EPath,
+	err = otherIF.Validate()
+	require.Error(t, err)
 }
 
 func TestInfoFieldFromRaw(t *testing.T) {
+	reference := newInfoField()
+	rawReference := newInfoFieldRaw()
 	info, err := InfoFieldFromRaw(rawReference)
-	if err != nil {
-		t.Fatalf("Unexpected error: %v", err)
-	}
-	if info.ExpirationTick != reference.ExpirationTick {
-		t.Fatalf("Bad ExpirationTick %v != %v", info.ExpirationTick, reference.ExpirationTick)
-	}
-	if info.BWCls != reference.BWCls {
-		t.Fatalf("Bad BWCls %v != %v", info.BWCls, reference.BWCls)
-	}
-	if info.RLC != reference.RLC {
-		t.Fatalf("Bad RLC %v != %v", info.RLC, reference.RLC)
-	}
-	if info.Idx != reference.Idx {
-		t.Fatalf("Bad Idx %v != %v", info.Idx, reference.Idx)
-	}
-	if info.PathType != reference.PathType {
-		t.Fatalf("Bad PathType %v != %v", info.PathType, reference.PathType)
-	}
+	require.NoError(t, err)
+	require.Equal(t, reference, *info)
 }
 
 func TestInfoFieldRead(t *testing.T) {
+	reference := newInfoField()
+	rawReference := newInfoFieldRaw()
 	raw := make([]byte, InfoFieldLen)
 	// pollute the buffer with garbage
 	for i := 0; i < InfoFieldLen; i++ {
 		raw[i] = byte(i % 256)
 	}
 	n, err := reference.Read(raw)
-	if err != nil {
-		t.Fatalf("Unexpect error: %v", err)
-	}
-	if n != InfoFieldLen {
-		t.Fatalf("Unexpected read size %d. Expected %d", n, InfoFieldLen)
-	}
-
-	if bytes.Compare(raw, rawReference) != 0 {
-		t.Fatalf("Fail to serialize InfoField. %v != %v",
-			hex.EncodeToString(raw), hex.EncodeToString(rawReference))
-	}
+	require.NoError(t, err)
+	require.Equal(t, InfoFieldLen, n)
+	require.Equal(t, rawReference, raw)
 }
 
 func TestValidatePathEndProperties(t *testing.T) {
 	for i := 0; i < 4; i++ {
 		pep := PathEndProps(i)
-		if err := pep.Validate(); err != nil {
-			t.Fatalf("Unexpected error at i = %d: %v", i, err)
-		}
+		err := pep.Validate()
+		require.NoError(t, err)
 	}
 	pep := PathEndProps(4)
-	if err := pep.Validate(); err == nil {
-		t.Fatal("Expected validation error but got none")
-	}
+	err := pep.Validate()
+	require.Error(t, err)
 
 	for i := 0; i < 4; i++ {
 		pep := PathEndProps(i << 4)
-		if err := pep.Validate(); err != nil {
-			t.Fatalf("Unexpected error at i = %d: %v", i, err)
-		}
+		err := pep.Validate()
+		require.NoError(t, err)
 	}
 	pep = PathEndProps(4 << 4)
-	if err := pep.Validate(); err == nil {
-		t.Fatal("Expected validation error but got none")
-	}
+	err = pep.Validate()
+	require.Error(t, err)
 
 	pep = PathEndProps(0x10 | 0x04)
-	if err := pep.Validate(); err == nil {
-		t.Fatal("Expected validation error but got none")
+	err = pep.Validate()
+	require.Error(t, err)
+}
+
+func TestValidateToken(t *testing.T) {
+	tok := newToken()
+	err := tok.Validate()
+	require.NoError(t, err)
+	tok.HopFields = []spath.HopField{}
+	err = tok.Validate()
+	require.Error(t, err)
+}
+
+func TestTokenLen(t *testing.T) {
+	tok := newToken()
+	require.Equal(t, len(newTokenRaw()), tok.Len())
+}
+
+func TestTokenFromRaw(t *testing.T) {
+	referenceRaw := newTokenRaw()
+	reference := newToken()
+	tok, err := TokenFromRaw(referenceRaw)
+	require.NoError(t, err)
+	require.Equal(t, reference, *tok)
+
+	// buffer too small
+	_, err = TokenFromRaw(referenceRaw[:3])
+	require.Error(t, err)
+
+	// one hop field less
+	tok, err = TokenFromRaw(referenceRaw[:len(referenceRaw)-spath.HopFieldLength])
+	require.NoError(t, err)
+	require.Len(t, tok.HopFields, len(reference.HopFields)-1)
+}
+func TestTokenRead(t *testing.T) {
+	tok := newToken()
+	rawReference := newTokenRaw()
+	buf := make([]byte, len(rawReference))
+	c, err := tok.Read(buf)
+	require.NoError(t, err)
+	require.Equal(t, len(buf), c)
+	require.Equal(t, rawReference, buf)
+
+	// buffer too small
+	_, err = tok.Read(buf[:len(rawReference)-1])
+	require.Error(t, err)
+}
+
+func newInfoField() InfoField {
+	return InfoField{
+		ExpirationTick: 384555855,
+		BWCls:          13,
+		RLC:            4,
+		Idx:            2,
+		PathType:       E2EPath,
+	}
+}
+
+func newInfoFieldRaw() []byte {
+	return xtest.MustParseHexString("16ebdb4f0d042500")
+}
+
+func newToken() Token {
+	return Token{
+		InfoField: newInfoField(),
+		HopFields: []spath.HopField{
+			{
+				Xover:       false,
+				ExpTime:     spath.DefaultHopFExpiry,
+				ConsIngress: 1,
+				ConsEgress:  2,
+				Mac:         xtest.MustParseHexString("bad1ce"),
+			},
+			{
+				Xover:       false,
+				ExpTime:     spath.DefaultHopFExpiry,
+				ConsIngress: 1,
+				ConsEgress:  2,
+				Mac:         xtest.MustParseHexString("facade"),
+			},
+		},
 	}
 }
+func newTokenRaw() []byte {
+	return xtest.MustParseHexString("16ebdb4f0d042500003f001002bad1ce003f001002facade")
+}
diff --git a/go/lib/ctrl/colibri_mgmt/colibri_mgmt_test.go b/go/lib/ctrl/colibri_mgmt/colibri_mgmt_test.go
index 6986e38bc..71f7d1f4c 100644
--- a/go/lib/ctrl/colibri_mgmt/colibri_mgmt_test.go
+++ b/go/lib/ctrl/colibri_mgmt/colibri_mgmt_test.go
@@ -135,11 +135,8 @@ func TestSerializeRequest(t *testing.T) {
 				E2ESetup: &colibri_mgmt.E2ESetup{
 					Which: proto.E2ESetupData_Which_success,
 					Success: &colibri_mgmt.E2ESetupSuccess{
-						ReservationID: &colibri_mgmt.E2EReservationID{
-							ASID:   xtest.MustParseHexString("ff00cafe0001"),
-							Suffix: xtest.MustParseHexString("0123456789abcdef0123456789abcdef"),
-						},
-						Token: xtest.MustParseHexString("0000"),
+						ReservationID: newE2EReservationID(),
+						Token:         xtest.MustParseHexString("0000"),
 					},
 				},
 			},
@@ -174,10 +171,7 @@ func TestSerializeRequest(t *testing.T) {
 			Request: &colibri_mgmt.Request{
 				Which: proto.Request_Which_e2eCleanup,
 				E2ECleanup: &colibri_mgmt.E2ECleanup{
-					ReservationID: &colibri_mgmt.E2EReservationID{
-						ASID:   xtest.MustParseHexString("ff00cafe0001"),
-						Suffix: xtest.MustParseHexString("0123456789abcdef0123456789abcdef"),
-					},
+					ReservationID: newE2EReservationID(),
 				},
 			},
 		},
@@ -209,15 +203,12 @@ func TestSerializeResponse(t *testing.T) {
 			Token: xtest.MustParseHexString("0000"),
 		}
 	}
-	newE2ESetupResp := func() *colibri_mgmt.E2ESetup {
+	newE2ESetup := func() *colibri_mgmt.E2ESetup {
 		return &colibri_mgmt.E2ESetup{
 			Which: proto.E2ESetupData_Which_success,
 			Success: &colibri_mgmt.E2ESetupSuccess{
-				ReservationID: &colibri_mgmt.E2EReservationID{
-					ASID:   xtest.MustParseHexString("ff00cafe0001"),
-					Suffix: xtest.MustParseHexString("0123456789abcdef0123456789abcdef"),
-				},
-				Token: xtest.MustParseHexString("0000"),
+				ReservationID: newE2EReservationID(),
+				Token:         xtest.MustParseHexString("0000"),
 			},
 		}
 	}
@@ -315,14 +306,14 @@ func TestSerializeResponse(t *testing.T) {
 		"e2e setup": {
 			Response: &colibri_mgmt.Response{
 				Which:    proto.Response_Which_e2eSetup,
-				E2ESetup: newE2ESetupResp(),
+				E2ESetup: newE2ESetup(),
 				Accepted: true,
 			},
 		},
 		"e2e renewal": {
 			Response: &colibri_mgmt.Response{
 				Which:      proto.Response_Which_e2eRenewal,
-				E2ERenewal: newE2ESetupResp(),
+				E2ERenewal: newE2ESetup(),
 				Accepted:   true,
 			},
 		},
@@ -330,10 +321,7 @@ func TestSerializeResponse(t *testing.T) {
 			Response: &colibri_mgmt.Response{
 				Which: proto.Response_Which_e2eCleanup,
 				E2ECleanup: &colibri_mgmt.E2ECleanup{
-					ReservationID: &colibri_mgmt.E2EReservationID{
-						ASID:   xtest.MustParseHexString("ff00cafe0001"),
-						Suffix: xtest.MustParseHexString("0123456789abcdef0123456789abcdef"),
-					},
+					ReservationID: newE2EReservationID(),
 				},
 				Accepted: true,
 			},
@@ -356,3 +344,10 @@ func TestSerializeResponse(t *testing.T) {
 		})
 	}
 }
+
+func newE2EReservationID() *colibri_mgmt.E2EReservationID {
+	return &colibri_mgmt.E2EReservationID{
+		ASID:   xtest.MustParseHexString("ff00cafe0001"),
+		Suffix: xtest.MustParseHexString("0123456789abcdef0123"),
+	}
+}