From 8ede270ff7f64d7e0e8a4392a6ed9eb5b4f30c83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20A=2E=20Garc=C3=ADa=20Pardo=20Gim=C3=A9nez=20de=20lo?= =?UTF-8?q?s=20Galanes?= Date: Wed, 13 May 2020 15:12:44 +0200 Subject: [PATCH] colibri: application level types requests (#3763) Add functions to convert between application level types and control messages from capnp. --- go/cs/reservation/e2e/BUILD.bazel | 9 +- go/cs/reservation/e2e/request.go | 132 +++++++- go/cs/reservation/e2e/request_test.go | 123 +++++++ go/cs/reservation/segment/BUILD.bazel | 4 + go/cs/reservation/segment/request.go | 81 ++++- go/cs/reservation/segment/request_test.go | 111 +++++++ go/lib/colibri/reservation/BUILD.bazel | 6 +- go/lib/colibri/reservation/types.go | 106 +++++- go/lib/colibri/reservation/types_test.go | 314 +++++++++--------- go/lib/ctrl/colibri_mgmt/colibri_mgmt_test.go | 37 +-- go/lib/ctrl/colibri_mgmt/reservation_ids.go | 2 +- 11 files changed, 721 insertions(+), 204 deletions(-) create mode 100644 go/cs/reservation/e2e/request_test.go create mode 100644 go/cs/reservation/segment/request_test.go diff --git a/go/cs/reservation/e2e/BUILD.bazel b/go/cs/reservation/e2e/BUILD.bazel index c96a3ef8a1..a4e6fc4b91 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 1fb8ecdc12..681bafef51 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 0000000000..e5ea74dff7 --- /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/BUILD.bazel b/go/cs/reservation/segment/BUILD.bazel index 1e67957028..2fdf38857c 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 d171786c62..235594e9db 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/cs/reservation/segment/request_test.go b/go/cs/reservation/segment/request_test.go new file mode 100644 index 0000000000..9132cc7fbf --- /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/BUILD.bazel b/go/lib/colibri/reservation/BUILD.bazel index b274a4c4cd..9ddf6723d3 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 cea6dd5819..8c78edc73f 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/colibri/reservation/types_test.go b/go/lib/colibri/reservation/types_test.go index 6dc03cfcbf..de984f286b 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 6986e38bcb..71f7d1f4cd 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"), + } +} diff --git a/go/lib/ctrl/colibri_mgmt/reservation_ids.go b/go/lib/ctrl/colibri_mgmt/reservation_ids.go index 28ab49e436..1673360f1d 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 {