From c599b885ce9ab81326c67b77cd203a7e38854ccf Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 2 Sep 2019 16:53:23 +0200 Subject: [PATCH 01/53] TCPCL: Contact Header --- cla/tcpcl/contact_header.go | 79 ++++++++++++++++++++++++++++++++ cla/tcpcl/contact_header_test.go | 52 +++++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 cla/tcpcl/contact_header.go create mode 100644 cla/tcpcl/contact_header_test.go diff --git a/cla/tcpcl/contact_header.go b/cla/tcpcl/contact_header.go new file mode 100644 index 00000000..2828fc2d --- /dev/null +++ b/cla/tcpcl/contact_header.go @@ -0,0 +1,79 @@ +package tcpcl + +import ( + "bytes" + "fmt" +) + +// ContactFlags are single-bit flags used in the ContactHeader. +type ContactFlags uint8 + +const ( + // ContactFlags_NONE is a default for no flags + ContactFlags_NONE ContactFlags = 0x00 + + // ContactFlags_CAN_TLS indicates that the sending peer is capable of TLS security. + ContactFlags_CAN_TLS ContactFlags = 0x01 + + // contactFlags_INVALID is a bit field of all invalid ContactFlags. + contactFlags_INVALID ContactFlags = 0xFE +) + +func (cf ContactFlags) String() string { + switch cf { + case ContactFlags_NONE: + return "NONE" + case ContactFlags_CAN_TLS: + return "CAN_TLS" + default: + return "INVALID" + } +} + +// ContactHeader will be exchanged at first after a TCP connection was +// established. Both entities are sending a ContactHeader and are validating +// the peer's one. +type ContactHeader struct { + Flags ContactFlags +} + +// NewContactHeader creates a new ContactHeader with given ContactFlags. +func NewContactHeader(flags ContactFlags) ContactHeader { + return ContactHeader{ + Flags: flags, + } +} + +func (ch ContactHeader) String() string { + return fmt.Sprintf("ContactHeader(Version=4, Flags=%v)", ch.Flags) +} + +// MarshalBinary encodes this ContactHeader into its binary form. +func (ch ContactHeader) MarshalBinary() (data []byte, _ error) { + // magic='dtn!', version=4, flags=flags + data = []byte{0x64, 0x74, 0x6E, 0x21, 0x04, byte(ch.Flags)} + return +} + +// UnmarshalBinary decodes a ContactHeader from its binary form. +func (ch *ContactHeader) UnmarshalBinary(data []byte) error { + if len(data) != 6 { + return fmt.Errorf("ContactHeader's length is wrong: %d instead of 6", len(data)) + } + + if !bytes.Equal(data[:4], []byte("dtn!")) { + return fmt.Errorf("ContactHeader's magic does not match: %x != 'dtn!'", data[:4]) + } + + if uint8(data[4]) != 4 { + return fmt.Errorf("ContactHeader's version is wrong: %d instead of 4", uint8(data[4])) + } + + if cf := ContactFlags(data[5]); cf&contactFlags_INVALID != 0 { + return fmt.Errorf("ContactHeader's flags %x contain invalid flags", cf) + } else { + ch.Flags = cf + } + + return nil +} diff --git a/cla/tcpcl/contact_header_test.go b/cla/tcpcl/contact_header_test.go new file mode 100644 index 00000000..690ae073 --- /dev/null +++ b/cla/tcpcl/contact_header_test.go @@ -0,0 +1,52 @@ +package tcpcl + +import ( + "bytes" + "reflect" + "testing" +) + +func TestContactHeaderMarshal(t *testing.T) { + tests := []struct { + contactHeader ContactHeader + expectedData []byte + }{ + {NewContactHeader(ContactFlags_NONE), []byte{0x64, 0x74, 0x6E, 0x21, 0x04, 0x00}}, + {NewContactHeader(ContactFlags_CAN_TLS), []byte{0x64, 0x74, 0x6E, 0x21, 0x04, 0x01}}, + } + + for _, test := range tests { + if data, err := test.contactHeader.MarshalBinary(); err != nil { + t.Fatal(err) + } else if !bytes.Equal(data, test.expectedData) { + t.Fatalf("Data does not match, expected %x and got %x", test.expectedData, data) + } + } +} + +func TestContactHeaderUnmarshal(t *testing.T) { + tests := []struct { + data []byte + valid bool + contactHeader ContactHeader + }{ + {[]byte{0x64, 0x74, 0x6E, 0x21, 0x04, 0x00}, true, ContactHeader{Flags: ContactFlags_NONE}}, + {[]byte{0x64, 0x74, 0x6E, 0x21, 0x04, 0x01}, true, ContactHeader{Flags: ContactFlags_CAN_TLS}}, + {[]byte{0x64, 0x74, 0x6E, 0x21, 0x04}, false, ContactHeader{}}, + {[]byte{0x64, 0x74, 0x6E, 0x21, 0x04, 0x00, 0x00}, false, ContactHeader{}}, + {[]byte{0x64, 0x74, 0x6E, 0x3F, 0x04, 0x00}, false, ContactHeader{}}, + {[]byte{0x64, 0x74, 0x6E, 0x21, 0x23, 0x00}, false, ContactHeader{}}, + {[]byte{0x64, 0x74, 0x6E, 0x21, 0x04, 0x23}, false, ContactHeader{}}, + } + + for _, test := range tests { + var ch ContactHeader + if err := ch.UnmarshalBinary(test.data); (err == nil) != test.valid { + t.Fatalf("Error state was not expected; valid := %t, got := %v", test.valid, err) + } else if !test.valid { + continue + } else if !reflect.DeepEqual(test.contactHeader, ch) { + t.Fatalf("ContactHeader does not match, expected %v and got %v", test.contactHeader, ch) + } + } +} From 902529b971cf51fd32101759f3de60446c78ba8c Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Thu, 5 Sep 2019 10:42:01 +0200 Subject: [PATCH 02/53] TCPCL: Session Initialization Message --- cla/tcpcl/message_sess_init.go | 126 +++++++++++++++++++++ cla/tcpcl/message_sess_init_test.go | 169 ++++++++++++++++++++++++++++ 2 files changed, 295 insertions(+) create mode 100644 cla/tcpcl/message_sess_init.go create mode 100644 cla/tcpcl/message_sess_init_test.go diff --git a/cla/tcpcl/message_sess_init.go b/cla/tcpcl/message_sess_init.go new file mode 100644 index 00000000..936907f6 --- /dev/null +++ b/cla/tcpcl/message_sess_init.go @@ -0,0 +1,126 @@ +package tcpcl + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +// SESS_INIT is the Message Header code for a Session Initialization Message. +const SESS_INIT uint8 = 0x07 + +// SessionInitMessage is the SESS_INIT message to negotiate session parameters. +type SessionInitMessage struct { + KeepaliveInterval uint16 + SegmentMru uint64 + TransferMru uint64 + Eid string + + // TODO: Session Extension Items +} + +// NewSessionInitMessage creates a new SessionInitMessage with given fields. +func NewSessionInitMessage(keepaliveInterval uint16, segmentMru, transferMru uint64, eid string) SessionInitMessage { + return SessionInitMessage{ + KeepaliveInterval: keepaliveInterval, + SegmentMru: segmentMru, + TransferMru: transferMru, + Eid: eid, + } +} + +func (si SessionInitMessage) String() string { + return fmt.Sprintf( + "SESS_INIT(Keepalive Interval=%d, Segment MRU=%d, Transfer MRU=%d, EID=%s)", + si.KeepaliveInterval, si.SegmentMru, si.TransferMru, si.Eid) +} + +// MarshalBinary encodes this SessionInitMessage into its binary form. +func (si SessionInitMessage) MarshalBinary() (data []byte, err error) { + var buf = new(bytes.Buffer) + var fields = []interface{}{ + SESS_INIT, + si.KeepaliveInterval, + si.SegmentMru, + si.TransferMru, + uint16(len(si.Eid))} + + for _, field := range fields { + if binErr := binary.Write(buf, binary.BigEndian, field); binErr != nil { + err = binErr + return + } + } + + if n, _ := buf.WriteString(si.Eid); n != len(si.Eid) { + err = fmt.Errorf("SESS_INIT EID's length is %d, but only wrote %d bytes", len(si.Eid), n) + return + } + + // TODO: Session Extension Items + // Currently, only an empty Session Extension Items Length is accepted. + if binErr := binary.Write(buf, binary.BigEndian, uint32(0)); binErr != nil { + err = binErr + return + } + + data = buf.Bytes() + return +} + +// UnmarshalBinary decodes a SessionInitMessage from its binary form. +func (si *SessionInitMessage) UnmarshalBinary(data []byte) error { + var buf = bytes.NewReader(data) + + var messageHeader uint8 + if err := binary.Read(buf, binary.BigEndian, &messageHeader); err != nil { + return err + } else if messageHeader != SESS_INIT { + return fmt.Errorf("SESS_INIT's Message Header is wrong: %d instead of %d", messageHeader, SESS_INIT) + } + + var eidLength uint16 + var fields = []interface{}{ + &si.KeepaliveInterval, + &si.SegmentMru, + &si.TransferMru, + &eidLength, + } + + for _, field := range fields { + if err := binary.Read(buf, binary.BigEndian, field); err != nil { + return err + } + } + + var eidBuff = make([]byte, eidLength) + if n, err := buf.Read(eidBuff); err != nil { + return err + } else if uint16(n) != eidLength { + return fmt.Errorf("SESS_INIT's EID length differs: expected %d and got %d", eidLength, n) + } else { + si.Eid = string(eidBuff) + } + + // TODO: Session Extension Items, see above + var sessionExtsLen uint32 + if err := binary.Read(buf, binary.BigEndian, &sessionExtsLen); err != nil { + return err + } else if sessionExtsLen > 0 { + sessionExtsBuff := make([]byte, sessionExtsLen) + + if n, err := buf.Read(sessionExtsBuff); err != nil { + return err + } else if uint32(n) != sessionExtsLen { + return fmt.Errorf( + "SESS_INIT's Session Extension Length differs: expected %d and got %d", + sessionExtsLen, n) + } + } + + if n := buf.Len(); n > 0 { + return fmt.Errorf("SESS_INIT's buffer should be empty; has %d octets", n) + } + + return nil +} diff --git a/cla/tcpcl/message_sess_init_test.go b/cla/tcpcl/message_sess_init_test.go new file mode 100644 index 00000000..2ca60163 --- /dev/null +++ b/cla/tcpcl/message_sess_init_test.go @@ -0,0 +1,169 @@ +package tcpcl + +import ( + "bytes" + "reflect" + "testing" +) + +func TestSessionInitMessage(t *testing.T) { + t1data := []byte{ + // Message Header: + 0x07, + // Keepalive Interval (u16): + 0x00, 0x00, + // Segment MRU (u64): + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Transfer MRU (u64): + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // EID Length (u16): + 0x00, 0x08, + // EID Data: + 0x64, 0x74, 0x6e, 0x3a, 0x6e, 0x6f, 0x6e, 0x65, + // Session Extension Item Length (u32): + 0x00, 0x00, 0x00, 0x00, + } + t1session := NewSessionInitMessage(0, 0, 0, "dtn:none") + + t2data := []byte{ + // Message Header: + 0x07, + // Keepalive Interval (u16): + 0x0E, 0x10, + // Segment MRU (u64): + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x68, + // Transfer MRU (u64): + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xFC, + // EID Length (u16): + 0x00, 0x0D, + // EID Data: + 0x64, 0x74, 0x6e, 0x3a, 0x2f, 0x2f, 0x66, 0x6f, 0x6f, 0x2f, 0x62, 0x61, 0x72, + // Session Extension Item Length (u32): + 0x00, 0x00, 0x00, 0x00, + } + t2session := NewSessionInitMessage(3600, 4200, 2300, "dtn://foo/bar") + + t3data := []byte{ + // Message Header: + 0x07, + // Keepalive Interval (u16): + 0x00, 0x01, + // Segment MRU (u64): + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x68, + // Transfer MRU (u64): + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xFC, + // EID Length (u16): + 0x00, 0x00, + // EID Data: none + // Session Extension Item Length (u32): + 0x00, 0x00, 0x00, 0x00, + } + t3session := NewSessionInitMessage(1, 4200, 2300, "") + + t4data := []byte{ + // Message Header: + 0xFF, + // Keepalive Interval (u16): + 0x00, 0x00, + // Segment MRU (u64): + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Transfer MRU (u64): + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // EID Length (u16): + 0x00, 0x00, + // EID Data: none + // Session Extension Item Length (u32): + 0x00, 0x00, 0x00, 0x00, + } + t4session := SessionInitMessage{} + + t5data := []byte{ + // Message Header: + 0x07, + // Keepalive Interval (u16): + 0x00, 0x00, + // Segment MRU (u64): + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Transfer MRU (u64): + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // EID Length (u16): + 0x00, 0x05, + // EID Data: + 0x64, 0x74, 0x6e, 0x3a, 0x6e, 0x6f, 0x6e, 0x65, + // Session Extension Item Length (u32): + 0x00, 0x00, 0x00, 0x00, + } + t5session := SessionInitMessage{} + + t6data := []byte{ + // Message Header: + 0x07, + // Keepalive Interval (u16): + 0x00, 0x00, + // Segment MRU (u64): + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Transfer MRU (u64): + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // EID Length (u16): + 0x00, 0x00, + // EID Data: none + // Session Extension Item Length (u32): + 0x00, 0x00, 0x00, 0x08, + // Session Extension Items: + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + } + t6session := SessionInitMessage{} + + t7data := []byte{ + // Message Header: + 0x07, + // Keepalive Interval (u16): + 0x00, 0x00, + // Segment MRU (u64): + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Transfer MRU (u64): + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // EID Length (u16): + 0x00, 0x00, + // EID Data: none + // Session Extension Item Length (u32): + 0x00, 0x00, 0x00, 0x0F, + // Session Extension Items: + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + } + t7session := SessionInitMessage{} + + tests := []struct { + valid bool + bijective bool + sim SessionInitMessage + data []byte + }{ + {true, true, t1session, t1data}, + {true, true, t2session, t2data}, + {true, true, t3session, t3data}, + {false, false, t4session, t4data}, + {false, false, t5session, t5data}, + {true, false, t6session, t6data}, + {false, false, t7session, t7data}, + } + + for _, test := range tests { + // Decode + var sim SessionInitMessage + if err := sim.UnmarshalBinary(test.data); (err == nil) != test.valid { + t.Fatalf("Error state was not expected; valid := %t, got := %v", test.valid, err) + } else if !test.valid { + continue + } else if !reflect.DeepEqual(test.sim, sim) { + t.Fatalf("SessionInitMessage does not match, expected %v and got %v", test.sim, sim) + } + + // Encode + if data, err := test.sim.MarshalBinary(); err != nil { + t.Fatal(err) + } else if test.bijective && !bytes.Equal(data, test.data) { + t.Fatalf("Data does not match, expected %x and got %x", test.data, data) + } + } +} From 50df8553661fb16750145ef1847bd107b7fb2d47 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Thu, 5 Sep 2019 11:01:40 +0200 Subject: [PATCH 03/53] TCPCL: Keepalive/Session Upkeep Message --- cla/tcpcl/message_keepalive.go | 44 +++++++++++++++++++++++++++++ cla/tcpcl/message_keepalive_test.go | 35 +++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 cla/tcpcl/message_keepalive.go create mode 100644 cla/tcpcl/message_keepalive_test.go diff --git a/cla/tcpcl/message_keepalive.go b/cla/tcpcl/message_keepalive.go new file mode 100644 index 00000000..317e887f --- /dev/null +++ b/cla/tcpcl/message_keepalive.go @@ -0,0 +1,44 @@ +package tcpcl + +import "fmt" + +// KEEPALIVE is the Message Header code for a Keepalive Message. +const KEEPALIVE uint8 = 0x04 + +// KeepaliveMessage is the KEEPALIVE message for session upkeep. +type KeepaliveMessage uint8 + +// NewKeepaliveMessage creates a new KeepaliveMessage. +func NewKeepaliveMessage() KeepaliveMessage { + return KeepaliveMessage(KEEPALIVE) +} + +func (_ KeepaliveMessage) String() string { + return "KEEPALIVE" +} + +// MarshalBinary encodes this KeepaliveMessage into its binary form. +func (km KeepaliveMessage) MarshalBinary() (data []byte, err error) { + if uint8(km) != KEEPALIVE { + err = fmt.Errorf("KEEPALIVE's value is %d instead of %d", uint8(km), KEEPALIVE) + return + } + + data = []byte{KEEPALIVE} + return +} + +// UnmarshalBinary decodes a KeepaliveMessage from its binary form. +func (km *KeepaliveMessage) UnmarshalBinary(data []byte) error { + if len(data) != 1 { + return fmt.Errorf("KEEPALIVE's octet length is %d instead of 1", len(data)) + } + + if x := uint8(data[0]); x != KEEPALIVE { + return fmt.Errorf("KEEPALIVE's value is %d instead of %d", x, KEEPALIVE) + } else { + *km = KeepaliveMessage(x) + } + + return nil +} diff --git a/cla/tcpcl/message_keepalive_test.go b/cla/tcpcl/message_keepalive_test.go new file mode 100644 index 00000000..0287a79d --- /dev/null +++ b/cla/tcpcl/message_keepalive_test.go @@ -0,0 +1,35 @@ +package tcpcl + +import ( + "bytes" + "testing" +) + +func TestKeepaliveMessage(t *testing.T) { + tests := []struct { + valid bool + data []byte + }{ + {true, []byte{KEEPALIVE}}, + {false, []byte{0x21}}, + {false, []byte{}}, + {false, []byte{0x23, 0x42}}, + } + + for _, test := range tests { + var km KeepaliveMessage + + if err := km.UnmarshalBinary(test.data); (err == nil) != test.valid { + t.Fatalf("Error state was not expected; valid := %t, got := %v", test.valid, err) + } else if !test.valid { + continue + } + + if data, err := NewKeepaliveMessage().MarshalBinary(); err != nil { + t.Fatal(err) + } else if !bytes.Equal(data, test.data) { + t.Fatalf("Data does not match, expected %x and got %x", test.data, data) + } + + } +} From a0d3af07c1d2eea40a4d92395deb986b2afe6a4c Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Thu, 5 Sep 2019 11:45:11 +0200 Subject: [PATCH 04/53] TCPCL: Message Rejection Message --- cla/tcpcl/message_reject.go | 107 +++++++++++++++++++++++++++++++ cla/tcpcl/message_reject_test.go | 38 +++++++++++ 2 files changed, 145 insertions(+) create mode 100644 cla/tcpcl/message_reject.go create mode 100644 cla/tcpcl/message_reject_test.go diff --git a/cla/tcpcl/message_reject.go b/cla/tcpcl/message_reject.go new file mode 100644 index 00000000..5d6bf556 --- /dev/null +++ b/cla/tcpcl/message_reject.go @@ -0,0 +1,107 @@ +package tcpcl + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +// MessageRejectionReason is the one-octet refusal code from a MessageRejectionMessage. +type MessageRejectionReason uint8 + +const ( + // RejectionTypeUnknown indicates an unknown Message Type Code. + RejectionTypeUnknown MessageRejectionReason = 0x01 + + // RejectionUnsupported indicates that this TCPCL node cannot comply with + // the message content. + RejectionUnsupported MessageRejectionReason = 0x02 + + // RejectionUnexptected indicates that this TCPCL node received a message + // while the session is in a state in which the message is not expected. + RejectionUnexptected MessageRejectionReason = 0x03 + + // messageRejectionReason_INVALID is a bit field of all invalid MessageRejectionMessages. + messageRejectionReason_INVALID = 0xFC +) + +func (mrr MessageRejectionReason) String() string { + switch mrr { + case RejectionTypeUnknown: + return "Message Type Unknown" + case RejectionUnsupported: + return "Message Unsupported" + case RejectionUnexptected: + return "Message Unexpected" + default: + return "INVALID" + } +} + +// MSG_REJECT is the Message Header code for a Message Rejection Message. +const MSG_REJECT uint8 = 0x06 + +// MessageRejectionMessage is the MSG_REJECT message for message rejection. +type MessageRejectionMessage struct { + ReasonCode MessageRejectionReason + MessageHeader uint8 +} + +// NewMessageRejectionMessage creates a new MessageRejectionMessage with given fields. +func NewMessageRejectionMessage(reasonCode MessageRejectionReason, messageHeader uint8) MessageRejectionMessage { + return MessageRejectionMessage{ + ReasonCode: reasonCode, + MessageHeader: messageHeader, + } +} + +func (mrm MessageRejectionMessage) String() string { + return fmt.Sprintf( + "MSG_REJECT(Reason Code=%v, Rejected Message Header=%d)", + mrm.ReasonCode, mrm.MessageHeader) +} + +// MarshalBinary encodes this MessageRejectionMessage into its binary form. +func (mrm MessageRejectionMessage) MarshalBinary() (data []byte, err error) { + var buf = new(bytes.Buffer) + var fields = []interface{}{ + MSG_REJECT, + mrm.ReasonCode, + mrm.MessageHeader} + + for _, field := range fields { + if binErr := binary.Write(buf, binary.BigEndian, field); binErr != nil { + err = binErr + return + } + } + + data = buf.Bytes() + return +} + +// UnmarshalBinary decodes a MessageRejectionMessage from its binary form. +func (mrm *MessageRejectionMessage) UnmarshalBinary(data []byte) error { + var buf = bytes.NewReader(data) + + var messageHeader uint8 + if err := binary.Read(buf, binary.BigEndian, &messageHeader); err != nil { + return err + } else if messageHeader != MSG_REJECT { + return fmt.Errorf("MSG_REJECT's Message Header is wrong: %d instead of %d", messageHeader, MSG_REJECT) + } + + var fields = []interface{}{&mrm.ReasonCode, &mrm.MessageHeader} + + for _, field := range fields { + if err := binary.Read(buf, binary.BigEndian, field); err != nil { + return err + } + } + + if mrm.ReasonCode&messageRejectionReason_INVALID != 0 { + return fmt.Errorf("MSG_REJECT's Reason Code %x is invalid", mrm.ReasonCode) + } + + return nil +} diff --git a/cla/tcpcl/message_reject_test.go b/cla/tcpcl/message_reject_test.go new file mode 100644 index 00000000..6c01a780 --- /dev/null +++ b/cla/tcpcl/message_reject_test.go @@ -0,0 +1,38 @@ +package tcpcl + +import ( + "bytes" + "reflect" + "testing" +) + +func TestMessageRejectionMessage(t *testing.T) { + tests := []struct { + valid bool + data []byte + mrm MessageRejectionMessage + }{ + {true, []byte{0x06, 0x01, 0x01}, NewMessageRejectionMessage(RejectionTypeUnknown, 0x01)}, + {true, []byte{0x06, 0x03, 0x01}, NewMessageRejectionMessage(RejectionUnexptected, 0x01)}, + {false, []byte{0x07, 0x00, 0x00}, MessageRejectionMessage{}}, + {false, []byte{0x06, 0xF0, 0x00}, MessageRejectionMessage{}}, + } + + for _, test := range tests { + var mrm MessageRejectionMessage + + if err := mrm.UnmarshalBinary(test.data); (err == nil) != test.valid { + t.Fatalf("Error state was not expected; valid := %t, got := %v", test.valid, err) + } else if !test.valid { + continue + } else if !reflect.DeepEqual(test.mrm, mrm) { + t.Fatalf("MessageRejectionMessage does not match, expected %v and got %v", test.mrm, mrm) + } + + if data, err := test.mrm.MarshalBinary(); err != nil { + t.Fatal(err) + } else if !bytes.Equal(data, test.data) { + t.Fatalf("Data does not match, expected %x and got %x", test.data, data) + } + } +} From ca5124085aee9c714182a27e8468f774c6e92dbe Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Thu, 5 Sep 2019 13:59:38 +0200 Subject: [PATCH 05/53] TCPCL: Treat Contact Header's flags as bit flags --- cla/tcpcl/contact_header.go | 21 +++++++++------------ cla/tcpcl/contact_header_test.go | 8 ++++---- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/cla/tcpcl/contact_header.go b/cla/tcpcl/contact_header.go index 2828fc2d..c493fe4b 100644 --- a/cla/tcpcl/contact_header.go +++ b/cla/tcpcl/contact_header.go @@ -3,31 +3,28 @@ package tcpcl import ( "bytes" "fmt" + "strings" ) // ContactFlags are single-bit flags used in the ContactHeader. type ContactFlags uint8 const ( - // ContactFlags_NONE is a default for no flags - ContactFlags_NONE ContactFlags = 0x00 - - // ContactFlags_CAN_TLS indicates that the sending peer is capable of TLS security. - ContactFlags_CAN_TLS ContactFlags = 0x01 + // ContactCanTls indicates that the sending peer is capable of TLS security. + ContactCanTls ContactFlags = 0x01 // contactFlags_INVALID is a bit field of all invalid ContactFlags. contactFlags_INVALID ContactFlags = 0xFE ) func (cf ContactFlags) String() string { - switch cf { - case ContactFlags_NONE: - return "NONE" - case ContactFlags_CAN_TLS: - return "CAN_TLS" - default: - return "INVALID" + var flags []string + + if cf&ContactCanTls != 0 { + flags = append(flags, "CAN_TLS") } + + return strings.Join(flags, ",") } // ContactHeader will be exchanged at first after a TCP connection was diff --git a/cla/tcpcl/contact_header_test.go b/cla/tcpcl/contact_header_test.go index 690ae073..c3650ad4 100644 --- a/cla/tcpcl/contact_header_test.go +++ b/cla/tcpcl/contact_header_test.go @@ -11,8 +11,8 @@ func TestContactHeaderMarshal(t *testing.T) { contactHeader ContactHeader expectedData []byte }{ - {NewContactHeader(ContactFlags_NONE), []byte{0x64, 0x74, 0x6E, 0x21, 0x04, 0x00}}, - {NewContactHeader(ContactFlags_CAN_TLS), []byte{0x64, 0x74, 0x6E, 0x21, 0x04, 0x01}}, + {NewContactHeader(0), []byte{0x64, 0x74, 0x6E, 0x21, 0x04, 0x00}}, + {NewContactHeader(ContactCanTls), []byte{0x64, 0x74, 0x6E, 0x21, 0x04, 0x01}}, } for _, test := range tests { @@ -30,8 +30,8 @@ func TestContactHeaderUnmarshal(t *testing.T) { valid bool contactHeader ContactHeader }{ - {[]byte{0x64, 0x74, 0x6E, 0x21, 0x04, 0x00}, true, ContactHeader{Flags: ContactFlags_NONE}}, - {[]byte{0x64, 0x74, 0x6E, 0x21, 0x04, 0x01}, true, ContactHeader{Flags: ContactFlags_CAN_TLS}}, + {[]byte{0x64, 0x74, 0x6E, 0x21, 0x04, 0x00}, true, ContactHeader{Flags: 0}}, + {[]byte{0x64, 0x74, 0x6E, 0x21, 0x04, 0x01}, true, ContactHeader{Flags: ContactCanTls}}, {[]byte{0x64, 0x74, 0x6E, 0x21, 0x04}, false, ContactHeader{}}, {[]byte{0x64, 0x74, 0x6E, 0x21, 0x04, 0x00, 0x00}, false, ContactHeader{}}, {[]byte{0x64, 0x74, 0x6E, 0x3F, 0x04, 0x00}, false, ContactHeader{}}, From 8e11f26a265afbd6f707a9022d2f3a5accd9f67b Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Thu, 5 Sep 2019 15:25:08 +0200 Subject: [PATCH 06/53] TCPCL: Data Transmission Message --- cla/tcpcl/message_xfer_segment.go | 141 +++++++++++++++++++++++++ cla/tcpcl/message_xfer_segment_test.go | 119 +++++++++++++++++++++ 2 files changed, 260 insertions(+) create mode 100644 cla/tcpcl/message_xfer_segment.go create mode 100644 cla/tcpcl/message_xfer_segment_test.go diff --git a/cla/tcpcl/message_xfer_segment.go b/cla/tcpcl/message_xfer_segment.go new file mode 100644 index 00000000..a6f4a0b7 --- /dev/null +++ b/cla/tcpcl/message_xfer_segment.go @@ -0,0 +1,141 @@ +package tcpcl + +import ( + "bytes" + "encoding/binary" + "fmt" + "strings" +) + +// SegmentFlags are an one-octet field of single-bit flags for a XFER_SEGMENT. +type SegmentFlags uint8 + +const ( + // SegmentEnd indicates that this segment is the last of the transfer. + SegmentEnd SegmentFlags = 0x01 + + // SegmentStart indicates that this segment is the first of the transfer. + SegmentStart SegmentFlags = 0x02 + + // segmentFlags_INVALID is a bit field of all invalid ContactFlags. + segmentFlags_INVALID SegmentFlags = 0xFC +) + +func (sf SegmentFlags) String() string { + var flags []string + + if sf&SegmentEnd != 0 { + flags = append(flags, "END") + } + if sf&SegmentStart != 0 { + flags = append(flags, "START") + } + + return strings.Join(flags, ",") +} + +// XFER_SEGMENT is the Message Header code for a Data Transmission Message. +const XFER_SEGMENT uint8 = 0x01 + +// DataTransmissionMessage is the XFER_SEGMENT message for data transmission. +type DataTransmissionMessage struct { + Flags SegmentFlags + TransferId uint64 + Data []byte + + // TODO: Transfer Extension Items +} + +// NewDataTransmissionMessage creates a new DataTransmissionMessage with given fields. +func NewDataTransmissionMessage(flags SegmentFlags, tid uint64, data []byte) DataTransmissionMessage { + return DataTransmissionMessage{ + Flags: flags, + TransferId: tid, + Data: data, + } +} + +func (dtm DataTransmissionMessage) String() string { + return fmt.Sprintf( + "XFER_SEGMENT(Message Flags=%v, Transfer ID=%d, Data=%x)", + dtm.Flags, dtm.TransferId, dtm.Data) +} + +// MarshalBinary encodes this DataTransmissionMessage into its binary form. +func (dtm DataTransmissionMessage) MarshalBinary() (data []byte, err error) { + var buf = new(bytes.Buffer) + var fields = []interface{}{ + XFER_SEGMENT, + dtm.Flags, + dtm.TransferId, + uint32(0), // TODO: Transfer Extension Items + uint64(len(dtm.Data))} + + for _, field := range fields { + if binErr := binary.Write(buf, binary.BigEndian, field); binErr != nil { + err = binErr + return + } + } + + if n, _ := buf.Write(dtm.Data); n != len(dtm.Data) { + err = fmt.Errorf("XFER_SEGMENT Data length is %d, but only wrote %d bytes", len(dtm.Data), n) + return + } + + data = buf.Bytes() + return +} + +// UnmarshalBinary decodes a DataTransmissionMessage from its binary form. +func (dtm *DataTransmissionMessage) UnmarshalBinary(data []byte) error { + var buf = bytes.NewReader(data) + + var messageHeader uint8 + if err := binary.Read(buf, binary.BigEndian, &messageHeader); err != nil { + return err + } else if messageHeader != XFER_SEGMENT { + return fmt.Errorf("XFER_SEGMENT's Message Header is wrong: %d instead of %d", messageHeader, XFER_SEGMENT) + } + + var transferExtLen uint32 + var fields = []interface{}{&dtm.Flags, &dtm.TransferId, &transferExtLen} + + for _, field := range fields { + if err := binary.Read(buf, binary.BigEndian, field); err != nil { + return err + } + } + + // TODO: Transfer Extension Items + if transferExtLen > 0 { + transferExtBuff := make([]byte, transferExtLen) + + if n, err := buf.Read(transferExtBuff); err != nil { + return err + } else if uint32(n) != transferExtLen { + return fmt.Errorf( + "XFER_SEGMENT's Transfer Extension Length differs: expected %d and got %d", + transferExtLen, n) + } + } + + var dataLen uint64 + if err := binary.Read(buf, binary.BigEndian, &dataLen); err != nil { + return err + } else if dataLen > 0 { + dataBuff := make([]byte, dataLen) + + if n, err := buf.Read(dataBuff); err != nil { + return err + } else if uint64(n) != dataLen { + return fmt.Errorf( + "XFER_SEGMENT's Data length differs: expected %d and got %d", + dataLen, n) + } else { + dtm.Data = dataBuff + } + } + + return nil +} diff --git a/cla/tcpcl/message_xfer_segment_test.go b/cla/tcpcl/message_xfer_segment_test.go new file mode 100644 index 00000000..33454b01 --- /dev/null +++ b/cla/tcpcl/message_xfer_segment_test.go @@ -0,0 +1,119 @@ +package tcpcl + +import ( + "bytes" + "reflect" + "testing" +) + +func TestDataTransmissionMessage(t *testing.T) { + t1data := []byte{ + // Message Header: + 0x01, + // Message Flags, START: + 0x02, + // Transfer ID: + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + // Transfer Extension Item Length: + 0x00, 0x00, 0x00, 0x00, + // Data Length: + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + // Data: + 0x75, 0x66, 0x66, + } + t1message := NewDataTransmissionMessage(SegmentStart, 1, []byte("uff")) + + t2data := []byte{ + // Message Header: + 0x01, + // Message Flags, START: + 0x03, + // Transfer ID: + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + // Transfer Extension Item Length: + 0x00, 0x00, 0x00, 0x00, + // Data Length: + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + // Data: + 0x75, 0x66, 0x66, + } + t2message := NewDataTransmissionMessage(SegmentStart|SegmentEnd, 1, []byte("uff")) + + t3data := []byte{ + // Message Header: + 0x04, + // Message Flags, START: + 0x00, + // Transfer ID: + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Transfer Extension Item Length: + 0x00, 0x00, 0x00, 0x00, + // Data Length: + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Data: + } + t3message := DataTransmissionMessage{} + + t4data := []byte{ + // Message Header: + 0x01, + // Message Flags, START: + 0x00, + // Transfer ID: + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + // Transfer Extension Item Length: + 0x00, 0x00, 0x00, 0x00, + // Data Length: + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, + // Data: + } + t4message := DataTransmissionMessage{} + + t5data := []byte{ + // Message Header: + 0x01, + // Message Flags, START: + 0x00, + // Transfer ID: + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + // Transfer Extension Item Length: + 0x00, 0x00, 0x00, 0x01, + // Transfer Extension Items: + 0xFF, + // Data Length: + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Data: + } + t5message := NewDataTransmissionMessage(0, 1, nil) + + tests := []struct { + valid bool + bijective bool + data []byte + dtm DataTransmissionMessage + }{ + {true, true, t1data, t1message}, + {true, true, t2data, t2message}, + {false, false, t3data, t3message}, + {false, false, t4data, t4message}, + {true, false, t5data, t5message}, + } + + for _, test := range tests { + var dtm DataTransmissionMessage + + if err := dtm.UnmarshalBinary(test.data); (err == nil) != test.valid { + t.Fatalf("Error state was not expected; valid := %t, got := %v", test.valid, err) + } else if !test.valid { + continue + } else if !reflect.DeepEqual(test.dtm, dtm) { + t.Fatalf("DataTransmissionMessage does not match, expected %v and got %v", test.dtm, dtm) + } + + if data, err := test.dtm.MarshalBinary(); err != nil { + t.Fatal(err) + } else if test.bijective && !bytes.Equal(data, test.data) { + t.Fatalf("Data does not match, expected %x and got %x", test.data, data) + } + } +} From fbf92155071da7a222bdad096063598c93209109 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Thu, 5 Sep 2019 15:49:22 +0200 Subject: [PATCH 07/53] TCPCL: Data Acknowledgement Message --- cla/tcpcl/message_xfer_ack.go | 74 ++++++++++++++++++++++++++++++ cla/tcpcl/message_xfer_ack_test.go | 60 ++++++++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 cla/tcpcl/message_xfer_ack.go create mode 100644 cla/tcpcl/message_xfer_ack_test.go diff --git a/cla/tcpcl/message_xfer_ack.go b/cla/tcpcl/message_xfer_ack.go new file mode 100644 index 00000000..7d208eaa --- /dev/null +++ b/cla/tcpcl/message_xfer_ack.go @@ -0,0 +1,74 @@ +package tcpcl + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +// XFER_ACK is the Message Header code for a Data Acknowledgement Message. +const XFER_ACK uint8 = 0x02 + +// DataAcknowledgementMessage is the XFER_ACK message for data acknowledgements. +type DataAcknowledgementMessage struct { + Flags SegmentFlags + TransferId uint64 + AckLen uint64 +} + +// NewDataAcknowledgementMessage creates a new DataAcknowledgementMessage with given fields. +func NewDataAcknowledgementMessage(flags SegmentFlags, tid, acklen uint64) DataAcknowledgementMessage { + return DataAcknowledgementMessage{ + Flags: flags, + TransferId: tid, + AckLen: acklen, + } +} + +func (dam DataAcknowledgementMessage) String() string { + return fmt.Sprintf( + "XFER_ACK(Message Flags=%v, Transfer ID=%d, Acknowledged length=%d)", + dam.Flags, dam.TransferId, dam.AckLen) +} + +// MarshalBinary encodes this DataAcknowledgementMessage into its binary form. +func (dam DataAcknowledgementMessage) MarshalBinary() (data []byte, err error) { + var buf = new(bytes.Buffer) + var fields = []interface{}{ + XFER_ACK, + dam.Flags, + dam.TransferId, + dam.AckLen} + + for _, field := range fields { + if binErr := binary.Write(buf, binary.BigEndian, field); binErr != nil { + err = binErr + return + } + } + + data = buf.Bytes() + return +} + +// UnmarshalBinary decodes a DataAcknowledgementMessage from its binary form. +func (dam *DataAcknowledgementMessage) UnmarshalBinary(data []byte) error { + var buf = bytes.NewReader(data) + + var messageHeader uint8 + if err := binary.Read(buf, binary.BigEndian, &messageHeader); err != nil { + return err + } else if messageHeader != XFER_ACK { + return fmt.Errorf("XFER_ACK's Message Header is wrong: %d instead of %d", messageHeader, XFER_ACK) + } + + var fields = []interface{}{&dam.Flags, &dam.TransferId, &dam.AckLen} + + for _, field := range fields { + if err := binary.Read(buf, binary.BigEndian, field); err != nil { + return err + } + } + + return nil +} diff --git a/cla/tcpcl/message_xfer_ack_test.go b/cla/tcpcl/message_xfer_ack_test.go new file mode 100644 index 00000000..77fd2e82 --- /dev/null +++ b/cla/tcpcl/message_xfer_ack_test.go @@ -0,0 +1,60 @@ +package tcpcl + +import ( + "bytes" + "reflect" + "testing" +) + +func TestDataAcknowledgementMessage(t *testing.T) { + t1data := []byte{ + // Message Header: + 0x02, + // Message Flags: + 0x03, + // Transfer ID: + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + // Acknowledgement Length: + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + } + t1message := NewDataAcknowledgementMessage(SegmentEnd|SegmentStart, 1, 255) + + t2data := []byte{ + // Message Header: + 0x03, + // Message Flags: + 0x03, + // Transfer ID: + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + // Acknowledgement Length: + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + } + t2message := DataAcknowledgementMessage{} + + tests := []struct { + valid bool + data []byte + dam DataAcknowledgementMessage + }{ + {true, t1data, t1message}, + {false, t2data, t2message}, + } + + for _, test := range tests { + var dam DataAcknowledgementMessage + + if err := dam.UnmarshalBinary(test.data); (err == nil) != test.valid { + t.Fatalf("Error state was not expected; valid := %t, got := %v", test.valid, err) + } else if !test.valid { + continue + } else if !reflect.DeepEqual(test.dam, dam) { + t.Fatalf("DataAcknowledgementMessage does not match, expected %v and got %v", test.dam, dam) + } + + if data, err := test.dam.MarshalBinary(); err != nil { + t.Fatal(err) + } else if !bytes.Equal(data, test.data) { + t.Fatalf("Data does not match, expected %x and got %x", test.data, data) + } + } +} From 1ba7f621ba9fef4935b429b9344cbf26644dbdac Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Thu, 5 Sep 2019 16:37:02 +0200 Subject: [PATCH 08/53] TCPCL: Transfer Refusal Message --- cla/tcpcl/message_xfer_refuse.go | 106 ++++++++++++++++++++++++++ cla/tcpcl/message_xfer_refuse_test.go | 56 ++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 cla/tcpcl/message_xfer_refuse.go create mode 100644 cla/tcpcl/message_xfer_refuse_test.go diff --git a/cla/tcpcl/message_xfer_refuse.go b/cla/tcpcl/message_xfer_refuse.go new file mode 100644 index 00000000..37fdbde5 --- /dev/null +++ b/cla/tcpcl/message_xfer_refuse.go @@ -0,0 +1,106 @@ +package tcpcl + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +// TransferRefusalCode is the one-octet refusal reason code for a XFER_REFUSE message. +type TransferRefusalCode uint8 + +const ( + // RefusalUnknown indicates an unknown or not specified reason. + RefusalUnknown TransferRefusalCode = 0x00 + + // RefusalExtensionFailure indicates a failure processing the Transfer Extension Items. + RefusalExtensionFailure TransferRefusalCode = 0x01 + + // RefusalCompleted indicates that the receiver already has the complete bundle. + RefusalCompleted TransferRefusalCode = 0x02 + + // RefusalNoResources indicate that the receiver's resources are exhausted. + RefusalNoResources TransferRefusalCode = 0x03 + + // RefusalRetransmit indicates a problem on the receiver's side. This requires + // the complete bundle to be retransmitted. + RefusalRetransmit TransferRefusalCode = 0x04 +) + +func (trc TransferRefusalCode) String() string { + switch trc { + case RefusalUnknown: + return "Unknown" + case RefusalExtensionFailure: + return "Extension Failure" + case RefusalCompleted: + return "Completed" + case RefusalNoResources: + return "No Resources" + case RefusalRetransmit: + return "Retransmit" + default: + return "INVALID" + } +} + +// XFER_REFUSE is the Message Header code for a Transfer Refusal Message. +const XFER_REFUSE uint8 = 0x03 + +// TransferRefusalMessage is the XFER_REFUSE message for transfer refusals. +type TransferRefusalMessage struct { + ReasonCode TransferRefusalCode + TransferId uint64 +} + +// NewTransferRefusalMessage creates a new TransferRefusalMessage with given fields. +func NewTransferRefusalMessage(reason TransferRefusalCode, tid uint64) TransferRefusalMessage { + return TransferRefusalMessage{ + ReasonCode: reason, + TransferId: tid, + } +} + +func (trm TransferRefusalMessage) String() string { + return fmt.Sprintf( + "XFER_REFUSE(Reason Code=%v, Transfer ID=%d)", + trm.ReasonCode, trm.TransferId) +} + +// MarshalBinary encodes this TransferRefusalMessage into its binary form. +func (trm TransferRefusalMessage) MarshalBinary() (data []byte, err error) { + var buf = new(bytes.Buffer) + var fields = []interface{}{XFER_REFUSE, trm.ReasonCode, trm.TransferId} + + for _, field := range fields { + if binErr := binary.Write(buf, binary.BigEndian, field); binErr != nil { + err = binErr + return + } + } + + data = buf.Bytes() + return +} + +// UnmarshalBinary decodes a TransferRefusalMessage from its binary form. +func (trm *TransferRefusalMessage) UnmarshalBinary(data []byte) error { + var buf = bytes.NewReader(data) + + var messageHeader uint8 + if err := binary.Read(buf, binary.BigEndian, &messageHeader); err != nil { + return err + } else if messageHeader != XFER_REFUSE { + return fmt.Errorf("XFER_REFUSE's Message Header is wrong: %d instead of %d", messageHeader, XFER_REFUSE) + } + + var fields = []interface{}{&trm.ReasonCode, &trm.TransferId} + + for _, field := range fields { + if err := binary.Read(buf, binary.BigEndian, field); err != nil { + return err + } + } + + return nil +} diff --git a/cla/tcpcl/message_xfer_refuse_test.go b/cla/tcpcl/message_xfer_refuse_test.go new file mode 100644 index 00000000..6482de11 --- /dev/null +++ b/cla/tcpcl/message_xfer_refuse_test.go @@ -0,0 +1,56 @@ +package tcpcl + +import ( + "bytes" + "reflect" + "testing" +) + +func TestTransferRefusalMessage(t *testing.T) { + t1data := []byte{ + // Message Header: + 0x03, + // Reason Code: + 0x00, + // Transfer ID: + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + } + t1message := NewTransferRefusalMessage(RefusalUnknown, 1) + + t2data := []byte{ + // Message Header: + 0x05, + // Reason Code: + 0x00, + // Transfer ID: + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + } + t2message := TransferRefusalMessage{} + + tests := []struct { + valid bool + data []byte + trm TransferRefusalMessage + }{ + {true, t1data, t1message}, + {false, t2data, t2message}, + } + + for _, test := range tests { + var trm TransferRefusalMessage + + if err := trm.UnmarshalBinary(test.data); (err == nil) != test.valid { + t.Fatalf("Error state was not expected; valid := %t, got := %v", test.valid, err) + } else if !test.valid { + continue + } else if !reflect.DeepEqual(test.trm, trm) { + t.Fatalf("TransferRefusalMessage does not match, expected %v and got %v", test.trm, trm) + } + + if data, err := test.trm.MarshalBinary(); err != nil { + t.Fatal(err) + } else if !bytes.Equal(data, test.data) { + t.Fatalf("Data does not match, expected %x and got %x", test.data, data) + } + } +} From 88b7549dc676e20d20bce9e312f9125a30d4b94c Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Thu, 5 Sep 2019 17:08:31 +0200 Subject: [PATCH 09/53] TCPCL: Session Termination Message --- cla/tcpcl/message_sess_term.go | 133 ++++++++++++++++++++++++++++ cla/tcpcl/message_sess_term_test.go | 46 ++++++++++ 2 files changed, 179 insertions(+) create mode 100644 cla/tcpcl/message_sess_term.go create mode 100644 cla/tcpcl/message_sess_term_test.go diff --git a/cla/tcpcl/message_sess_term.go b/cla/tcpcl/message_sess_term.go new file mode 100644 index 00000000..c8ebc896 --- /dev/null +++ b/cla/tcpcl/message_sess_term.go @@ -0,0 +1,133 @@ +package tcpcl + +import ( + "bytes" + "encoding/binary" + "fmt" + "strings" +) + +// SessionTerminationFlags are single-bit flags used in the SessionTerminationMessage. +type SessionTerminationFlags uint8 + +const ( + // TerminationReply indicates that this message is an acknowledgement of an + // earlier SESS_TERM message. + TerminationReply SessionTerminationFlags = 0x01 +) + +func (stf SessionTerminationFlags) String() string { + var flags []string + + if stf&TerminationReply != 0 { + flags = append(flags, "REPLY") + } + + return strings.Join(flags, ",") +} + +// SessionTerminationCode is the one-octet refusal reason code for a SESS_TERM message. +type SessionTerminationCode uint8 + +const ( + // TerminationUnknown indicates an unknown or not specified reason. + TerminationUnknown SessionTerminationCode = 0x00 + + // TerminationIdleTimeout indicates a session being closed due to idleness. + TerminationIdleTimeout SessionTerminationCode = 0x01 + + // TerminationVersionMismatch indicates that the node cannot conform to the + // specified TCPCL protocol version number. + TerminationVersionMismatch SessionTerminationCode = 0x02 + + // TerminationBusy indicates a too busy node. + TerminationBusy SessionTerminationCode = 0x03 + + // TerminationContactFailure indicates that the node cannot interpret or + // negotiate contact header options. + TerminationContactFailure SessionTerminationCode = 0x04 + + // TerminationResourceExhaustion indicates that the node has run into some + // resource limit. + TerminationResourceExhaustion SessionTerminationCode = 0x05 +) + +func (stc SessionTerminationCode) String() string { + switch stc { + case TerminationUnknown: + return "Unknown" + case TerminationIdleTimeout: + return "Idle timeout" + case TerminationVersionMismatch: + return "Version mismatch" + case TerminationBusy: + return "Busy" + case TerminationContactFailure: + return "Contact Failure" + case TerminationResourceExhaustion: + return "Resource Exhaustion" + default: + return "INVALID" + } +} + +// SESS_TERM is the Message Header code for a Session Termination Message. +const SESS_TERM uint8 = 0x05 + +// SessionTerminationMessage is the SESS_TERM message for session termination. +type SessionTerminationMessage struct { + Flags SessionTerminationFlags + ReasonCode SessionTerminationCode +} + +// NewSessionTerminationMessage creates a new SessionTerminationMessage with given fields. +func NewSessionTerminationMessage(flags SessionTerminationFlags, reason SessionTerminationCode) SessionTerminationMessage { + return SessionTerminationMessage{ + Flags: flags, + ReasonCode: reason, + } +} + +func (stm SessionTerminationMessage) String() string { + return fmt.Sprintf( + "SESS_TERM(Message Flags=%v, Reason Code=%v)", + stm.Flags, stm.ReasonCode) +} + +// MarshalBinary encodes this SessionTerminationMessage into its binary form. +func (stm SessionTerminationMessage) MarshalBinary() (data []byte, err error) { + var buf = new(bytes.Buffer) + var fields = []interface{}{SESS_TERM, stm.Flags, stm.ReasonCode} + + for _, field := range fields { + if binErr := binary.Write(buf, binary.BigEndian, field); binErr != nil { + err = binErr + return + } + } + + data = buf.Bytes() + return +} + +// UnmarshalBinary decodes a SessionTerminationMessage from its binary form. +func (stm *SessionTerminationMessage) UnmarshalBinary(data []byte) error { + var buf = bytes.NewReader(data) + + var messageHeader uint8 + if err := binary.Read(buf, binary.BigEndian, &messageHeader); err != nil { + return err + } else if messageHeader != SESS_TERM { + return fmt.Errorf("SESS_TERM's Message Header is wrong: %d instead of %d", messageHeader, SESS_TERM) + } + + var fields = []interface{}{&stm.Flags, &stm.ReasonCode} + + for _, field := range fields { + if err := binary.Read(buf, binary.BigEndian, field); err != nil { + return err + } + } + + return nil +} diff --git a/cla/tcpcl/message_sess_term_test.go b/cla/tcpcl/message_sess_term_test.go new file mode 100644 index 00000000..3e8a5c7b --- /dev/null +++ b/cla/tcpcl/message_sess_term_test.go @@ -0,0 +1,46 @@ +package tcpcl + +import ( + "bytes" + "reflect" + "testing" +) + +func TestSessionTerminationMessage(t *testing.T) { + t1data := []byte{0x05, 0x00, 0x00} + t1message := NewSessionTerminationMessage(0, TerminationUnknown) + + t2data := []byte{0x05, 0x01, 0x01} + t2message := NewSessionTerminationMessage(TerminationReply, TerminationIdleTimeout) + + t3data := []byte{0xFF, 0x00, 0x00} + t3message := SessionTerminationMessage{} + + tests := []struct { + valid bool + data []byte + stm SessionTerminationMessage + }{ + {true, t1data, t1message}, + {true, t2data, t2message}, + {false, t3data, t3message}, + } + + for _, test := range tests { + var stm SessionTerminationMessage + + if err := stm.UnmarshalBinary(test.data); (err == nil) != test.valid { + t.Fatalf("Error state was not expected; valid := %t, got := %v", test.valid, err) + } else if !test.valid { + continue + } else if !reflect.DeepEqual(test.stm, stm) { + t.Fatalf("SessionTerminationMessage does not match, expected %v and got %v", test.stm, stm) + } + + if data, err := test.stm.MarshalBinary(); err != nil { + t.Fatal(err) + } else if !bytes.Equal(data, test.data) { + t.Fatalf("Data does not match, expected %x and got %x", test.data, data) + } + } +} From 773742729a07856228d1e226e9d31f5c5b3c0229 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Thu, 5 Sep 2019 17:41:56 +0200 Subject: [PATCH 10/53] TCPCL: Check Reason Codes and ignore unknown Flags --- cla/tcpcl/contact_header.go | 9 +-------- cla/tcpcl/contact_header_test.go | 2 +- cla/tcpcl/message_reject.go | 15 +++++++++++---- cla/tcpcl/message_sess_term.go | 15 +++++++++++++++ cla/tcpcl/message_sess_term_test.go | 4 ++++ cla/tcpcl/message_xfer_refuse.go | 15 +++++++++++++++ cla/tcpcl/message_xfer_refuse_test.go | 11 +++++++++++ cla/tcpcl/message_xfer_segment.go | 3 --- 8 files changed, 58 insertions(+), 16 deletions(-) diff --git a/cla/tcpcl/contact_header.go b/cla/tcpcl/contact_header.go index c493fe4b..758f611f 100644 --- a/cla/tcpcl/contact_header.go +++ b/cla/tcpcl/contact_header.go @@ -12,9 +12,6 @@ type ContactFlags uint8 const ( // ContactCanTls indicates that the sending peer is capable of TLS security. ContactCanTls ContactFlags = 0x01 - - // contactFlags_INVALID is a bit field of all invalid ContactFlags. - contactFlags_INVALID ContactFlags = 0xFE ) func (cf ContactFlags) String() string { @@ -66,11 +63,7 @@ func (ch *ContactHeader) UnmarshalBinary(data []byte) error { return fmt.Errorf("ContactHeader's version is wrong: %d instead of 4", uint8(data[4])) } - if cf := ContactFlags(data[5]); cf&contactFlags_INVALID != 0 { - return fmt.Errorf("ContactHeader's flags %x contain invalid flags", cf) - } else { - ch.Flags = cf - } + ch.Flags = ContactFlags(data[5]) return nil } diff --git a/cla/tcpcl/contact_header_test.go b/cla/tcpcl/contact_header_test.go index c3650ad4..595feed2 100644 --- a/cla/tcpcl/contact_header_test.go +++ b/cla/tcpcl/contact_header_test.go @@ -36,7 +36,7 @@ func TestContactHeaderUnmarshal(t *testing.T) { {[]byte{0x64, 0x74, 0x6E, 0x21, 0x04, 0x00, 0x00}, false, ContactHeader{}}, {[]byte{0x64, 0x74, 0x6E, 0x3F, 0x04, 0x00}, false, ContactHeader{}}, {[]byte{0x64, 0x74, 0x6E, 0x21, 0x23, 0x00}, false, ContactHeader{}}, - {[]byte{0x64, 0x74, 0x6E, 0x21, 0x04, 0x23}, false, ContactHeader{}}, + {[]byte{0x64, 0x74, 0x6E, 0x21, 0x04, 0x23}, true, ContactHeader{Flags: 0x23}}, } for _, test := range tests { diff --git a/cla/tcpcl/message_reject.go b/cla/tcpcl/message_reject.go index 5d6bf556..b87e1329 100644 --- a/cla/tcpcl/message_reject.go +++ b/cla/tcpcl/message_reject.go @@ -20,11 +20,18 @@ const ( // RejectionUnexptected indicates that this TCPCL node received a message // while the session is in a state in which the message is not expected. RejectionUnexptected MessageRejectionReason = 0x03 - - // messageRejectionReason_INVALID is a bit field of all invalid MessageRejectionMessages. - messageRejectionReason_INVALID = 0xFC ) +// IsValid checks if this MessageRejectionReason represents a valid value. +func (mrr MessageRejectionReason) IsValid() bool { + switch mrr { + case RejectionTypeUnknown, RejectionUnsupported, RejectionUnexptected: + return true + default: + return false + } +} + func (mrr MessageRejectionReason) String() string { switch mrr { case RejectionTypeUnknown: @@ -99,7 +106,7 @@ func (mrm *MessageRejectionMessage) UnmarshalBinary(data []byte) error { } } - if mrm.ReasonCode&messageRejectionReason_INVALID != 0 { + if !mrm.ReasonCode.IsValid() { return fmt.Errorf("MSG_REJECT's Reason Code %x is invalid", mrm.ReasonCode) } diff --git a/cla/tcpcl/message_sess_term.go b/cla/tcpcl/message_sess_term.go index c8ebc896..49ca776e 100644 --- a/cla/tcpcl/message_sess_term.go +++ b/cla/tcpcl/message_sess_term.go @@ -52,6 +52,17 @@ const ( TerminationResourceExhaustion SessionTerminationCode = 0x05 ) +// IsValid checks if this SessionTerminationCode represents a valid value. +func (stc SessionTerminationCode) IsValid() bool { + switch stc { + case TerminationUnknown, TerminationIdleTimeout, TerminationVersionMismatch, + TerminationBusy, TerminationContactFailure, TerminationResourceExhaustion: + return true + default: + return false + } +} + func (stc SessionTerminationCode) String() string { switch stc { case TerminationUnknown: @@ -129,5 +140,9 @@ func (stm *SessionTerminationMessage) UnmarshalBinary(data []byte) error { } } + if !stm.ReasonCode.IsValid() { + return fmt.Errorf("SESS_TERM's Reason Code %x is invalid", stm.ReasonCode) + } + return nil } diff --git a/cla/tcpcl/message_sess_term_test.go b/cla/tcpcl/message_sess_term_test.go index 3e8a5c7b..cc607daa 100644 --- a/cla/tcpcl/message_sess_term_test.go +++ b/cla/tcpcl/message_sess_term_test.go @@ -16,6 +16,9 @@ func TestSessionTerminationMessage(t *testing.T) { t3data := []byte{0xFF, 0x00, 0x00} t3message := SessionTerminationMessage{} + t4data := []byte{0x05, 0x00, 0xFF} + t4message := SessionTerminationMessage{} + tests := []struct { valid bool data []byte @@ -24,6 +27,7 @@ func TestSessionTerminationMessage(t *testing.T) { {true, t1data, t1message}, {true, t2data, t2message}, {false, t3data, t3message}, + {false, t4data, t4message}, } for _, test := range tests { diff --git a/cla/tcpcl/message_xfer_refuse.go b/cla/tcpcl/message_xfer_refuse.go index 37fdbde5..b6ec2c5f 100644 --- a/cla/tcpcl/message_xfer_refuse.go +++ b/cla/tcpcl/message_xfer_refuse.go @@ -27,6 +27,17 @@ const ( RefusalRetransmit TransferRefusalCode = 0x04 ) +// IsValid checks if this TransferRefusalCode represents a valid value. +func (trc TransferRefusalCode) IsValid() bool { + switch trc { + case RefusalUnknown, RefusalExtensionFailure, RefusalCompleted, + RefusalNoResources, RefusalRetransmit: + return true + default: + return false + } +} + func (trc TransferRefusalCode) String() string { switch trc { case RefusalUnknown: @@ -102,5 +113,9 @@ func (trm *TransferRefusalMessage) UnmarshalBinary(data []byte) error { } } + if !trm.ReasonCode.IsValid() { + return fmt.Errorf("XFER_REFUSE's Reason Code %x is invalid", trm.ReasonCode) + } + return nil } diff --git a/cla/tcpcl/message_xfer_refuse_test.go b/cla/tcpcl/message_xfer_refuse_test.go index 6482de11..5c0cf96f 100644 --- a/cla/tcpcl/message_xfer_refuse_test.go +++ b/cla/tcpcl/message_xfer_refuse_test.go @@ -27,6 +27,16 @@ func TestTransferRefusalMessage(t *testing.T) { } t2message := TransferRefusalMessage{} + t3data := []byte{ + // Message Header: + 0x03, + // Reason Code: + 0xFF, + // Transfer ID: + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + } + t3message := NewTransferRefusalMessage(RefusalUnknown, 1) + tests := []struct { valid bool data []byte @@ -34,6 +44,7 @@ func TestTransferRefusalMessage(t *testing.T) { }{ {true, t1data, t1message}, {false, t2data, t2message}, + {false, t3data, t3message}, } for _, test := range tests { diff --git a/cla/tcpcl/message_xfer_segment.go b/cla/tcpcl/message_xfer_segment.go index a6f4a0b7..a97f8e0e 100644 --- a/cla/tcpcl/message_xfer_segment.go +++ b/cla/tcpcl/message_xfer_segment.go @@ -16,9 +16,6 @@ const ( // SegmentStart indicates that this segment is the first of the transfer. SegmentStart SegmentFlags = 0x02 - - // segmentFlags_INVALID is a bit field of all invalid ContactFlags. - segmentFlags_INVALID SegmentFlags = 0xFC ) func (sf SegmentFlags) String() string { From bb0ff65e8e890646ec93efcd159859f40e677f2a Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Fri, 6 Sep 2019 14:12:47 +0200 Subject: [PATCH 11/53] TCPCL: Marshal/Unmarshal with io.Writer/io.Reader --- cla/tcpcl/contact_header.go | 27 ++++++++++------ cla/tcpcl/contact_header_test.go | 11 ++++--- cla/tcpcl/message_keepalive.go | 28 ++++++++-------- cla/tcpcl/message_keepalive_test.go | 9 +++--- cla/tcpcl/message_reject.go | 27 +++++++--------- cla/tcpcl/message_reject_test.go | 7 ++-- cla/tcpcl/message_sess_init.go | 45 ++++++++++---------------- cla/tcpcl/message_sess_init_test.go | 10 +++--- cla/tcpcl/message_sess_term.go | 23 +++++-------- cla/tcpcl/message_sess_term_test.go | 7 ++-- cla/tcpcl/message_xfer_ack.go | 23 +++++-------- cla/tcpcl/message_xfer_ack_test.go | 7 ++-- cla/tcpcl/message_xfer_refuse.go | 23 +++++-------- cla/tcpcl/message_xfer_refuse_test.go | 7 ++-- cla/tcpcl/message_xfer_segment.go | 36 +++++++++------------ cla/tcpcl/message_xfer_segment_test.go | 7 ++-- 16 files changed, 135 insertions(+), 162 deletions(-) diff --git a/cla/tcpcl/contact_header.go b/cla/tcpcl/contact_header.go index 758f611f..d005d3f3 100644 --- a/cla/tcpcl/contact_header.go +++ b/cla/tcpcl/contact_header.go @@ -3,6 +3,7 @@ package tcpcl import ( "bytes" "fmt" + "io" "strings" ) @@ -42,17 +43,25 @@ func (ch ContactHeader) String() string { return fmt.Sprintf("ContactHeader(Version=4, Flags=%v)", ch.Flags) } -// MarshalBinary encodes this ContactHeader into its binary form. -func (ch ContactHeader) MarshalBinary() (data []byte, _ error) { - // magic='dtn!', version=4, flags=flags - data = []byte{0x64, 0x74, 0x6E, 0x21, 0x04, byte(ch.Flags)} - return +func (ch ContactHeader) Marshal(w io.Writer) error { + var data = []byte{0x64, 0x74, 0x6E, 0x21, 0x04, byte(ch.Flags)} + + if n, err := w.Write(data); err != nil { + return err + } else if n != len(data) { + return fmt.Errorf("Wrote %d octets instead of %d", n, len(data)) + } + + return nil } -// UnmarshalBinary decodes a ContactHeader from its binary form. -func (ch *ContactHeader) UnmarshalBinary(data []byte) error { - if len(data) != 6 { - return fmt.Errorf("ContactHeader's length is wrong: %d instead of 6", len(data)) +func (ch *ContactHeader) Unmarshal(r io.Reader) error { + var data = make([]byte, 6) + + if n, err := r.Read(data); err != nil { + return err + } else if n != len(data) { + return fmt.Errorf("Read %d octets instead of %d", n, len(data)) } if !bytes.Equal(data[:4], []byte("dtn!")) { diff --git a/cla/tcpcl/contact_header_test.go b/cla/tcpcl/contact_header_test.go index 595feed2..37ac9394 100644 --- a/cla/tcpcl/contact_header_test.go +++ b/cla/tcpcl/contact_header_test.go @@ -16,9 +16,11 @@ func TestContactHeaderMarshal(t *testing.T) { } for _, test := range tests { - if data, err := test.contactHeader.MarshalBinary(); err != nil { + var buf = new(bytes.Buffer) + + if err := test.contactHeader.Marshal(buf); err != nil { t.Fatal(err) - } else if !bytes.Equal(data, test.expectedData) { + } else if data := buf.Bytes(); !bytes.Equal(data, test.expectedData) { t.Fatalf("Data does not match, expected %x and got %x", test.expectedData, data) } } @@ -33,7 +35,6 @@ func TestContactHeaderUnmarshal(t *testing.T) { {[]byte{0x64, 0x74, 0x6E, 0x21, 0x04, 0x00}, true, ContactHeader{Flags: 0}}, {[]byte{0x64, 0x74, 0x6E, 0x21, 0x04, 0x01}, true, ContactHeader{Flags: ContactCanTls}}, {[]byte{0x64, 0x74, 0x6E, 0x21, 0x04}, false, ContactHeader{}}, - {[]byte{0x64, 0x74, 0x6E, 0x21, 0x04, 0x00, 0x00}, false, ContactHeader{}}, {[]byte{0x64, 0x74, 0x6E, 0x3F, 0x04, 0x00}, false, ContactHeader{}}, {[]byte{0x64, 0x74, 0x6E, 0x21, 0x23, 0x00}, false, ContactHeader{}}, {[]byte{0x64, 0x74, 0x6E, 0x21, 0x04, 0x23}, true, ContactHeader{Flags: 0x23}}, @@ -41,7 +42,9 @@ func TestContactHeaderUnmarshal(t *testing.T) { for _, test := range tests { var ch ContactHeader - if err := ch.UnmarshalBinary(test.data); (err == nil) != test.valid { + var buf = bytes.NewBuffer(test.data) + + if err := ch.Unmarshal(buf); (err == nil) != test.valid { t.Fatalf("Error state was not expected; valid := %t, got := %v", test.valid, err) } else if !test.valid { continue diff --git a/cla/tcpcl/message_keepalive.go b/cla/tcpcl/message_keepalive.go index 317e887f..50c91bea 100644 --- a/cla/tcpcl/message_keepalive.go +++ b/cla/tcpcl/message_keepalive.go @@ -1,6 +1,10 @@ package tcpcl -import "fmt" +import ( + "encoding/binary" + "fmt" + "io" +) // KEEPALIVE is the Message Header code for a Keepalive Message. const KEEPALIVE uint8 = 0x04 @@ -17,27 +21,21 @@ func (_ KeepaliveMessage) String() string { return "KEEPALIVE" } -// MarshalBinary encodes this KeepaliveMessage into its binary form. -func (km KeepaliveMessage) MarshalBinary() (data []byte, err error) { +func (km KeepaliveMessage) Marshal(w io.Writer) error { if uint8(km) != KEEPALIVE { - err = fmt.Errorf("KEEPALIVE's value is %d instead of %d", uint8(km), KEEPALIVE) - return + return fmt.Errorf("KEEPALIVE's value is %d instead of %d", uint8(km), KEEPALIVE) } - data = []byte{KEEPALIVE} - return + return binary.Write(w, binary.BigEndian, km) } -// UnmarshalBinary decodes a KeepaliveMessage from its binary form. -func (km *KeepaliveMessage) UnmarshalBinary(data []byte) error { - if len(data) != 1 { - return fmt.Errorf("KEEPALIVE's octet length is %d instead of 1", len(data)) +func (km *KeepaliveMessage) Unmarshal(r io.Reader) error { + if err := binary.Read(r, binary.BigEndian, km); err != nil { + return err } - if x := uint8(data[0]); x != KEEPALIVE { - return fmt.Errorf("KEEPALIVE's value is %d instead of %d", x, KEEPALIVE) - } else { - *km = KeepaliveMessage(x) + if uint8(*km) != KEEPALIVE { + return fmt.Errorf("KEEPALIVE's value is %d instead of %d", uint8(*km), KEEPALIVE) } return nil diff --git a/cla/tcpcl/message_keepalive_test.go b/cla/tcpcl/message_keepalive_test.go index 0287a79d..9ea51675 100644 --- a/cla/tcpcl/message_keepalive_test.go +++ b/cla/tcpcl/message_keepalive_test.go @@ -13,21 +13,22 @@ func TestKeepaliveMessage(t *testing.T) { {true, []byte{KEEPALIVE}}, {false, []byte{0x21}}, {false, []byte{}}, - {false, []byte{0x23, 0x42}}, } for _, test := range tests { var km KeepaliveMessage + var buf = bytes.NewBuffer(test.data) - if err := km.UnmarshalBinary(test.data); (err == nil) != test.valid { + if err := km.Unmarshal(buf); (err == nil) != test.valid { t.Fatalf("Error state was not expected; valid := %t, got := %v", test.valid, err) } else if !test.valid { continue } - if data, err := NewKeepaliveMessage().MarshalBinary(); err != nil { + var km2 = NewKeepaliveMessage() + if err := km2.Marshal(buf); err != nil { t.Fatal(err) - } else if !bytes.Equal(data, test.data) { + } else if data := buf.Bytes(); !bytes.Equal(data, test.data) { t.Fatalf("Data does not match, expected %x and got %x", test.data, data) } diff --git a/cla/tcpcl/message_reject.go b/cla/tcpcl/message_reject.go index b87e1329..cf27b358 100644 --- a/cla/tcpcl/message_reject.go +++ b/cla/tcpcl/message_reject.go @@ -1,9 +1,9 @@ package tcpcl import ( - "bytes" "encoding/binary" "fmt" + "io" ) // MessageRejectionReason is the one-octet refusal code from a MessageRejectionMessage. @@ -68,40 +68,35 @@ func (mrm MessageRejectionMessage) String() string { mrm.ReasonCode, mrm.MessageHeader) } -// MarshalBinary encodes this MessageRejectionMessage into its binary form. -func (mrm MessageRejectionMessage) MarshalBinary() (data []byte, err error) { - var buf = new(bytes.Buffer) +func (mrm MessageRejectionMessage) Marshal(w io.Writer) error { var fields = []interface{}{ MSG_REJECT, mrm.ReasonCode, mrm.MessageHeader} for _, field := range fields { - if binErr := binary.Write(buf, binary.BigEndian, field); binErr != nil { - err = binErr - return + if err := binary.Write(w, binary.BigEndian, field); err != nil { + return err } } - data = buf.Bytes() - return + return nil } -// UnmarshalBinary decodes a MessageRejectionMessage from its binary form. -func (mrm *MessageRejectionMessage) UnmarshalBinary(data []byte) error { - var buf = bytes.NewReader(data) - +func (mrm *MessageRejectionMessage) Unmarshal(r io.Reader) error { var messageHeader uint8 - if err := binary.Read(buf, binary.BigEndian, &messageHeader); err != nil { + if err := binary.Read(r, binary.BigEndian, &messageHeader); err != nil { return err } else if messageHeader != MSG_REJECT { - return fmt.Errorf("MSG_REJECT's Message Header is wrong: %d instead of %d", messageHeader, MSG_REJECT) + return fmt.Errorf( + "MSG_REJECT's Message Header is wrong: %d instead of %d", + messageHeader, MSG_REJECT) } var fields = []interface{}{&mrm.ReasonCode, &mrm.MessageHeader} for _, field := range fields { - if err := binary.Read(buf, binary.BigEndian, field); err != nil { + if err := binary.Read(r, binary.BigEndian, field); err != nil { return err } } diff --git a/cla/tcpcl/message_reject_test.go b/cla/tcpcl/message_reject_test.go index 6c01a780..02e7a950 100644 --- a/cla/tcpcl/message_reject_test.go +++ b/cla/tcpcl/message_reject_test.go @@ -20,8 +20,9 @@ func TestMessageRejectionMessage(t *testing.T) { for _, test := range tests { var mrm MessageRejectionMessage + var buf = bytes.NewBuffer(test.data) - if err := mrm.UnmarshalBinary(test.data); (err == nil) != test.valid { + if err := mrm.Unmarshal(buf); (err == nil) != test.valid { t.Fatalf("Error state was not expected; valid := %t, got := %v", test.valid, err) } else if !test.valid { continue @@ -29,9 +30,9 @@ func TestMessageRejectionMessage(t *testing.T) { t.Fatalf("MessageRejectionMessage does not match, expected %v and got %v", test.mrm, mrm) } - if data, err := test.mrm.MarshalBinary(); err != nil { + if err := test.mrm.Marshal(buf); err != nil { t.Fatal(err) - } else if !bytes.Equal(data, test.data) { + } else if data := buf.Bytes(); !bytes.Equal(data, test.data) { t.Fatalf("Data does not match, expected %x and got %x", test.data, data) } } diff --git a/cla/tcpcl/message_sess_init.go b/cla/tcpcl/message_sess_init.go index 936907f6..cde0c553 100644 --- a/cla/tcpcl/message_sess_init.go +++ b/cla/tcpcl/message_sess_init.go @@ -1,9 +1,9 @@ package tcpcl import ( - "bytes" "encoding/binary" "fmt" + "io" ) // SESS_INIT is the Message Header code for a Session Initialization Message. @@ -35,9 +35,7 @@ func (si SessionInitMessage) String() string { si.KeepaliveInterval, si.SegmentMru, si.TransferMru, si.Eid) } -// MarshalBinary encodes this SessionInitMessage into its binary form. -func (si SessionInitMessage) MarshalBinary() (data []byte, err error) { - var buf = new(bytes.Buffer) +func (si SessionInitMessage) Marshal(w io.Writer) error { var fields = []interface{}{ SESS_INIT, si.KeepaliveInterval, @@ -46,34 +44,29 @@ func (si SessionInitMessage) MarshalBinary() (data []byte, err error) { uint16(len(si.Eid))} for _, field := range fields { - if binErr := binary.Write(buf, binary.BigEndian, field); binErr != nil { - err = binErr - return + if err := binary.Write(w, binary.BigEndian, field); err != nil { + return err } } - if n, _ := buf.WriteString(si.Eid); n != len(si.Eid) { - err = fmt.Errorf("SESS_INIT EID's length is %d, but only wrote %d bytes", len(si.Eid), n) - return + if n, err := io.WriteString(w, si.Eid); err != nil { + return err + } else if n != len(si.Eid) { + return fmt.Errorf("SESS_INIT EID's length is %d, but only wrote %d bytes", len(si.Eid), n) } // TODO: Session Extension Items // Currently, only an empty Session Extension Items Length is accepted. - if binErr := binary.Write(buf, binary.BigEndian, uint32(0)); binErr != nil { - err = binErr - return + if err := binary.Write(w, binary.BigEndian, uint32(0)); err != nil { + return err } - data = buf.Bytes() - return + return nil } -// UnmarshalBinary decodes a SessionInitMessage from its binary form. -func (si *SessionInitMessage) UnmarshalBinary(data []byte) error { - var buf = bytes.NewReader(data) - +func (si *SessionInitMessage) Unmarshal(r io.Reader) error { var messageHeader uint8 - if err := binary.Read(buf, binary.BigEndian, &messageHeader); err != nil { + if err := binary.Read(r, binary.BigEndian, &messageHeader); err != nil { return err } else if messageHeader != SESS_INIT { return fmt.Errorf("SESS_INIT's Message Header is wrong: %d instead of %d", messageHeader, SESS_INIT) @@ -88,13 +81,13 @@ func (si *SessionInitMessage) UnmarshalBinary(data []byte) error { } for _, field := range fields { - if err := binary.Read(buf, binary.BigEndian, field); err != nil { + if err := binary.Read(r, binary.BigEndian, field); err != nil { return err } } var eidBuff = make([]byte, eidLength) - if n, err := buf.Read(eidBuff); err != nil { + if n, err := r.Read(eidBuff); err != nil { return err } else if uint16(n) != eidLength { return fmt.Errorf("SESS_INIT's EID length differs: expected %d and got %d", eidLength, n) @@ -104,12 +97,12 @@ func (si *SessionInitMessage) UnmarshalBinary(data []byte) error { // TODO: Session Extension Items, see above var sessionExtsLen uint32 - if err := binary.Read(buf, binary.BigEndian, &sessionExtsLen); err != nil { + if err := binary.Read(r, binary.BigEndian, &sessionExtsLen); err != nil { return err } else if sessionExtsLen > 0 { sessionExtsBuff := make([]byte, sessionExtsLen) - if n, err := buf.Read(sessionExtsBuff); err != nil { + if n, err := r.Read(sessionExtsBuff); err != nil { return err } else if uint32(n) != sessionExtsLen { return fmt.Errorf( @@ -118,9 +111,5 @@ func (si *SessionInitMessage) UnmarshalBinary(data []byte) error { } } - if n := buf.Len(); n > 0 { - return fmt.Errorf("SESS_INIT's buffer should be empty; has %d octets", n) - } - return nil } diff --git a/cla/tcpcl/message_sess_init_test.go b/cla/tcpcl/message_sess_init_test.go index 2ca60163..d4040e42 100644 --- a/cla/tcpcl/message_sess_init_test.go +++ b/cla/tcpcl/message_sess_init_test.go @@ -149,9 +149,10 @@ func TestSessionInitMessage(t *testing.T) { } for _, test := range tests { - // Decode var sim SessionInitMessage - if err := sim.UnmarshalBinary(test.data); (err == nil) != test.valid { + var buf = bytes.NewBuffer(test.data) + + if err := sim.Unmarshal(buf); (err == nil) != test.valid { t.Fatalf("Error state was not expected; valid := %t, got := %v", test.valid, err) } else if !test.valid { continue @@ -159,10 +160,9 @@ func TestSessionInitMessage(t *testing.T) { t.Fatalf("SessionInitMessage does not match, expected %v and got %v", test.sim, sim) } - // Encode - if data, err := test.sim.MarshalBinary(); err != nil { + if err := test.sim.Marshal(buf); err != nil { t.Fatal(err) - } else if test.bijective && !bytes.Equal(data, test.data) { + } else if data := buf.Bytes(); test.bijective && !bytes.Equal(data, test.data) { t.Fatalf("Data does not match, expected %x and got %x", test.data, data) } } diff --git a/cla/tcpcl/message_sess_term.go b/cla/tcpcl/message_sess_term.go index 49ca776e..27ce4e21 100644 --- a/cla/tcpcl/message_sess_term.go +++ b/cla/tcpcl/message_sess_term.go @@ -1,9 +1,9 @@ package tcpcl import ( - "bytes" "encoding/binary" "fmt" + "io" "strings" ) @@ -105,28 +105,21 @@ func (stm SessionTerminationMessage) String() string { stm.Flags, stm.ReasonCode) } -// MarshalBinary encodes this SessionTerminationMessage into its binary form. -func (stm SessionTerminationMessage) MarshalBinary() (data []byte, err error) { - var buf = new(bytes.Buffer) +func (stm SessionTerminationMessage) Marshal(w io.Writer) error { var fields = []interface{}{SESS_TERM, stm.Flags, stm.ReasonCode} for _, field := range fields { - if binErr := binary.Write(buf, binary.BigEndian, field); binErr != nil { - err = binErr - return + if err := binary.Write(w, binary.BigEndian, field); err != nil { + return err } } - data = buf.Bytes() - return + return nil } -// UnmarshalBinary decodes a SessionTerminationMessage from its binary form. -func (stm *SessionTerminationMessage) UnmarshalBinary(data []byte) error { - var buf = bytes.NewReader(data) - +func (stm *SessionTerminationMessage) Unmarshal(r io.Reader) error { var messageHeader uint8 - if err := binary.Read(buf, binary.BigEndian, &messageHeader); err != nil { + if err := binary.Read(r, binary.BigEndian, &messageHeader); err != nil { return err } else if messageHeader != SESS_TERM { return fmt.Errorf("SESS_TERM's Message Header is wrong: %d instead of %d", messageHeader, SESS_TERM) @@ -135,7 +128,7 @@ func (stm *SessionTerminationMessage) UnmarshalBinary(data []byte) error { var fields = []interface{}{&stm.Flags, &stm.ReasonCode} for _, field := range fields { - if err := binary.Read(buf, binary.BigEndian, field); err != nil { + if err := binary.Read(r, binary.BigEndian, field); err != nil { return err } } diff --git a/cla/tcpcl/message_sess_term_test.go b/cla/tcpcl/message_sess_term_test.go index cc607daa..c2de4a83 100644 --- a/cla/tcpcl/message_sess_term_test.go +++ b/cla/tcpcl/message_sess_term_test.go @@ -32,8 +32,9 @@ func TestSessionTerminationMessage(t *testing.T) { for _, test := range tests { var stm SessionTerminationMessage + var buf = bytes.NewBuffer(test.data) - if err := stm.UnmarshalBinary(test.data); (err == nil) != test.valid { + if err := stm.Unmarshal(buf); (err == nil) != test.valid { t.Fatalf("Error state was not expected; valid := %t, got := %v", test.valid, err) } else if !test.valid { continue @@ -41,9 +42,9 @@ func TestSessionTerminationMessage(t *testing.T) { t.Fatalf("SessionTerminationMessage does not match, expected %v and got %v", test.stm, stm) } - if data, err := test.stm.MarshalBinary(); err != nil { + if err := test.stm.Marshal(buf); err != nil { t.Fatal(err) - } else if !bytes.Equal(data, test.data) { + } else if data := buf.Bytes(); !bytes.Equal(data, test.data) { t.Fatalf("Data does not match, expected %x and got %x", test.data, data) } } diff --git a/cla/tcpcl/message_xfer_ack.go b/cla/tcpcl/message_xfer_ack.go index 7d208eaa..ba5ffc8f 100644 --- a/cla/tcpcl/message_xfer_ack.go +++ b/cla/tcpcl/message_xfer_ack.go @@ -1,9 +1,9 @@ package tcpcl import ( - "bytes" "encoding/binary" "fmt" + "io" ) // XFER_ACK is the Message Header code for a Data Acknowledgement Message. @@ -31,9 +31,7 @@ func (dam DataAcknowledgementMessage) String() string { dam.Flags, dam.TransferId, dam.AckLen) } -// MarshalBinary encodes this DataAcknowledgementMessage into its binary form. -func (dam DataAcknowledgementMessage) MarshalBinary() (data []byte, err error) { - var buf = new(bytes.Buffer) +func (dam DataAcknowledgementMessage) Marshal(w io.Writer) error { var fields = []interface{}{ XFER_ACK, dam.Flags, @@ -41,22 +39,17 @@ func (dam DataAcknowledgementMessage) MarshalBinary() (data []byte, err error) { dam.AckLen} for _, field := range fields { - if binErr := binary.Write(buf, binary.BigEndian, field); binErr != nil { - err = binErr - return + if err := binary.Write(w, binary.BigEndian, field); err != nil { + return err } } - data = buf.Bytes() - return + return nil } -// UnmarshalBinary decodes a DataAcknowledgementMessage from its binary form. -func (dam *DataAcknowledgementMessage) UnmarshalBinary(data []byte) error { - var buf = bytes.NewReader(data) - +func (dam *DataAcknowledgementMessage) Unmarshal(r io.Reader) error { var messageHeader uint8 - if err := binary.Read(buf, binary.BigEndian, &messageHeader); err != nil { + if err := binary.Read(r, binary.BigEndian, &messageHeader); err != nil { return err } else if messageHeader != XFER_ACK { return fmt.Errorf("XFER_ACK's Message Header is wrong: %d instead of %d", messageHeader, XFER_ACK) @@ -65,7 +58,7 @@ func (dam *DataAcknowledgementMessage) UnmarshalBinary(data []byte) error { var fields = []interface{}{&dam.Flags, &dam.TransferId, &dam.AckLen} for _, field := range fields { - if err := binary.Read(buf, binary.BigEndian, field); err != nil { + if err := binary.Read(r, binary.BigEndian, field); err != nil { return err } } diff --git a/cla/tcpcl/message_xfer_ack_test.go b/cla/tcpcl/message_xfer_ack_test.go index 77fd2e82..12786cf0 100644 --- a/cla/tcpcl/message_xfer_ack_test.go +++ b/cla/tcpcl/message_xfer_ack_test.go @@ -42,8 +42,9 @@ func TestDataAcknowledgementMessage(t *testing.T) { for _, test := range tests { var dam DataAcknowledgementMessage + var buf = bytes.NewBuffer(test.data) - if err := dam.UnmarshalBinary(test.data); (err == nil) != test.valid { + if err := dam.Unmarshal(buf); (err == nil) != test.valid { t.Fatalf("Error state was not expected; valid := %t, got := %v", test.valid, err) } else if !test.valid { continue @@ -51,9 +52,9 @@ func TestDataAcknowledgementMessage(t *testing.T) { t.Fatalf("DataAcknowledgementMessage does not match, expected %v and got %v", test.dam, dam) } - if data, err := test.dam.MarshalBinary(); err != nil { + if err := test.dam.Marshal(buf); err != nil { t.Fatal(err) - } else if !bytes.Equal(data, test.data) { + } else if data := buf.Bytes(); !bytes.Equal(data, test.data) { t.Fatalf("Data does not match, expected %x and got %x", test.data, data) } } diff --git a/cla/tcpcl/message_xfer_refuse.go b/cla/tcpcl/message_xfer_refuse.go index b6ec2c5f..a975a1c5 100644 --- a/cla/tcpcl/message_xfer_refuse.go +++ b/cla/tcpcl/message_xfer_refuse.go @@ -1,9 +1,9 @@ package tcpcl import ( - "bytes" "encoding/binary" "fmt" + "io" ) // TransferRefusalCode is the one-octet refusal reason code for a XFER_REFUSE message. @@ -78,28 +78,21 @@ func (trm TransferRefusalMessage) String() string { trm.ReasonCode, trm.TransferId) } -// MarshalBinary encodes this TransferRefusalMessage into its binary form. -func (trm TransferRefusalMessage) MarshalBinary() (data []byte, err error) { - var buf = new(bytes.Buffer) +func (trm TransferRefusalMessage) Marshal(w io.Writer) error { var fields = []interface{}{XFER_REFUSE, trm.ReasonCode, trm.TransferId} for _, field := range fields { - if binErr := binary.Write(buf, binary.BigEndian, field); binErr != nil { - err = binErr - return + if err := binary.Write(w, binary.BigEndian, field); err != nil { + return err } } - data = buf.Bytes() - return + return nil } -// UnmarshalBinary decodes a TransferRefusalMessage from its binary form. -func (trm *TransferRefusalMessage) UnmarshalBinary(data []byte) error { - var buf = bytes.NewReader(data) - +func (trm *TransferRefusalMessage) Unmarshal(r io.Reader) error { var messageHeader uint8 - if err := binary.Read(buf, binary.BigEndian, &messageHeader); err != nil { + if err := binary.Read(r, binary.BigEndian, &messageHeader); err != nil { return err } else if messageHeader != XFER_REFUSE { return fmt.Errorf("XFER_REFUSE's Message Header is wrong: %d instead of %d", messageHeader, XFER_REFUSE) @@ -108,7 +101,7 @@ func (trm *TransferRefusalMessage) UnmarshalBinary(data []byte) error { var fields = []interface{}{&trm.ReasonCode, &trm.TransferId} for _, field := range fields { - if err := binary.Read(buf, binary.BigEndian, field); err != nil { + if err := binary.Read(r, binary.BigEndian, field); err != nil { return err } } diff --git a/cla/tcpcl/message_xfer_refuse_test.go b/cla/tcpcl/message_xfer_refuse_test.go index 5c0cf96f..a185003e 100644 --- a/cla/tcpcl/message_xfer_refuse_test.go +++ b/cla/tcpcl/message_xfer_refuse_test.go @@ -49,8 +49,9 @@ func TestTransferRefusalMessage(t *testing.T) { for _, test := range tests { var trm TransferRefusalMessage + var buf = bytes.NewBuffer(test.data) - if err := trm.UnmarshalBinary(test.data); (err == nil) != test.valid { + if err := trm.Unmarshal(buf); (err == nil) != test.valid { t.Fatalf("Error state was not expected; valid := %t, got := %v", test.valid, err) } else if !test.valid { continue @@ -58,9 +59,9 @@ func TestTransferRefusalMessage(t *testing.T) { t.Fatalf("TransferRefusalMessage does not match, expected %v and got %v", test.trm, trm) } - if data, err := test.trm.MarshalBinary(); err != nil { + if err := test.trm.Marshal(buf); err != nil { t.Fatal(err) - } else if !bytes.Equal(data, test.data) { + } else if data := buf.Bytes(); !bytes.Equal(data, test.data) { t.Fatalf("Data does not match, expected %x and got %x", test.data, data) } } diff --git a/cla/tcpcl/message_xfer_segment.go b/cla/tcpcl/message_xfer_segment.go index a97f8e0e..9670c033 100644 --- a/cla/tcpcl/message_xfer_segment.go +++ b/cla/tcpcl/message_xfer_segment.go @@ -1,9 +1,9 @@ package tcpcl import ( - "bytes" "encoding/binary" "fmt" + "io" "strings" ) @@ -58,9 +58,7 @@ func (dtm DataTransmissionMessage) String() string { dtm.Flags, dtm.TransferId, dtm.Data) } -// MarshalBinary encodes this DataTransmissionMessage into its binary form. -func (dtm DataTransmissionMessage) MarshalBinary() (data []byte, err error) { - var buf = new(bytes.Buffer) +func (dtm DataTransmissionMessage) Marshal(w io.Writer) error { var fields = []interface{}{ XFER_SEGMENT, dtm.Flags, @@ -69,27 +67,23 @@ func (dtm DataTransmissionMessage) MarshalBinary() (data []byte, err error) { uint64(len(dtm.Data))} for _, field := range fields { - if binErr := binary.Write(buf, binary.BigEndian, field); binErr != nil { - err = binErr - return + if err := binary.Write(w, binary.BigEndian, field); err != nil { + return err } } - if n, _ := buf.Write(dtm.Data); n != len(dtm.Data) { - err = fmt.Errorf("XFER_SEGMENT Data length is %d, but only wrote %d bytes", len(dtm.Data), n) - return + if n, err := w.Write(dtm.Data); err != nil { + return err + } else if n != len(dtm.Data) { + return fmt.Errorf("XFER_SEGMENT Data length is %d, but only wrote %d bytes", len(dtm.Data), n) } - data = buf.Bytes() - return + return nil } -// UnmarshalBinary decodes a DataTransmissionMessage from its binary form. -func (dtm *DataTransmissionMessage) UnmarshalBinary(data []byte) error { - var buf = bytes.NewReader(data) - +func (dtm *DataTransmissionMessage) Unmarshal(r io.Reader) error { var messageHeader uint8 - if err := binary.Read(buf, binary.BigEndian, &messageHeader); err != nil { + if err := binary.Read(r, binary.BigEndian, &messageHeader); err != nil { return err } else if messageHeader != XFER_SEGMENT { return fmt.Errorf("XFER_SEGMENT's Message Header is wrong: %d instead of %d", messageHeader, XFER_SEGMENT) @@ -99,7 +93,7 @@ func (dtm *DataTransmissionMessage) UnmarshalBinary(data []byte) error { var fields = []interface{}{&dtm.Flags, &dtm.TransferId, &transferExtLen} for _, field := range fields { - if err := binary.Read(buf, binary.BigEndian, field); err != nil { + if err := binary.Read(r, binary.BigEndian, field); err != nil { return err } } @@ -108,7 +102,7 @@ func (dtm *DataTransmissionMessage) UnmarshalBinary(data []byte) error { if transferExtLen > 0 { transferExtBuff := make([]byte, transferExtLen) - if n, err := buf.Read(transferExtBuff); err != nil { + if n, err := r.Read(transferExtBuff); err != nil { return err } else if uint32(n) != transferExtLen { return fmt.Errorf( @@ -118,12 +112,12 @@ func (dtm *DataTransmissionMessage) UnmarshalBinary(data []byte) error { } var dataLen uint64 - if err := binary.Read(buf, binary.BigEndian, &dataLen); err != nil { + if err := binary.Read(r, binary.BigEndian, &dataLen); err != nil { return err } else if dataLen > 0 { dataBuff := make([]byte, dataLen) - if n, err := buf.Read(dataBuff); err != nil { + if n, err := r.Read(dataBuff); err != nil { return err } else if uint64(n) != dataLen { return fmt.Errorf( diff --git a/cla/tcpcl/message_xfer_segment_test.go b/cla/tcpcl/message_xfer_segment_test.go index 33454b01..e151a08b 100644 --- a/cla/tcpcl/message_xfer_segment_test.go +++ b/cla/tcpcl/message_xfer_segment_test.go @@ -101,8 +101,9 @@ func TestDataTransmissionMessage(t *testing.T) { for _, test := range tests { var dtm DataTransmissionMessage + var buf = bytes.NewBuffer(test.data) - if err := dtm.UnmarshalBinary(test.data); (err == nil) != test.valid { + if err := dtm.Unmarshal(buf); (err == nil) != test.valid { t.Fatalf("Error state was not expected; valid := %t, got := %v", test.valid, err) } else if !test.valid { continue @@ -110,9 +111,9 @@ func TestDataTransmissionMessage(t *testing.T) { t.Fatalf("DataTransmissionMessage does not match, expected %v and got %v", test.dtm, dtm) } - if data, err := test.dtm.MarshalBinary(); err != nil { + if err := test.dtm.Marshal(buf); err != nil { t.Fatal(err) - } else if test.bijective && !bytes.Equal(data, test.data) { + } else if data := buf.Bytes(); test.bijective && !bytes.Equal(data, test.data) { t.Fatalf("Data does not match, expected %x and got %x", test.data, data) } } From 7bf47b867ba48bf774eb55a81d5b1e5662811bc4 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Fri, 6 Sep 2019 15:42:24 +0200 Subject: [PATCH 12/53] TCPCL: First server/client, working Contact Header --- cla/tcpcl/client.go | 83 +++++++++++++++++++++++++++++++++++++ cla/tcpcl/server.go | 99 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 cla/tcpcl/client.go create mode 100644 cla/tcpcl/server.go diff --git a/cla/tcpcl/client.go b/cla/tcpcl/client.go new file mode 100644 index 00000000..1c96ab7a --- /dev/null +++ b/cla/tcpcl/client.go @@ -0,0 +1,83 @@ +package tcpcl + +import ( + "bufio" + "net" + "time" + + log "github.com/sirupsen/logrus" +) + +type ClientState int + +const ( + Contact ClientState = iota + Initialization ClientState = iota + Established ClientState = iota + Termination ClientState = iota +) + +type TCPCLClient struct { + address string + conn net.Conn + active bool + state ClientState +} + +func NewTCPCLClient(conn net.Conn) *TCPCLClient { + return &TCPCLClient{ + conn: conn, + active: false, + } +} + +func Dial(address string) *TCPCLClient { + return &TCPCLClient{ + address: address, + active: true, + } +} + +func (client *TCPCLClient) Start() (err error, retry bool) { + if client.conn == nil { + if conn, connErr := net.DialTimeout("tcp", client.address, time.Second); connErr != nil { + err = connErr + return + } else { + client.conn = conn + } + } + + log.Info("Starting client") + + go client.handler() + return +} + +func (client *TCPCLClient) handler() { + rw := bufio.NewReadWriter(bufio.NewReader(client.conn), bufio.NewWriter(client.conn)) + + for { + switch client.state { + case Contact: + if client.active { + ch := NewContactHeader(0) + if err := ch.Marshal(rw); err != nil { + log.WithError(err).Error("Marshaling errored") + } else if err := rw.Flush(); err != nil { + log.WithError(err).Error("Flushing errored") + } else { + client.state += 1 + } + } else { + var ch ContactHeader + if err := ch.Unmarshal(rw); err != nil { + log.WithError(err).Error("Unmarshaling errored") + } else { + log.WithField("msg", ch).Info("Contact Header received") + client.state += 1 + } + } + } + } +} diff --git a/cla/tcpcl/server.go b/cla/tcpcl/server.go new file mode 100644 index 00000000..7d9961f6 --- /dev/null +++ b/cla/tcpcl/server.go @@ -0,0 +1,99 @@ +package tcpcl + +import ( + "fmt" + "net" + "time" + + log "github.com/sirupsen/logrus" + + "github.com/dtn7/dtn7-go/bundle" + "github.com/dtn7/dtn7-go/cla" +) + +type TCPCLServer struct { + listenAddress string + reportChan chan cla.ConvergenceStatus + endpointID bundle.EndpointID + permanent bool + + stopSyn chan struct{} + stopAck chan struct{} +} + +func NewTCPCLServer(listenAddress string, endpointID bundle.EndpointID, permanent bool) *TCPCLServer { + return &TCPCLServer{ + listenAddress: listenAddress, + reportChan: make(chan cla.ConvergenceStatus), + endpointID: endpointID, + permanent: permanent, + stopSyn: make(chan struct{}), + stopAck: make(chan struct{}), + } +} + +func (serv *TCPCLServer) Start() (error, bool) { + tcpAddr, err := net.ResolveTCPAddr("tcp", serv.listenAddress) + if err != nil { + return err, false + } + + ln, err := net.ListenTCP("tcp", tcpAddr) + if err != nil { + return err, true + } + + go func(ln *net.TCPListener) { + for { + select { + case <-serv.stopSyn: + ln.Close() + close(serv.reportChan) + close(serv.stopAck) + + return + + default: + if err := ln.SetDeadline(time.Now().Add(50 * time.Millisecond)); err != nil { + log.WithFields(log.Fields{ + "cla": serv, + "error": err, + }).Warn("TCPCLServer failed to set deadline on TCP socket") + + serv.Close() + } else if conn, err := ln.Accept(); err == nil { + // TODO + client := NewTCPCLClient(conn) + _, _ = client.Start() + } + } + } + }(ln) + + return nil, true +} + +func (serv *TCPCLServer) Channel() chan cla.ConvergenceStatus { + return serv.reportChan +} + +func (serv *TCPCLServer) Close() { + close(serv.stopSyn) + <-serv.stopAck +} + +func (serv TCPCLServer) GetEndpointID() bundle.EndpointID { + return serv.endpointID +} + +func (serv TCPCLServer) Address() string { + return fmt.Sprintf("tcpcl://%s", serv.listenAddress) +} + +func (serv TCPCLServer) IsPermanent() bool { + return serv.permanent +} + +func (serv TCPCLServer) String() string { + return serv.Address() +} From 7321847e27a87d03a5979b5c8437d09f388df169 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 9 Sep 2019 11:27:34 +0200 Subject: [PATCH 13/53] TCPCL: Refactored Contact Header Handler --- cla/tcpcl/client.go | 106 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 86 insertions(+), 20 deletions(-) diff --git a/cla/tcpcl/client.go b/cla/tcpcl/client.go index 1c96ab7a..205f6ef9 100644 --- a/cla/tcpcl/client.go +++ b/cla/tcpcl/client.go @@ -2,7 +2,9 @@ package tcpcl import ( "bufio" + "fmt" "net" + "strings" "time" log "github.com/sirupsen/logrus" @@ -20,8 +22,18 @@ const ( type TCPCLClient struct { address string conn net.Conn - active bool - state ClientState + rw *bufio.ReadWriter + + active bool + state ClientState + + // Contact state fields: + contactSent bool + contactRecv bool + chSent ContactHeader + chRecv ContactHeader + + // Termination state fields: } func NewTCPCLClient(conn net.Conn) *TCPCLClient { @@ -38,6 +50,17 @@ func Dial(address string) *TCPCLClient { } } +func (client *TCPCLClient) String() string { + var b strings.Builder + + fmt.Fprintf(&b, "TCPCL(") + fmt.Fprintf(&b, "peer=%v,", client.conn.RemoteAddr()) + fmt.Fprintf(&b, "active peer=%t", client.active) + fmt.Fprintf(&b, ")") + + return b.String() +} + func (client *TCPCLClient) Start() (err error, retry bool) { if client.conn == nil { if conn, connErr := net.DialTimeout("tcp", client.address, time.Second); connErr != nil { @@ -47,6 +70,7 @@ func (client *TCPCLClient) Start() (err error, retry bool) { client.conn = conn } } + client.rw = bufio.NewReadWriter(bufio.NewReader(client.conn), bufio.NewWriter(client.conn)) log.Info("Starting client") @@ -55,29 +79,71 @@ func (client *TCPCLClient) Start() (err error, retry bool) { } func (client *TCPCLClient) handler() { - rw := bufio.NewReadWriter(bufio.NewReader(client.conn), bufio.NewWriter(client.conn)) + var logger = log.WithField("session", client) for { switch client.state { case Contact: - if client.active { - ch := NewContactHeader(0) - if err := ch.Marshal(rw); err != nil { - log.WithError(err).Error("Marshaling errored") - } else if err := rw.Flush(); err != nil { - log.WithError(err).Error("Flushing errored") - } else { - client.state += 1 - } - } else { - var ch ContactHeader - if err := ch.Unmarshal(rw); err != nil { - log.WithError(err).Error("Unmarshaling errored") - } else { - log.WithField("msg", ch).Info("Contact Header received") - client.state += 1 - } + if err := client.handleContact(); err != nil { + logger.WithField("state", "contact").WithError(err).Warn( + "Error occured during contact state") + + client.terminate(TerminationContactFailure) + return } } } } + +// handleContact manges the contact stage with the Contact Header exchange. +func (client *TCPCLClient) handleContact() error { + var logger = log.WithFields(log.Fields{ + "session": client, + "active peer": client.active, + "state": "contact", + }) + + switch { + case client.active && !client.contactSent, !client.active && !client.contactSent && client.contactRecv: + client.chSent = NewContactHeader(0) + if err := client.chSent.Marshal(client.rw); err != nil { + return err + } else if err := client.rw.Flush(); err != nil { + return err + } else { + client.contactSent = true + logger.WithField("msg", client.chSent).Debug("Sent Contact Header") + } + + case !client.active && !client.contactRecv, client.active && client.contactSent && !client.contactRecv: + if err := client.chRecv.Unmarshal(client.rw); err != nil { + return err + } else { + client.contactRecv = true + logger.WithField("msg", client.chRecv).Debug("Received Contact Header") + } + + case client.contactSent && client.contactRecv: + // TODO: check contact header flags + logger.Debug("Exchanged Contact Headers") + client.state += 1 + } + + return nil +} + +// terminate sends a SESS_TERM message to its peer and closes the session afterwards. +func (client *TCPCLClient) terminate(code SessionTerminationCode) { + var logger = log.WithField("session", client) + + var sessTerm = NewSessionTerminationMessage(0, code) + if err := sessTerm.Marshal(client.rw); err != nil { + logger.WithError(err).Warn("Failed to send session termination message") + } else if err := client.rw.Flush(); err != nil { + logger.WithError(err).Warn("Failed to flush buffer") + } else if err := client.conn.Close(); err != nil { + logger.WithError(err).Warn("Failed to close TCP connection") + } else { + logger.Info("Terminated session") + } +} From c836a455fc7f65651b28ee0f3cab16a34e1ff8c2 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 9 Sep 2019 12:01:06 +0200 Subject: [PATCH 14/53] TCPCL: SESS_INIT support for the client code --- cla/tcpcl/client.go | 108 +++++++++++++++++++++++++++++++++++++------- cla/tcpcl/server.go | 2 +- 2 files changed, 92 insertions(+), 18 deletions(-) diff --git a/cla/tcpcl/client.go b/cla/tcpcl/client.go index 205f6ef9..d1228ec2 100644 --- a/cla/tcpcl/client.go +++ b/cla/tcpcl/client.go @@ -19,10 +19,27 @@ const ( Termination ClientState = iota ) +func (cs ClientState) String() string { + switch cs { + case Contact: + return "contact" + case Initialization: + return "initialization" + case Established: + return "established" + case Termination: + return "termination" + default: + return "INVALID" + } +} + type TCPCLClient struct { - address string - conn net.Conn - rw *bufio.ReadWriter + address string + endpointID string + + conn net.Conn + rw *bufio.ReadWriter active bool state ClientState @@ -33,20 +50,26 @@ type TCPCLClient struct { chSent ContactHeader chRecv ContactHeader - // Termination state fields: + // Initialization state fields: + initSent bool + initRecv bool + sessInitSent SessionInitMessage + sessInitRecv SessionInitMessage } -func NewTCPCLClient(conn net.Conn) *TCPCLClient { +func NewTCPCLClient(conn net.Conn, endpointID string) *TCPCLClient { return &TCPCLClient{ - conn: conn, - active: false, + conn: conn, + active: false, + endpointID: endpointID, } } -func Dial(address string) *TCPCLClient { +func Dial(address string, endpointID string) *TCPCLClient { return &TCPCLClient{ - address: address, - active: true, + address: address, + active: true, + endpointID: endpointID, } } @@ -54,7 +77,7 @@ func (client *TCPCLClient) String() string { var b strings.Builder fmt.Fprintf(&b, "TCPCL(") - fmt.Fprintf(&b, "peer=%v,", client.conn.RemoteAddr()) + fmt.Fprintf(&b, "peer=%v, ", client.conn.RemoteAddr()) fmt.Fprintf(&b, "active peer=%t", client.active) fmt.Fprintf(&b, ")") @@ -79,18 +102,28 @@ func (client *TCPCLClient) Start() (err error, retry bool) { } func (client *TCPCLClient) handler() { - var logger = log.WithField("session", client) + var logger = log.WithFields(log.Fields{ + "session": client, + "state": client.state, + }) for { switch client.state { case Contact: if err := client.handleContact(); err != nil { - logger.WithField("state", "contact").WithError(err).Warn( - "Error occured during contact state") + logger.WithError(err).Warn("Error occured during contact header exchange") client.terminate(TerminationContactFailure) return } + + case Initialization: + if err := client.handleSessInit(); err != nil { + logger.WithError(err).Warn("Error occured during session initialization") + + // TODO + return + } } } } @@ -98,9 +131,8 @@ func (client *TCPCLClient) handler() { // handleContact manges the contact stage with the Contact Header exchange. func (client *TCPCLClient) handleContact() error { var logger = log.WithFields(log.Fields{ - "session": client, - "active peer": client.active, - "state": "contact", + "session": client, + "state": "contact", }) switch { @@ -132,6 +164,48 @@ func (client *TCPCLClient) handleContact() error { return nil } +func (client *TCPCLClient) handleSessInit() error { + var logger = log.WithFields(log.Fields{ + "session": client, + "state": "initialization", + }) + + // XXX + const ( + keepalive = 10 + segmentMru = 0xFFFFFFFF + transferMru = 0xFFFFFFFF + ) + + switch { + case client.active && !client.initSent, !client.active && !client.initSent && client.initRecv: + client.sessInitSent = NewSessionInitMessage(keepalive, segmentMru, transferMru, client.endpointID) + if err := client.sessInitSent.Marshal(client.rw); err != nil { + return err + } else if err := client.rw.Flush(); err != nil { + return err + } else { + client.initSent = true + logger.WithField("msg", client.sessInitSent).Debug("Sent SESS_INIT message") + } + + case !client.active && !client.initRecv, client.active && client.initSent && !client.initRecv: + if err := client.sessInitRecv.Unmarshal(client.rw); err != nil { + return err + } else { + client.initRecv = true + logger.WithField("msg", client.sessInitRecv).Debug("Received SESS_INIT message") + } + + case client.initSent && client.initRecv: + // TODO: everything + logger.Debug("Exchanged SESS_INIT messages") + client.state += 1 + } + + return nil +} + // terminate sends a SESS_TERM message to its peer and closes the session afterwards. func (client *TCPCLClient) terminate(code SessionTerminationCode) { var logger = log.WithField("session", client) diff --git a/cla/tcpcl/server.go b/cla/tcpcl/server.go index 7d9961f6..662f3b59 100644 --- a/cla/tcpcl/server.go +++ b/cla/tcpcl/server.go @@ -63,7 +63,7 @@ func (serv *TCPCLServer) Start() (error, bool) { serv.Close() } else if conn, err := ln.Accept(); err == nil { // TODO - client := NewTCPCLClient(conn) + client := NewTCPCLClient(conn, serv.endpointID.String()) _, _ = client.Start() } } From d0307f0fba0b5e6adc4348e005ede9472cd90e0a Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 9 Sep 2019 12:27:25 +0200 Subject: [PATCH 15/53] TCPCL: Negotiate Session Parameters --- cla/tcpcl/client.go | 50 ++++++++++++++++++++++++++++++++++++--------- cla/tcpcl/server.go | 2 +- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/cla/tcpcl/client.go b/cla/tcpcl/client.go index d1228ec2..fc7a1f1e 100644 --- a/cla/tcpcl/client.go +++ b/cla/tcpcl/client.go @@ -8,6 +8,8 @@ import ( "time" log "github.com/sirupsen/logrus" + + "github.com/dtn7/dtn7-go/bundle" ) type ClientState int @@ -35,8 +37,9 @@ func (cs ClientState) String() string { } type TCPCLClient struct { - address string - endpointID string + address string + endpointID bundle.EndpointID + peerEndpointID bundle.EndpointID conn net.Conn rw *bufio.ReadWriter @@ -55,9 +58,13 @@ type TCPCLClient struct { initRecv bool sessInitSent SessionInitMessage sessInitRecv SessionInitMessage + + keepalive uint16 + segmentMru uint64 + transferMru uint64 } -func NewTCPCLClient(conn net.Conn, endpointID string) *TCPCLClient { +func NewTCPCLClient(conn net.Conn, endpointID bundle.EndpointID) *TCPCLClient { return &TCPCLClient{ conn: conn, active: false, @@ -65,7 +72,7 @@ func NewTCPCLClient(conn net.Conn, endpointID string) *TCPCLClient { } } -func Dial(address string, endpointID string) *TCPCLClient { +func Dial(address string, endpointID bundle.EndpointID) *TCPCLClient { return &TCPCLClient{ address: address, active: true, @@ -121,7 +128,7 @@ func (client *TCPCLClient) handler() { if err := client.handleSessInit(); err != nil { logger.WithError(err).Warn("Error occured during session initialization") - // TODO + client.terminate(TerminationUnknown) return } } @@ -173,13 +180,13 @@ func (client *TCPCLClient) handleSessInit() error { // XXX const ( keepalive = 10 - segmentMru = 0xFFFFFFFF - transferMru = 0xFFFFFFFF + segmentMru = 0xFFFFFFFFFFFFFFFF + transferMru = 0xFFFFFFFFFFFFFFFF ) switch { case client.active && !client.initSent, !client.active && !client.initSent && client.initRecv: - client.sessInitSent = NewSessionInitMessage(keepalive, segmentMru, transferMru, client.endpointID) + client.sessInitSent = NewSessionInitMessage(keepalive, segmentMru, transferMru, client.endpointID.String()) if err := client.sessInitSent.Marshal(client.rw); err != nil { return err } else if err := client.rw.Flush(); err != nil { @@ -198,8 +205,31 @@ func (client *TCPCLClient) handleSessInit() error { } case client.initSent && client.initRecv: - // TODO: everything - logger.Debug("Exchanged SESS_INIT messages") + if eid, err := bundle.NewEndpointID(client.sessInitRecv.Eid); err != nil { + return err + } else { + client.peerEndpointID = eid + } + + client.keepalive = client.sessInitSent.KeepaliveInterval + if client.sessInitRecv.KeepaliveInterval < client.keepalive { + client.keepalive = client.sessInitRecv.KeepaliveInterval + } + client.segmentMru = client.sessInitSent.SegmentMru + if client.sessInitRecv.SegmentMru < client.segmentMru { + client.segmentMru = client.sessInitRecv.SegmentMru + } + client.transferMru = client.sessInitSent.TransferMru + if client.sessInitRecv.TransferMru < client.transferMru { + client.transferMru = client.sessInitRecv.TransferMru + } + + logger.WithFields(log.Fields{ + "endpoint ID": client.peerEndpointID, + "keepalive": client.keepalive, + "segment MRU": client.segmentMru, + "transfer MRU": client.transferMru, + }).Debug("Exchanged SESS_INIT messages") client.state += 1 } diff --git a/cla/tcpcl/server.go b/cla/tcpcl/server.go index 662f3b59..820703d1 100644 --- a/cla/tcpcl/server.go +++ b/cla/tcpcl/server.go @@ -63,7 +63,7 @@ func (serv *TCPCLServer) Start() (error, bool) { serv.Close() } else if conn, err := ln.Accept(); err == nil { // TODO - client := NewTCPCLClient(conn, serv.endpointID.String()) + client := NewTCPCLClient(conn, serv.endpointID) _, _ = client.Start() } } From c24c7b08e62f7fcd6fd1f09635a96069b5d56d11 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 9 Sep 2019 13:28:58 +0200 Subject: [PATCH 16/53] TCPCL: Send and receive KEEPALIVE messages --- cla/tcpcl/client.go | 85 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 6 deletions(-) diff --git a/cla/tcpcl/client.go b/cla/tcpcl/client.go index fc7a1f1e..8a62063f 100644 --- a/cla/tcpcl/client.go +++ b/cla/tcpcl/client.go @@ -62,21 +62,30 @@ type TCPCLClient struct { keepalive uint16 segmentMru uint64 transferMru uint64 + + // Established state fields: + keepaliveStarted bool + keepaliveStopSyn chan struct{} + keepaliveStopAck chan struct{} } func NewTCPCLClient(conn net.Conn, endpointID bundle.EndpointID) *TCPCLClient { return &TCPCLClient{ - conn: conn, - active: false, - endpointID: endpointID, + conn: conn, + active: false, + endpointID: endpointID, + keepaliveStopSyn: make(chan struct{}), + keepaliveStopAck: make(chan struct{}), } } func Dial(address string, endpointID bundle.EndpointID) *TCPCLClient { return &TCPCLClient{ - address: address, - active: true, - endpointID: endpointID, + address: address, + active: true, + endpointID: endpointID, + keepaliveStopSyn: make(chan struct{}), + keepaliveStopAck: make(chan struct{}), } } @@ -131,6 +140,14 @@ func (client *TCPCLClient) handler() { client.terminate(TerminationUnknown) return } + + case Established: + if err := client.handleEstablished(); err != nil { + logger.WithError(err).Warn("Error occured during established session") + + // TODO + return + } } } } @@ -236,6 +253,62 @@ func (client *TCPCLClient) handleSessInit() error { return nil } +func (client *TCPCLClient) keepaliveHandler() { + var logger = log.WithField("session", client) + + var keepaliveTicker = time.NewTicker(time.Duration(client.keepalive) * time.Second) + defer keepaliveTicker.Stop() + + for { + select { + case <-keepaliveTicker.C: + var keepaliveMsg = NewKeepaliveMessage() + if err := keepaliveMsg.Marshal(client.rw); err != nil { + logger.WithError(err).Warn("Sending KEEPALIVE errored") + } else if err := client.rw.Flush(); err != nil { + logger.WithError(err).Warn("Flushing KEEPALIVE errored") + } else { + log.WithField("msg", keepaliveMsg).Debug("Sent KEEPALIVE message") + } + + case <-client.keepaliveStopSyn: + close(client.keepaliveStopAck) + return + } + } +} + +func (client *TCPCLClient) handleEstablished() error { + var logger = log.WithField("session", client) + + if !client.keepaliveStarted { + go client.keepaliveHandler() + client.keepaliveStarted = true + } + + nextMsg, nextMsgErr := client.rw.ReadByte() + if nextMsgErr != nil { + return nextMsgErr + } else if err := client.rw.UnreadByte(); err != nil { + return err + } + + switch nextMsg { + case KEEPALIVE: + var keepaliveMsg KeepaliveMessage + if err := keepaliveMsg.Unmarshal(client.rw); err != nil { + return err + } else { + logger.WithField("msg", keepaliveMsg).Debug("Received KEEPALIVE message") + } + + default: + logger.WithField("magic", nextMsg).Debug("Received unsupported magic") + } + + return nil +} + // terminate sends a SESS_TERM message to its peer and closes the session afterwards. func (client *TCPCLClient) terminate(code SessionTerminationCode) { var logger = log.WithField("session", client) From c593f6e548a5ad61b36668fb7e187a4b066127df Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Sat, 14 Sep 2019 10:48:57 +0200 Subject: [PATCH 17/53] TCPCL: Refactored Client state into own file --- cla/tcpcl/client.go | 28 ++-------------------- cla/tcpcl/client_state.go | 50 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 26 deletions(-) create mode 100644 cla/tcpcl/client_state.go diff --git a/cla/tcpcl/client.go b/cla/tcpcl/client.go index 8a62063f..ec28a5d1 100644 --- a/cla/tcpcl/client.go +++ b/cla/tcpcl/client.go @@ -12,30 +12,6 @@ import ( "github.com/dtn7/dtn7-go/bundle" ) -type ClientState int - -const ( - Contact ClientState = iota - Initialization ClientState = iota - Established ClientState = iota - Termination ClientState = iota -) - -func (cs ClientState) String() string { - switch cs { - case Contact: - return "contact" - case Initialization: - return "initialization" - case Established: - return "established" - case Termination: - return "termination" - default: - return "INVALID" - } -} - type TCPCLClient struct { address string endpointID bundle.EndpointID @@ -182,7 +158,7 @@ func (client *TCPCLClient) handleContact() error { case client.contactSent && client.contactRecv: // TODO: check contact header flags logger.Debug("Exchanged Contact Headers") - client.state += 1 + client.state.Next() } return nil @@ -247,7 +223,7 @@ func (client *TCPCLClient) handleSessInit() error { "segment MRU": client.segmentMru, "transfer MRU": client.transferMru, }).Debug("Exchanged SESS_INIT messages") - client.state += 1 + client.state.Next() } return nil diff --git a/cla/tcpcl/client_state.go b/cla/tcpcl/client_state.go new file mode 100644 index 00000000..6a761cb9 --- /dev/null +++ b/cla/tcpcl/client_state.go @@ -0,0 +1,50 @@ +package tcpcl + +import "errors" + +// ClientState describes the state of a TCPCL Client. Each Client can always +// upgrade its state to a later one, but cannot go back to a previous state. +// A transition can be made into the following or the termination state. +type ClientState int + +const ( + // Contact is the initial Contact Header exchange state, entered directly + // after a TCP connection was established. + Contact ClientState = iota + + // Initialization is the SESS_INIT state. + Initialization ClientState = iota + + // Established describes an established connection, allowing Bundles to be exchanged. + Established ClientState = iota + + // Termination is the final state, entered when at least one client wants to + // terminate/close the session. + Termination ClientState = iota +) + +func (cs ClientState) String() string { + switch cs { + case Contact: + return "contact" + case Initialization: + return "initialization" + case Established: + return "established" + case Termination: + return "termination" + default: + return "INVALID" + } +} + +// Next enters the following ClientState or errors if there is no next state. +func (cs *ClientState) Next() (err error) { + if *cs == Termination { + err = errors.New("There is no state after termination") + } else { + *cs += 1 + } + + return +} From dec02ba75ac5dd1946f972726f2c207757986193 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Sat, 14 Sep 2019 11:09:57 +0200 Subject: [PATCH 18/53] TCPCL: Move Client State code to own files --- cla/tcpcl/client.go | 177 +------------------------------- cla/tcpcl/client_contact.go | 43 ++++++++ cla/tcpcl/client_established.go | 67 ++++++++++++ cla/tcpcl/client_init.go | 75 ++++++++++++++ cla/tcpcl/client_state.go | 6 +- cla/tcpcl/client_termination.go | 23 +++++ 6 files changed, 213 insertions(+), 178 deletions(-) create mode 100644 cla/tcpcl/client_contact.go create mode 100644 cla/tcpcl/client_established.go create mode 100644 cla/tcpcl/client_init.go create mode 100644 cla/tcpcl/client_termination.go diff --git a/cla/tcpcl/client.go b/cla/tcpcl/client.go index ec28a5d1..e193491b 100644 --- a/cla/tcpcl/client.go +++ b/cla/tcpcl/client.go @@ -29,7 +29,7 @@ type TCPCLClient struct { chSent ContactHeader chRecv ContactHeader - // Initialization state fields: + // Init state fields: initSent bool initRecv bool sessInitSent SessionInitMessage @@ -109,7 +109,7 @@ func (client *TCPCLClient) handler() { return } - case Initialization: + case Init: if err := client.handleSessInit(); err != nil { logger.WithError(err).Warn("Error occured during session initialization") @@ -127,176 +127,3 @@ func (client *TCPCLClient) handler() { } } } - -// handleContact manges the contact stage with the Contact Header exchange. -func (client *TCPCLClient) handleContact() error { - var logger = log.WithFields(log.Fields{ - "session": client, - "state": "contact", - }) - - switch { - case client.active && !client.contactSent, !client.active && !client.contactSent && client.contactRecv: - client.chSent = NewContactHeader(0) - if err := client.chSent.Marshal(client.rw); err != nil { - return err - } else if err := client.rw.Flush(); err != nil { - return err - } else { - client.contactSent = true - logger.WithField("msg", client.chSent).Debug("Sent Contact Header") - } - - case !client.active && !client.contactRecv, client.active && client.contactSent && !client.contactRecv: - if err := client.chRecv.Unmarshal(client.rw); err != nil { - return err - } else { - client.contactRecv = true - logger.WithField("msg", client.chRecv).Debug("Received Contact Header") - } - - case client.contactSent && client.contactRecv: - // TODO: check contact header flags - logger.Debug("Exchanged Contact Headers") - client.state.Next() - } - - return nil -} - -func (client *TCPCLClient) handleSessInit() error { - var logger = log.WithFields(log.Fields{ - "session": client, - "state": "initialization", - }) - - // XXX - const ( - keepalive = 10 - segmentMru = 0xFFFFFFFFFFFFFFFF - transferMru = 0xFFFFFFFFFFFFFFFF - ) - - switch { - case client.active && !client.initSent, !client.active && !client.initSent && client.initRecv: - client.sessInitSent = NewSessionInitMessage(keepalive, segmentMru, transferMru, client.endpointID.String()) - if err := client.sessInitSent.Marshal(client.rw); err != nil { - return err - } else if err := client.rw.Flush(); err != nil { - return err - } else { - client.initSent = true - logger.WithField("msg", client.sessInitSent).Debug("Sent SESS_INIT message") - } - - case !client.active && !client.initRecv, client.active && client.initSent && !client.initRecv: - if err := client.sessInitRecv.Unmarshal(client.rw); err != nil { - return err - } else { - client.initRecv = true - logger.WithField("msg", client.sessInitRecv).Debug("Received SESS_INIT message") - } - - case client.initSent && client.initRecv: - if eid, err := bundle.NewEndpointID(client.sessInitRecv.Eid); err != nil { - return err - } else { - client.peerEndpointID = eid - } - - client.keepalive = client.sessInitSent.KeepaliveInterval - if client.sessInitRecv.KeepaliveInterval < client.keepalive { - client.keepalive = client.sessInitRecv.KeepaliveInterval - } - client.segmentMru = client.sessInitSent.SegmentMru - if client.sessInitRecv.SegmentMru < client.segmentMru { - client.segmentMru = client.sessInitRecv.SegmentMru - } - client.transferMru = client.sessInitSent.TransferMru - if client.sessInitRecv.TransferMru < client.transferMru { - client.transferMru = client.sessInitRecv.TransferMru - } - - logger.WithFields(log.Fields{ - "endpoint ID": client.peerEndpointID, - "keepalive": client.keepalive, - "segment MRU": client.segmentMru, - "transfer MRU": client.transferMru, - }).Debug("Exchanged SESS_INIT messages") - client.state.Next() - } - - return nil -} - -func (client *TCPCLClient) keepaliveHandler() { - var logger = log.WithField("session", client) - - var keepaliveTicker = time.NewTicker(time.Duration(client.keepalive) * time.Second) - defer keepaliveTicker.Stop() - - for { - select { - case <-keepaliveTicker.C: - var keepaliveMsg = NewKeepaliveMessage() - if err := keepaliveMsg.Marshal(client.rw); err != nil { - logger.WithError(err).Warn("Sending KEEPALIVE errored") - } else if err := client.rw.Flush(); err != nil { - logger.WithError(err).Warn("Flushing KEEPALIVE errored") - } else { - log.WithField("msg", keepaliveMsg).Debug("Sent KEEPALIVE message") - } - - case <-client.keepaliveStopSyn: - close(client.keepaliveStopAck) - return - } - } -} - -func (client *TCPCLClient) handleEstablished() error { - var logger = log.WithField("session", client) - - if !client.keepaliveStarted { - go client.keepaliveHandler() - client.keepaliveStarted = true - } - - nextMsg, nextMsgErr := client.rw.ReadByte() - if nextMsgErr != nil { - return nextMsgErr - } else if err := client.rw.UnreadByte(); err != nil { - return err - } - - switch nextMsg { - case KEEPALIVE: - var keepaliveMsg KeepaliveMessage - if err := keepaliveMsg.Unmarshal(client.rw); err != nil { - return err - } else { - logger.WithField("msg", keepaliveMsg).Debug("Received KEEPALIVE message") - } - - default: - logger.WithField("magic", nextMsg).Debug("Received unsupported magic") - } - - return nil -} - -// terminate sends a SESS_TERM message to its peer and closes the session afterwards. -func (client *TCPCLClient) terminate(code SessionTerminationCode) { - var logger = log.WithField("session", client) - - var sessTerm = NewSessionTerminationMessage(0, code) - if err := sessTerm.Marshal(client.rw); err != nil { - logger.WithError(err).Warn("Failed to send session termination message") - } else if err := client.rw.Flush(); err != nil { - logger.WithError(err).Warn("Failed to flush buffer") - } else if err := client.conn.Close(); err != nil { - logger.WithError(err).Warn("Failed to close TCP connection") - } else { - logger.Info("Terminated session") - } -} diff --git a/cla/tcpcl/client_contact.go b/cla/tcpcl/client_contact.go new file mode 100644 index 00000000..613ec736 --- /dev/null +++ b/cla/tcpcl/client_contact.go @@ -0,0 +1,43 @@ +package tcpcl + +import ( + log "github.com/sirupsen/logrus" +) + +// This file contains code for the Client's contact state. + +// handleContact manges the contact state for the Contact Header exchange. +func (client *TCPCLClient) handleContact() error { + var logger = log.WithFields(log.Fields{ + "session": client, + "state": "contact", + }) + + switch { + case client.active && !client.contactSent, !client.active && !client.contactSent && client.contactRecv: + client.chSent = NewContactHeader(0) + if err := client.chSent.Marshal(client.rw); err != nil { + return err + } else if err := client.rw.Flush(); err != nil { + return err + } else { + client.contactSent = true + logger.WithField("msg", client.chSent).Debug("Sent Contact Header") + } + + case !client.active && !client.contactRecv, client.active && client.contactSent && !client.contactRecv: + if err := client.chRecv.Unmarshal(client.rw); err != nil { + return err + } else { + client.contactRecv = true + logger.WithField("msg", client.chRecv).Debug("Received Contact Header") + } + + case client.contactSent && client.contactRecv: + // TODO: check contact header flags + logger.Debug("Exchanged Contact Headers") + client.state.Next() + } + + return nil +} diff --git a/cla/tcpcl/client_established.go b/cla/tcpcl/client_established.go new file mode 100644 index 00000000..6f695cee --- /dev/null +++ b/cla/tcpcl/client_established.go @@ -0,0 +1,67 @@ +package tcpcl + +import ( + "time" + + log "github.com/sirupsen/logrus" +) + +// This file contains code for the Client's established state. + +// keepaliveHandler handles the KEEPALIVE messages. +func (client *TCPCLClient) keepaliveHandler() { + var logger = log.WithField("session", client) + + var keepaliveTicker = time.NewTicker(time.Duration(client.keepalive) * time.Second) + defer keepaliveTicker.Stop() + + for { + select { + case <-keepaliveTicker.C: + var keepaliveMsg = NewKeepaliveMessage() + if err := keepaliveMsg.Marshal(client.rw); err != nil { + logger.WithError(err).Warn("Sending KEEPALIVE errored") + } else if err := client.rw.Flush(); err != nil { + logger.WithError(err).Warn("Flushing KEEPALIVE errored") + } else { + log.WithField("msg", keepaliveMsg).Debug("Sent KEEPALIVE message") + } + + case <-client.keepaliveStopSyn: + close(client.keepaliveStopAck) + return + } + } +} + +// handleEstablished manges the established state. +func (client *TCPCLClient) handleEstablished() error { + var logger = log.WithField("session", client) + + if !client.keepaliveStarted { + go client.keepaliveHandler() + client.keepaliveStarted = true + } + + nextMsg, nextMsgErr := client.rw.ReadByte() + if nextMsgErr != nil { + return nextMsgErr + } else if err := client.rw.UnreadByte(); err != nil { + return err + } + + switch nextMsg { + case KEEPALIVE: + var keepaliveMsg KeepaliveMessage + if err := keepaliveMsg.Unmarshal(client.rw); err != nil { + return err + } else { + logger.WithField("msg", keepaliveMsg).Debug("Received KEEPALIVE message") + } + + default: + logger.WithField("magic", nextMsg).Debug("Received unsupported magic") + } + + return nil +} diff --git a/cla/tcpcl/client_init.go b/cla/tcpcl/client_init.go new file mode 100644 index 00000000..a70e8766 --- /dev/null +++ b/cla/tcpcl/client_init.go @@ -0,0 +1,75 @@ +package tcpcl + +import ( + log "github.com/sirupsen/logrus" + + "github.com/dtn7/dtn7-go/bundle" +) + +// This file contains code for the Client's contact state. + +// handleSessInit manges the initialization state. +func (client *TCPCLClient) handleSessInit() error { + var logger = log.WithFields(log.Fields{ + "session": client, + "state": "initialization", + }) + + // XXX + const ( + keepalive = 10 + segmentMru = 0xFFFFFFFFFFFFFFFF + transferMru = 0xFFFFFFFFFFFFFFFF + ) + + switch { + case client.active && !client.initSent, !client.active && !client.initSent && client.initRecv: + client.sessInitSent = NewSessionInitMessage(keepalive, segmentMru, transferMru, client.endpointID.String()) + if err := client.sessInitSent.Marshal(client.rw); err != nil { + return err + } else if err := client.rw.Flush(); err != nil { + return err + } else { + client.initSent = true + logger.WithField("msg", client.sessInitSent).Debug("Sent SESS_INIT message") + } + + case !client.active && !client.initRecv, client.active && client.initSent && !client.initRecv: + if err := client.sessInitRecv.Unmarshal(client.rw); err != nil { + return err + } else { + client.initRecv = true + logger.WithField("msg", client.sessInitRecv).Debug("Received SESS_INIT message") + } + + case client.initSent && client.initRecv: + if eid, err := bundle.NewEndpointID(client.sessInitRecv.Eid); err != nil { + return err + } else { + client.peerEndpointID = eid + } + + client.keepalive = client.sessInitSent.KeepaliveInterval + if client.sessInitRecv.KeepaliveInterval < client.keepalive { + client.keepalive = client.sessInitRecv.KeepaliveInterval + } + client.segmentMru = client.sessInitSent.SegmentMru + if client.sessInitRecv.SegmentMru < client.segmentMru { + client.segmentMru = client.sessInitRecv.SegmentMru + } + client.transferMru = client.sessInitSent.TransferMru + if client.sessInitRecv.TransferMru < client.transferMru { + client.transferMru = client.sessInitRecv.TransferMru + } + + logger.WithFields(log.Fields{ + "endpoint ID": client.peerEndpointID, + "keepalive": client.keepalive, + "segment MRU": client.segmentMru, + "transfer MRU": client.transferMru, + }).Debug("Exchanged SESS_INIT messages") + client.state.Next() + } + + return nil +} diff --git a/cla/tcpcl/client_state.go b/cla/tcpcl/client_state.go index 6a761cb9..2df30b77 100644 --- a/cla/tcpcl/client_state.go +++ b/cla/tcpcl/client_state.go @@ -12,8 +12,8 @@ const ( // after a TCP connection was established. Contact ClientState = iota - // Initialization is the SESS_INIT state. - Initialization ClientState = iota + // Init is the SESS_INIT state. + Init ClientState = iota // Established describes an established connection, allowing Bundles to be exchanged. Established ClientState = iota @@ -27,7 +27,7 @@ func (cs ClientState) String() string { switch cs { case Contact: return "contact" - case Initialization: + case Init: return "initialization" case Established: return "established" diff --git a/cla/tcpcl/client_termination.go b/cla/tcpcl/client_termination.go new file mode 100644 index 00000000..67e33e5a --- /dev/null +++ b/cla/tcpcl/client_termination.go @@ -0,0 +1,23 @@ +package tcpcl + +import ( + log "github.com/sirupsen/logrus" +) + +// This file contains code for the Client's termination state. + +// terminate sends a SESS_TERM message to its peer and closes the session afterwards. +func (client *TCPCLClient) terminate(code SessionTerminationCode) { + var logger = log.WithField("session", client) + + var sessTerm = NewSessionTerminationMessage(0, code) + if err := sessTerm.Marshal(client.rw); err != nil { + logger.WithError(err).Warn("Failed to send session termination message") + } else if err := client.rw.Flush(); err != nil { + logger.WithError(err).Warn("Failed to flush buffer") + } else if err := client.conn.Close(); err != nil { + logger.WithError(err).Warn("Failed to close TCP connection") + } else { + logger.Info("Terminated session") + } +} From d5a25bba0396570f6b888cdfa1feeeb4ae833016 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Sat, 14 Sep 2019 11:52:50 +0200 Subject: [PATCH 19/53] TCPCL: Check lasts KEEPALIVE reception time --- cla/tcpcl/client.go | 1 + cla/tcpcl/client_established.go | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/cla/tcpcl/client.go b/cla/tcpcl/client.go index e193491b..e397ca7d 100644 --- a/cla/tcpcl/client.go +++ b/cla/tcpcl/client.go @@ -43,6 +43,7 @@ type TCPCLClient struct { keepaliveStarted bool keepaliveStopSyn chan struct{} keepaliveStopAck chan struct{} + keepaliveLast time.Time } func NewTCPCLClient(conn net.Conn, endpointID bundle.EndpointID) *TCPCLClient { diff --git a/cla/tcpcl/client_established.go b/cla/tcpcl/client_established.go index 6f695cee..9a04d039 100644 --- a/cla/tcpcl/client_established.go +++ b/cla/tcpcl/client_established.go @@ -18,13 +18,29 @@ func (client *TCPCLClient) keepaliveHandler() { for { select { case <-keepaliveTicker.C: + // Send a keepalive var keepaliveMsg = NewKeepaliveMessage() if err := keepaliveMsg.Marshal(client.rw); err != nil { logger.WithError(err).Warn("Sending KEEPALIVE errored") } else if err := client.rw.Flush(); err != nil { logger.WithError(err).Warn("Flushing KEEPALIVE errored") } else { - log.WithField("msg", keepaliveMsg).Debug("Sent KEEPALIVE message") + logger.WithField("msg", keepaliveMsg).Debug("Sent KEEPALIVE message") + } + // TODO: terminate session + + // Check last received keepalive + var diff = time.Now().Sub(client.keepaliveLast) + if diff > 2*time.Duration(client.keepalive)*time.Second { + logger.WithFields(log.Fields{ + "last keepalive": client.keepaliveLast, + "interval": time.Duration(client.keepalive) * time.Second, + }).Warn("No KEEPALIVE was received within expected time frame") + + // TODO: terminate session + } else { + logger.WithField("last keepalive", client.keepaliveLast).Debug( + "Received last KEEPALIVE within expected time frame") } case <-client.keepaliveStopSyn: @@ -56,6 +72,7 @@ func (client *TCPCLClient) handleEstablished() error { if err := keepaliveMsg.Unmarshal(client.rw); err != nil { return err } else { + client.keepaliveLast = time.Now() logger.WithField("msg", keepaliveMsg).Debug("Received KEEPALIVE message") } From ab47ea026336e761e709cd648e7c3311191f2109 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Sat, 14 Sep 2019 13:17:33 +0200 Subject: [PATCH 20/53] TCPCL: Handle SESS_TERM --- cla/tcpcl/client.go | 18 ++++++++++++++++++ cla/tcpcl/client_established.go | 9 +++++++++ cla/tcpcl/client_state.go | 5 +++++ 3 files changed, 32 insertions(+) diff --git a/cla/tcpcl/client.go b/cla/tcpcl/client.go index e397ca7d..7367eebe 100644 --- a/cla/tcpcl/client.go +++ b/cla/tcpcl/client.go @@ -125,6 +125,24 @@ func (client *TCPCLClient) handler() { // TODO return } + + case Termination: + logger.Debug("Entering Termination state") + + if client.keepaliveStarted { + close(client.keepaliveStopSyn) + <-client.keepaliveStopAck + client.keepaliveStarted = false + } + + client.terminate(TerminationUnknown) + + logger.Info("rip in pieces") + return } } } + +func (client *TCPCLClient) Close() { + client.state.Terminate() +} diff --git a/cla/tcpcl/client_established.go b/cla/tcpcl/client_established.go index 9a04d039..114681f4 100644 --- a/cla/tcpcl/client_established.go +++ b/cla/tcpcl/client_established.go @@ -76,6 +76,15 @@ func (client *TCPCLClient) handleEstablished() error { logger.WithField("msg", keepaliveMsg).Debug("Received KEEPALIVE message") } + case SESS_TERM: + var sesstermMsg SessionTerminationMessage + if err := sesstermMsg.Unmarshal(client.rw); err != nil { + return err + } else { + logger.WithField("msg", sesstermMsg).Info("Received SESS_TERM") + client.state.Terminate() + } + default: logger.WithField("magic", nextMsg).Debug("Received unsupported magic") } diff --git a/cla/tcpcl/client_state.go b/cla/tcpcl/client_state.go index 2df30b77..e4189ac9 100644 --- a/cla/tcpcl/client_state.go +++ b/cla/tcpcl/client_state.go @@ -48,3 +48,8 @@ func (cs *ClientState) Next() (err error) { return } + +// Terminate sets the ClientState into the termination state. +func (cs *ClientState) Terminate() { + *cs = Termination +} From 35bb2ca355ec0c2e86e016e7104c1d71b1c086cd Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Sat, 14 Sep 2019 13:53:03 +0200 Subject: [PATCH 21/53] TCPCL: Change client state Next function --- cla/tcpcl/client_established.go | 2 +- cla/tcpcl/client_state.go | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/cla/tcpcl/client_established.go b/cla/tcpcl/client_established.go index 114681f4..6a4049bd 100644 --- a/cla/tcpcl/client_established.go +++ b/cla/tcpcl/client_established.go @@ -30,7 +30,7 @@ func (client *TCPCLClient) keepaliveHandler() { // TODO: terminate session // Check last received keepalive - var diff = time.Now().Sub(client.keepaliveLast) + var diff = time.Since(client.keepaliveLast) if diff > 2*time.Duration(client.keepalive)*time.Second { logger.WithFields(log.Fields{ "last keepalive": client.keepaliveLast, diff --git a/cla/tcpcl/client_state.go b/cla/tcpcl/client_state.go index e4189ac9..db4f6a2d 100644 --- a/cla/tcpcl/client_state.go +++ b/cla/tcpcl/client_state.go @@ -1,7 +1,5 @@ package tcpcl -import "errors" - // ClientState describes the state of a TCPCL Client. Each Client can always // upgrade its state to a later one, but cannot go back to a previous state. // A transition can be made into the following or the termination state. @@ -38,15 +36,11 @@ func (cs ClientState) String() string { } } -// Next enters the following ClientState or errors if there is no next state. -func (cs *ClientState) Next() (err error) { - if *cs == Termination { - err = errors.New("There is no state after termination") - } else { +// Next enters the following ClientState. +func (cs *ClientState) Next() { + if *cs != Termination { *cs += 1 } - - return } // Terminate sets the ClientState into the termination state. From 40f17ff453d4ee5e522e4580e9e625b7cbc24340 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Sun, 15 Sep 2019 11:18:11 +0200 Subject: [PATCH 22/53] TCPCL: Initial XFER_SEGMENT support --- cla/tcpcl/client_established.go | 36 ++++++++++++++++++ cla/tcpcl/client_init.go | 4 +- cla/tcpcl/transfer.go | 65 +++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 cla/tcpcl/transfer.go diff --git a/cla/tcpcl/client_established.go b/cla/tcpcl/client_established.go index 6a4049bd..2395707a 100644 --- a/cla/tcpcl/client_established.go +++ b/cla/tcpcl/client_established.go @@ -1,9 +1,12 @@ package tcpcl import ( + "io" "time" log "github.com/sirupsen/logrus" + + "github.com/dtn7/dtn7-go/bundle" ) // This file contains code for the Client's established state. @@ -85,9 +88,42 @@ func (client *TCPCLClient) handleEstablished() error { client.state.Terminate() } + case XFER_SEGMENT: + var dataTransMsg DataTransmissionMessage + if err := dataTransMsg.Unmarshal(client.rw); err != nil { + return err + } else { + logger.WithField("msg", dataTransMsg).Info("Received XFER_SEGMENT") + } + default: logger.WithField("magic", nextMsg).Debug("Received unsupported magic") } return nil } + +func (client *TCPCLClient) Send(bndl *bundle.Bundle) error { + var logger = log.WithField("session", client) + var t = NewBundleTransfer(23, *bndl) + + for { + dtm, err := t.NextSegment(client.segmentMru) + + if err == io.EOF { + logger.Info("Finished Transfer") + return nil + } else if err != nil { + logger.WithError(err).Warn("Fetching Segment errored") + return err + } + + if err := dtm.Marshal(client.rw); err != nil { + logger.WithField("msg", dtm).WithError(err).Warn( + "Sending XFER_SEGMENT errored") + return err + } else { + logger.WithField("msg", dtm).Debug("Sent XFER_SEGMENT") + } + } +} diff --git a/cla/tcpcl/client_init.go b/cla/tcpcl/client_init.go index a70e8766..fef41fb1 100644 --- a/cla/tcpcl/client_init.go +++ b/cla/tcpcl/client_init.go @@ -18,8 +18,8 @@ func (client *TCPCLClient) handleSessInit() error { // XXX const ( keepalive = 10 - segmentMru = 0xFFFFFFFFFFFFFFFF - transferMru = 0xFFFFFFFFFFFFFFFF + segmentMru = 1500 + transferMru = 0xFFFFFFFF ) switch { diff --git a/cla/tcpcl/transfer.go b/cla/tcpcl/transfer.go new file mode 100644 index 00000000..6a373b1a --- /dev/null +++ b/cla/tcpcl/transfer.go @@ -0,0 +1,65 @@ +package tcpcl + +import ( + "bufio" + "io" + + "github.com/dtn7/dtn7-go/bundle" +) + +// Transfer represents a Bundle Transfer for the TCPCL. +type Transfer struct { + Id uint64 + + startFlag bool + dataStream io.Reader +} + +// NewTransfer creates a new Transfer for data written into the returned Writer. +func NewTransfer(id uint64) (t *Transfer, w io.Writer) { + r, w := io.Pipe() + t = &Transfer{ + Id: id, + startFlag: true, + dataStream: r, + } + + return +} + +// NewBundleTransfer creates a new Transfer for a Bundle. +func NewBundleTransfer(id uint64, b bundle.Bundle) *Transfer { + var t, w = NewTransfer(id) + + go func(w *io.PipeWriter) { + bw := bufio.NewWriter(w) + + _ = b.MarshalCbor(bw) + _ = bw.Flush() + _ = w.Close() + }(w.(*io.PipeWriter)) + + return t +} + +// NextSegment creates the next XFER_SEGMENT for the given MRU or an EOF in case +// of a finished Writer. +func (t *Transfer) NextSegment(mru uint64) (dtm DataTransmissionMessage, err error) { + var segFlags SegmentFlags + + if t.startFlag { + t.startFlag = false + segFlags |= SegmentStart + } + + var buf = make([]byte, mru) + if n, rErr := t.dataStream.Read(buf); rErr != nil { + err = rErr + return + } else if uint64(n) < mru { + segFlags |= SegmentEnd + } + + dtm = NewDataTransmissionMessage(segFlags, t.Id, buf) + return +} From 1cc5b52c98797f2798af1c0853e675b0c41b6842 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Sat, 21 Sep 2019 13:40:49 +0200 Subject: [PATCH 23/53] TCPCL: Crop null bytes for outbunding Transfers --- cla/tcpcl/client.go | 2 ++ cla/tcpcl/client_established.go | 9 ++++++++- cla/tcpcl/transfer.go | 6 ++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/cla/tcpcl/client.go b/cla/tcpcl/client.go index 7367eebe..b76ee215 100644 --- a/cla/tcpcl/client.go +++ b/cla/tcpcl/client.go @@ -44,6 +44,8 @@ type TCPCLClient struct { keepaliveStopSyn chan struct{} keepaliveStopAck chan struct{} keepaliveLast time.Time + + transferIdOut uint64 } func NewTCPCLClient(conn net.Conn, endpointID bundle.EndpointID) *TCPCLClient { diff --git a/cla/tcpcl/client_established.go b/cla/tcpcl/client_established.go index 2395707a..caadff3d 100644 --- a/cla/tcpcl/client_established.go +++ b/cla/tcpcl/client_established.go @@ -105,7 +105,14 @@ func (client *TCPCLClient) handleEstablished() error { func (client *TCPCLClient) Send(bndl *bundle.Bundle) error { var logger = log.WithField("session", client) - var t = NewBundleTransfer(23, *bndl) + + client.transferIdOut += 1 + var t = NewBundleTransfer(client.transferIdOut, *bndl) + + logger.WithFields(log.Fields{ + "bundle": bndl, + "transfer": t, + }).Info("Started Bundle Transfer") for { dtm, err := t.NextSegment(client.segmentMru) diff --git a/cla/tcpcl/transfer.go b/cla/tcpcl/transfer.go index 6a373b1a..82d4f5be 100644 --- a/cla/tcpcl/transfer.go +++ b/cla/tcpcl/transfer.go @@ -2,6 +2,7 @@ package tcpcl import ( "bufio" + "fmt" "io" "github.com/dtn7/dtn7-go/bundle" @@ -27,6 +28,10 @@ func NewTransfer(id uint64) (t *Transfer, w io.Writer) { return } +func (t Transfer) String() string { + return fmt.Sprintf("%d", t.Id) +} + // NewBundleTransfer creates a new Transfer for a Bundle. func NewBundleTransfer(id uint64, b bundle.Bundle) *Transfer { var t, w = NewTransfer(id) @@ -57,6 +62,7 @@ func (t *Transfer) NextSegment(mru uint64) (dtm DataTransmissionMessage, err err err = rErr return } else if uint64(n) < mru { + buf = buf[:n] segFlags |= SegmentEnd } From a0bfd1abc989e954f2d2a96288c6c05b14aba5b8 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Sat, 21 Sep 2019 17:13:29 +0200 Subject: [PATCH 24/53] TCPCL: Generic Message type and parsing --- cla/tcpcl/message.go | 63 ++++++++++++++++++++++++++ cla/tcpcl/message_keepalive.go | 19 +++----- cla/tcpcl/message_test.go | 82 ++++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 12 deletions(-) create mode 100644 cla/tcpcl/message.go create mode 100644 cla/tcpcl/message_test.go diff --git a/cla/tcpcl/message.go b/cla/tcpcl/message.go new file mode 100644 index 00000000..e75490cc --- /dev/null +++ b/cla/tcpcl/message.go @@ -0,0 +1,63 @@ +package tcpcl + +import ( + "bytes" + "fmt" + "io" + "reflect" +) + +// Message describes all kind of TCPCL messages, which have their serialization +// and deserialization in common. +type Message interface { + Marshal(w io.Writer) error + Unmarshal(r io.Reader) error +} + +// messages maps the different TCPCL message type codes to an example instance +// of their type. +var messages = map[uint8]Message{ + SESS_INIT: &SessionInitMessage{}, + SESS_TERM: &SessionTerminationMessage{}, + XFER_SEGMENT: &DataTransmissionMessage{}, + XFER_ACK: &DataAcknowledgementMessage{}, + XFER_REFUSE: &TransferRefusalMessage{}, + KEEPALIVE: &KeepaliveMessage{}, + MSG_REJECT: &MessageRejectionMessage{}, +} + +// NewMessage creates a new Message type for a given type code. +func NewMessage(typeCode uint8) (msg Message, err error) { + msgType, exists := messages[typeCode] + if !exists { + err = fmt.Errorf("No TCPCL Message registered for type code %d", typeCode) + return + } + + msgElem := reflect.TypeOf(msgType).Elem() + msg = reflect.New(msgElem).Interface().(Message) + return +} + +// ReadMessage parses the next TCPCL message from the Reader. +func ReadMessage(r io.Reader) (msg Message, err error) { + msgTypeBytes := make([]byte, 1) + if n, msgTypeErr := r.Read(msgTypeBytes); msgTypeErr != nil { + err = msgTypeErr + return + } else if n != 1 { + err = fmt.Errorf("Expected one byte, got %d bytes", n) + return + } + + msg, msgErr := NewMessage(msgTypeBytes[0]) + if msgErr != nil { + err = msgErr + return + } + + mr := io.MultiReader(bytes.NewBuffer(msgTypeBytes), r) + + err = msg.Unmarshal(mr) + return +} diff --git a/cla/tcpcl/message_keepalive.go b/cla/tcpcl/message_keepalive.go index 50c91bea..9418f016 100644 --- a/cla/tcpcl/message_keepalive.go +++ b/cla/tcpcl/message_keepalive.go @@ -10,11 +10,11 @@ import ( const KEEPALIVE uint8 = 0x04 // KeepaliveMessage is the KEEPALIVE message for session upkeep. -type KeepaliveMessage uint8 +type KeepaliveMessage struct{} // NewKeepaliveMessage creates a new KeepaliveMessage. func NewKeepaliveMessage() KeepaliveMessage { - return KeepaliveMessage(KEEPALIVE) + return KeepaliveMessage{} } func (_ KeepaliveMessage) String() string { @@ -22,20 +22,15 @@ func (_ KeepaliveMessage) String() string { } func (km KeepaliveMessage) Marshal(w io.Writer) error { - if uint8(km) != KEEPALIVE { - return fmt.Errorf("KEEPALIVE's value is %d instead of %d", uint8(km), KEEPALIVE) - } - - return binary.Write(w, binary.BigEndian, km) + return binary.Write(w, binary.BigEndian, KEEPALIVE) } func (km *KeepaliveMessage) Unmarshal(r io.Reader) error { - if err := binary.Read(r, binary.BigEndian, km); err != nil { + var kmType uint8 + if err := binary.Read(r, binary.BigEndian, &kmType); err != nil { return err - } - - if uint8(*km) != KEEPALIVE { - return fmt.Errorf("KEEPALIVE's value is %d instead of %d", uint8(*km), KEEPALIVE) + } else if kmType != KEEPALIVE { + return fmt.Errorf("KEEPALIVE's value is %d instead of %d", kmType, KEEPALIVE) } return nil diff --git a/cla/tcpcl/message_test.go b/cla/tcpcl/message_test.go new file mode 100644 index 00000000..ac2ab79f --- /dev/null +++ b/cla/tcpcl/message_test.go @@ -0,0 +1,82 @@ +package tcpcl + +import ( + "bytes" + "reflect" + "testing" +) + +func TestNewMessage(t *testing.T) { + tests := []struct { + valid bool + typeCode uint8 + msgType Message + }{ + {true, SESS_INIT, &SessionInitMessage{}}, + {true, KEEPALIVE, &KeepaliveMessage{}}, + {true, XFER_SEGMENT, &DataTransmissionMessage{}}, + {false, 0xFF, nil}, + } + + for _, test := range tests { + if msg, err := NewMessage(test.typeCode); (err == nil) != test.valid { + t.Fatalf("Error state was not expected; valid := %t, got := %v", test.valid, err) + } else if !test.valid { + continue + } else if msgType := reflect.TypeOf(msg); msgType != reflect.TypeOf(test.msgType) { + t.Fatalf("Message Type is wrong; expected := %v, got := %v", test.msgType, msgType) + } + } +} + +func TestReadMessage(t *testing.T) { + // cla/tcpcl/message_sess_init_test.go + t1data := []byte{ + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x64, 0x74, 0x6e, + 0x3a, 0x6e, 0x6f, 0x6e, 0x65, 0x00, 0x00, 0x00, 0x00, + } + t1msg := NewSessionInitMessage(0, 0, 0, "dtn:none") + + t2data := []byte{0x04} + t2msg := NewKeepaliveMessage() + + // cla/tcpcl/message_xfer_segment_test.go + t3data := []byte{ + 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x75, 0x66, + 0x66, + } + t3msg := NewDataTransmissionMessage(SegmentStart|SegmentEnd, 1, []byte("uff")) + + // cla/tcpcl/message_xfer_segment_test.go + t4data := []byte{ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, + } + + t5data := []byte{0xC0, 0xFF, 0xEE} + + tests := []struct { + valid bool + data []byte + msg Message + }{ + {true, t1data, &t1msg}, + {true, t2data, &t2msg}, + {true, t3data, &t3msg}, + {false, t4data, nil}, + {false, t5data, nil}, + } + + for _, test := range tests { + var buf = bytes.NewBuffer(test.data) + if msg, err := ReadMessage(buf); (err == nil) != test.valid { + t.Fatalf("Error state was not expected; valid := %t, got := %v", test.valid, err) + } else if !test.valid { + continue + } else if reflect.DeepEqual(msg, &test.msg) { + t.Fatalf("Message does not match; expected := %v, got := %v", test.msg, msg) + } + } +} From 447d6efd3302e3dea4f32d8e6fadd580f99b5f86 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 23 Sep 2019 11:42:56 +0200 Subject: [PATCH 25/53] TCPCL: Central message exchange handler --- cla/tcpcl/client.go | 60 +++++++++++++- cla/tcpcl/client_contact.go | 34 ++++---- cla/tcpcl/client_established.go | 143 ++++++++++++++------------------ cla/tcpcl/client_init.go | 34 ++++---- cla/tcpcl/client_termination.go | 18 ++-- cla/tcpcl/contact_header.go | 4 +- cla/tcpcl/message.go | 1 + 7 files changed, 155 insertions(+), 139 deletions(-) diff --git a/cla/tcpcl/client.go b/cla/tcpcl/client.go index b76ee215..79a2082d 100644 --- a/cla/tcpcl/client.go +++ b/cla/tcpcl/client.go @@ -18,7 +18,9 @@ type TCPCLClient struct { peerEndpointID bundle.EndpointID conn net.Conn - rw *bufio.ReadWriter + + msgsOut chan Message + msgsIn chan Message active bool state ClientState @@ -52,6 +54,8 @@ func NewTCPCLClient(conn net.Conn, endpointID bundle.EndpointID) *TCPCLClient { return &TCPCLClient{ conn: conn, active: false, + msgsOut: make(chan Message), + msgsIn: make(chan Message), endpointID: endpointID, keepaliveStopSyn: make(chan struct{}), keepaliveStopAck: make(chan struct{}), @@ -62,13 +66,15 @@ func Dial(address string, endpointID bundle.EndpointID) *TCPCLClient { return &TCPCLClient{ address: address, active: true, + msgsOut: make(chan Message), + msgsIn: make(chan Message), endpointID: endpointID, keepaliveStopSyn: make(chan struct{}), keepaliveStopAck: make(chan struct{}), } } -func (client *TCPCLClient) String() string { +func (client TCPCLClient) String() string { var b strings.Builder fmt.Fprintf(&b, "TCPCL(") @@ -79,6 +85,14 @@ func (client *TCPCLClient) String() string { return b.String() } +// log prepares a new log entry with predefined session data. +func (client TCPCLClient) log() *log.Entry { + return log.WithFields(log.Fields{ + "session": client.String(), + "state": client.state, + }) +} + func (client *TCPCLClient) Start() (err error, retry bool) { if client.conn == nil { if conn, connErr := net.DialTimeout("tcp", client.address, time.Second); connErr != nil { @@ -88,14 +102,54 @@ func (client *TCPCLClient) Start() (err error, retry bool) { client.conn = conn } } - client.rw = bufio.NewReadWriter(bufio.NewReader(client.conn), bufio.NewWriter(client.conn)) log.Info("Starting client") + go client.handleConnection() go client.handler() + return } +func (client *TCPCLClient) handleConnection() { + defer func() { + // TODO + client.log().Debug("Leaving handler function") + }() + + var rw = bufio.NewReadWriter(bufio.NewReader(client.conn), bufio.NewWriter(client.conn)) + + for { + select { + case msg := <-client.msgsOut: + if err := msg.Marshal(rw); err != nil { + client.log().WithError(err).WithField("msg", msg).Error("Sending message errored") + return + } else if err := rw.Flush(); err != nil { + client.log().WithError(err).WithField("msg", msg).Error("Flushing errored") + return + } else { + client.log().WithField("msg", msg).Debug("Sent message") + } + + default: + if err := client.conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)); err != nil { + client.log().WithError(err).Error("Setting read deadline errored") + return + } + + if msg, err := ReadMessage(rw); err == nil { + client.log().WithField("msg", msg).Debug("Received message") + client.msgsIn <- msg + } else if netErr, ok := err.(net.Error); ok && !netErr.Timeout() { + client.log().WithError(netErr).Warn("Network error occured") + } else if !ok { + client.log().WithError(err).Warn("Parsing next message errored") + } + } + } +} + func (client *TCPCLClient) handler() { var logger = log.WithFields(log.Fields{ "session": client, diff --git a/cla/tcpcl/client_contact.go b/cla/tcpcl/client_contact.go index 613ec736..7d015c08 100644 --- a/cla/tcpcl/client_contact.go +++ b/cla/tcpcl/client_contact.go @@ -1,41 +1,37 @@ package tcpcl import ( - log "github.com/sirupsen/logrus" + "fmt" ) // This file contains code for the Client's contact state. // handleContact manges the contact state for the Contact Header exchange. func (client *TCPCLClient) handleContact() error { - var logger = log.WithFields(log.Fields{ - "session": client, - "state": "contact", - }) - switch { case client.active && !client.contactSent, !client.active && !client.contactSent && client.contactRecv: client.chSent = NewContactHeader(0) - if err := client.chSent.Marshal(client.rw); err != nil { - return err - } else if err := client.rw.Flush(); err != nil { - return err - } else { - client.contactSent = true - logger.WithField("msg", client.chSent).Debug("Sent Contact Header") - } + client.contactSent = true + + client.msgsOut <- &client.chSent + client.log().WithField("msg", client.chSent).Debug("Sent Contact Header") case !client.active && !client.contactRecv, client.active && client.contactSent && !client.contactRecv: - if err := client.chRecv.Unmarshal(client.rw); err != nil { - return err - } else { + msg := <-client.msgsIn + switch msg.(type) { + case *ContactHeader: + client.chRecv = *msg.(*ContactHeader) client.contactRecv = true - logger.WithField("msg", client.chRecv).Debug("Received Contact Header") + client.log().WithField("msg", client.chRecv).Debug("Received Contact Header") + + default: + client.log().WithField("msg", msg).Warn("Received wrong message") + return fmt.Errorf("Wrong message type") } case client.contactSent && client.contactRecv: // TODO: check contact header flags - logger.Debug("Exchanged Contact Headers") + client.log().Debug("Exchanged Contact Headers") client.state.Next() } diff --git a/cla/tcpcl/client_established.go b/cla/tcpcl/client_established.go index caadff3d..d0e8dbda 100644 --- a/cla/tcpcl/client_established.go +++ b/cla/tcpcl/client_established.go @@ -12,104 +12,86 @@ import ( // This file contains code for the Client's established state. // keepaliveHandler handles the KEEPALIVE messages. +// TODO: re-enable code func (client *TCPCLClient) keepaliveHandler() { - var logger = log.WithField("session", client) - - var keepaliveTicker = time.NewTicker(time.Duration(client.keepalive) * time.Second) - defer keepaliveTicker.Stop() - - for { - select { - case <-keepaliveTicker.C: - // Send a keepalive - var keepaliveMsg = NewKeepaliveMessage() - if err := keepaliveMsg.Marshal(client.rw); err != nil { - logger.WithError(err).Warn("Sending KEEPALIVE errored") - } else if err := client.rw.Flush(); err != nil { - logger.WithError(err).Warn("Flushing KEEPALIVE errored") - } else { - logger.WithField("msg", keepaliveMsg).Debug("Sent KEEPALIVE message") - } - // TODO: terminate session - - // Check last received keepalive - var diff = time.Since(client.keepaliveLast) - if diff > 2*time.Duration(client.keepalive)*time.Second { - logger.WithFields(log.Fields{ - "last keepalive": client.keepaliveLast, - "interval": time.Duration(client.keepalive) * time.Second, - }).Warn("No KEEPALIVE was received within expected time frame") - + /* + var logger = log.WithField("session", client) + + var keepaliveTicker = time.NewTicker(time.Duration(client.keepalive) * time.Second) + defer keepaliveTicker.Stop() + + for { + select { + case <-keepaliveTicker.C: + // Send a keepalive + var keepaliveMsg = NewKeepaliveMessage() + if err := keepaliveMsg.Marshal(client.rw); err != nil { + logger.WithError(err).Warn("Sending KEEPALIVE errored") + } else if err := client.rw.Flush(); err != nil { + logger.WithError(err).Warn("Flushing KEEPALIVE errored") + } else { + logger.WithField("msg", keepaliveMsg).Debug("Sent KEEPALIVE message") + } // TODO: terminate session - } else { - logger.WithField("last keepalive", client.keepaliveLast).Debug( - "Received last KEEPALIVE within expected time frame") - } - case <-client.keepaliveStopSyn: - close(client.keepaliveStopAck) - return + // Check last received keepalive + var diff = time.Since(client.keepaliveLast) + if diff > 2*time.Duration(client.keepalive)*time.Second { + logger.WithFields(log.Fields{ + "last keepalive": client.keepaliveLast, + "interval": time.Duration(client.keepalive) * time.Second, + }).Warn("No KEEPALIVE was received within expected time frame") + + // TODO: terminate session + } else { + logger.WithField("last keepalive", client.keepaliveLast).Debug( + "Received last KEEPALIVE within expected time frame") + } + + case <-client.keepaliveStopSyn: + close(client.keepaliveStopAck) + return + } } - } + */ } // handleEstablished manges the established state. func (client *TCPCLClient) handleEstablished() error { - var logger = log.WithField("session", client) - if !client.keepaliveStarted { - go client.keepaliveHandler() - client.keepaliveStarted = true + // TODO + // go client.keepaliveHandler() + // client.keepaliveStarted = true } - nextMsg, nextMsgErr := client.rw.ReadByte() - if nextMsgErr != nil { - return nextMsgErr - } else if err := client.rw.UnreadByte(); err != nil { - return err - } + msg := <-client.msgsIn + switch msg.(type) { + case *KeepaliveMessage: + keepaliveMsg := *msg.(*KeepaliveMessage) + client.keepaliveLast = time.Now() + client.log().WithField("msg", keepaliveMsg).Debug("Received KEEPALIVE message") - switch nextMsg { - case KEEPALIVE: - var keepaliveMsg KeepaliveMessage - if err := keepaliveMsg.Unmarshal(client.rw); err != nil { - return err - } else { - client.keepaliveLast = time.Now() - logger.WithField("msg", keepaliveMsg).Debug("Received KEEPALIVE message") - } - - case SESS_TERM: - var sesstermMsg SessionTerminationMessage - if err := sesstermMsg.Unmarshal(client.rw); err != nil { - return err - } else { - logger.WithField("msg", sesstermMsg).Info("Received SESS_TERM") - client.state.Terminate() - } + case *SessionTerminationMessage: + sesstermMsg := *msg.(*SessionTerminationMessage) + client.log().WithField("msg", sesstermMsg).Info("Received SESS_TERM") + client.state.Terminate() - case XFER_SEGMENT: - var dataTransMsg DataTransmissionMessage - if err := dataTransMsg.Unmarshal(client.rw); err != nil { - return err - } else { - logger.WithField("msg", dataTransMsg).Info("Received XFER_SEGMENT") - } + case *DataTransmissionMessage: + dataTransMsg := *msg.(*DataTransmissionMessage) + client.log().WithField("msg", dataTransMsg).Info("Received XFER_SEGMENT") default: - logger.WithField("magic", nextMsg).Debug("Received unsupported magic") + client.log().WithField("msg", msg).Warn("Received unexpected message") } return nil } func (client *TCPCLClient) Send(bndl *bundle.Bundle) error { - var logger = log.WithField("session", client) - client.transferIdOut += 1 var t = NewBundleTransfer(client.transferIdOut, *bndl) - logger.WithFields(log.Fields{ + client.log().WithFields(log.Fields{ "bundle": bndl, "transfer": t, }).Info("Started Bundle Transfer") @@ -118,19 +100,14 @@ func (client *TCPCLClient) Send(bndl *bundle.Bundle) error { dtm, err := t.NextSegment(client.segmentMru) if err == io.EOF { - logger.Info("Finished Transfer") + client.log().Info("Finished Transfer") return nil } else if err != nil { - logger.WithError(err).Warn("Fetching Segment errored") + client.log().WithError(err).Warn("Fetching Segment errored") return err } - if err := dtm.Marshal(client.rw); err != nil { - logger.WithField("msg", dtm).WithError(err).Warn( - "Sending XFER_SEGMENT errored") - return err - } else { - logger.WithField("msg", dtm).Debug("Sent XFER_SEGMENT") - } + client.msgsOut <- &dtm + client.log().WithField("msg", dtm).Debug("Sent XFER_SEGMENT") } } diff --git a/cla/tcpcl/client_init.go b/cla/tcpcl/client_init.go index fef41fb1..a1011e9a 100644 --- a/cla/tcpcl/client_init.go +++ b/cla/tcpcl/client_init.go @@ -1,6 +1,8 @@ package tcpcl import ( + "fmt" + log "github.com/sirupsen/logrus" "github.com/dtn7/dtn7-go/bundle" @@ -10,11 +12,6 @@ import ( // handleSessInit manges the initialization state. func (client *TCPCLClient) handleSessInit() error { - var logger = log.WithFields(log.Fields{ - "session": client, - "state": "initialization", - }) - // XXX const ( keepalive = 10 @@ -25,21 +22,22 @@ func (client *TCPCLClient) handleSessInit() error { switch { case client.active && !client.initSent, !client.active && !client.initSent && client.initRecv: client.sessInitSent = NewSessionInitMessage(keepalive, segmentMru, transferMru, client.endpointID.String()) - if err := client.sessInitSent.Marshal(client.rw); err != nil { - return err - } else if err := client.rw.Flush(); err != nil { - return err - } else { - client.initSent = true - logger.WithField("msg", client.sessInitSent).Debug("Sent SESS_INIT message") - } + client.initSent = true + + client.msgsOut <- &client.sessInitSent + client.log().WithField("msg", client.sessInitSent).Debug("Sent SESS_INIT message") case !client.active && !client.initRecv, client.active && client.initSent && !client.initRecv: - if err := client.sessInitRecv.Unmarshal(client.rw); err != nil { - return err - } else { + msg := <-client.msgsIn + switch msg.(type) { + case *SessionInitMessage: + client.sessInitRecv = *msg.(*SessionInitMessage) client.initRecv = true - logger.WithField("msg", client.sessInitRecv).Debug("Received SESS_INIT message") + client.log().WithField("msg", client.sessInitRecv).Debug("Received SESS_INIT message") + + default: + client.log().WithField("msg", msg).Warn("Received wrong message") + return fmt.Errorf("Wrong message type") } case client.initSent && client.initRecv: @@ -62,7 +60,7 @@ func (client *TCPCLClient) handleSessInit() error { client.transferMru = client.sessInitRecv.TransferMru } - logger.WithFields(log.Fields{ + client.log().WithFields(log.Fields{ "endpoint ID": client.peerEndpointID, "keepalive": client.keepalive, "segment MRU": client.segmentMru, diff --git a/cla/tcpcl/client_termination.go b/cla/tcpcl/client_termination.go index 67e33e5a..3ee48563 100644 --- a/cla/tcpcl/client_termination.go +++ b/cla/tcpcl/client_termination.go @@ -1,23 +1,15 @@ package tcpcl -import ( - log "github.com/sirupsen/logrus" -) - // This file contains code for the Client's termination state. // terminate sends a SESS_TERM message to its peer and closes the session afterwards. func (client *TCPCLClient) terminate(code SessionTerminationCode) { - var logger = log.WithField("session", client) - var sessTerm = NewSessionTerminationMessage(0, code) - if err := sessTerm.Marshal(client.rw); err != nil { - logger.WithError(err).Warn("Failed to send session termination message") - } else if err := client.rw.Flush(); err != nil { - logger.WithError(err).Warn("Failed to flush buffer") - } else if err := client.conn.Close(); err != nil { - logger.WithError(err).Warn("Failed to close TCP connection") + client.msgsOut <- &sessTerm + + if err := client.conn.Close(); err != nil { + client.log().WithError(err).Warn("Failed to close TCP connection") } else { - logger.Info("Terminated session") + client.log().Info("Terminated session") } } diff --git a/cla/tcpcl/contact_header.go b/cla/tcpcl/contact_header.go index d005d3f3..89c92d66 100644 --- a/cla/tcpcl/contact_header.go +++ b/cla/tcpcl/contact_header.go @@ -58,10 +58,8 @@ func (ch ContactHeader) Marshal(w io.Writer) error { func (ch *ContactHeader) Unmarshal(r io.Reader) error { var data = make([]byte, 6) - if n, err := r.Read(data); err != nil { + if _, err := io.ReadFull(r, data); err != nil { return err - } else if n != len(data) { - return fmt.Errorf("Read %d octets instead of %d", n, len(data)) } if !bytes.Equal(data[:4], []byte("dtn!")) { diff --git a/cla/tcpcl/message.go b/cla/tcpcl/message.go index e75490cc..ae53c375 100644 --- a/cla/tcpcl/message.go +++ b/cla/tcpcl/message.go @@ -24,6 +24,7 @@ var messages = map[uint8]Message{ XFER_REFUSE: &TransferRefusalMessage{}, KEEPALIVE: &KeepaliveMessage{}, MSG_REJECT: &MessageRejectionMessage{}, + 0x64: &ContactHeader{}, } // NewMessage creates a new Message type for a given type code. From d37bbfe4fd892c44111eb119cf7f2bab86f5b833 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 23 Sep 2019 12:56:20 +0200 Subject: [PATCH 26/53] TCPCL: Add KEEPALIVE to the Established state --- cla/tcpcl/client.go | 63 +++++++++---------- cla/tcpcl/client_established.go | 103 +++++++++++++------------------- 2 files changed, 67 insertions(+), 99 deletions(-) diff --git a/cla/tcpcl/client.go b/cla/tcpcl/client.go index 79a2082d..9181635c 100644 --- a/cla/tcpcl/client.go +++ b/cla/tcpcl/client.go @@ -43,34 +43,29 @@ type TCPCLClient struct { // Established state fields: keepaliveStarted bool - keepaliveStopSyn chan struct{} - keepaliveStopAck chan struct{} keepaliveLast time.Time + keepaliveTicker *time.Ticker transferIdOut uint64 } func NewTCPCLClient(conn net.Conn, endpointID bundle.EndpointID) *TCPCLClient { return &TCPCLClient{ - conn: conn, - active: false, - msgsOut: make(chan Message), - msgsIn: make(chan Message), - endpointID: endpointID, - keepaliveStopSyn: make(chan struct{}), - keepaliveStopAck: make(chan struct{}), + conn: conn, + active: false, + msgsOut: make(chan Message, 100), + msgsIn: make(chan Message), + endpointID: endpointID, } } func Dial(address string, endpointID bundle.EndpointID) *TCPCLClient { return &TCPCLClient{ - address: address, - active: true, - msgsOut: make(chan Message), - msgsIn: make(chan Message), - endpointID: endpointID, - keepaliveStopSyn: make(chan struct{}), - keepaliveStopAck: make(chan struct{}), + address: address, + active: true, + msgsOut: make(chan Message, 100), + msgsIn: make(chan Message), + endpointID: endpointID, } } @@ -106,7 +101,7 @@ func (client *TCPCLClient) Start() (err error, retry bool) { log.Info("Starting client") go client.handleConnection() - go client.handler() + go client.handleState() return } @@ -114,7 +109,7 @@ func (client *TCPCLClient) Start() (err error, retry bool) { func (client *TCPCLClient) handleConnection() { defer func() { // TODO - client.log().Debug("Leaving handler function") + client.log().Debug("Leaving connection handler function") }() var rw = bufio.NewReadWriter(bufio.NewReader(client.conn), bufio.NewWriter(client.conn)) @@ -142,7 +137,8 @@ func (client *TCPCLClient) handleConnection() { client.log().WithField("msg", msg).Debug("Received message") client.msgsIn <- msg } else if netErr, ok := err.(net.Error); ok && !netErr.Timeout() { - client.log().WithError(netErr).Warn("Network error occured") + client.log().WithError(netErr).Error("Network error occured") + return } else if !ok { client.log().WithError(err).Warn("Parsing next message errored") } @@ -150,17 +146,17 @@ func (client *TCPCLClient) handleConnection() { } } -func (client *TCPCLClient) handler() { - var logger = log.WithFields(log.Fields{ - "session": client, - "state": client.state, - }) +func (client *TCPCLClient) handleState() { + defer func() { + // TODO + client.log().Debug("Leaving state handler function") + }() for { switch client.state { case Contact: if err := client.handleContact(); err != nil { - logger.WithError(err).Warn("Error occured during contact header exchange") + client.log().WithError(err).Warn("Error occured during contact header exchange") client.terminate(TerminationContactFailure) return @@ -168,7 +164,7 @@ func (client *TCPCLClient) handler() { case Init: if err := client.handleSessInit(); err != nil { - logger.WithError(err).Warn("Error occured during session initialization") + client.log().WithError(err).Warn("Error occured during session initialization") client.terminate(TerminationUnknown) return @@ -176,24 +172,19 @@ func (client *TCPCLClient) handler() { case Established: if err := client.handleEstablished(); err != nil { - logger.WithError(err).Warn("Error occured during established session") + client.log().WithError(err).Warn("Error occured during established session") - // TODO + client.terminate(TerminationUnknown) return } case Termination: - logger.Debug("Entering Termination state") - - if client.keepaliveStarted { - close(client.keepaliveStopSyn) - <-client.keepaliveStopAck - client.keepaliveStarted = false - } + // TODO + client.log().Debug("Entering Termination state") client.terminate(TerminationUnknown) - logger.Info("rip in pieces") + client.log().Info("rip in pieces") return } } diff --git a/cla/tcpcl/client_established.go b/cla/tcpcl/client_established.go index d0e8dbda..79dda3f7 100644 --- a/cla/tcpcl/client_established.go +++ b/cla/tcpcl/client_established.go @@ -11,77 +11,54 @@ import ( // This file contains code for the Client's established state. -// keepaliveHandler handles the KEEPALIVE messages. -// TODO: re-enable code -func (client *TCPCLClient) keepaliveHandler() { - /* - var logger = log.WithField("session", client) - - var keepaliveTicker = time.NewTicker(time.Duration(client.keepalive) * time.Second) - defer keepaliveTicker.Stop() - - for { - select { - case <-keepaliveTicker.C: - // Send a keepalive - var keepaliveMsg = NewKeepaliveMessage() - if err := keepaliveMsg.Marshal(client.rw); err != nil { - logger.WithError(err).Warn("Sending KEEPALIVE errored") - } else if err := client.rw.Flush(); err != nil { - logger.WithError(err).Warn("Flushing KEEPALIVE errored") - } else { - logger.WithField("msg", keepaliveMsg).Debug("Sent KEEPALIVE message") - } - // TODO: terminate session - - // Check last received keepalive - var diff = time.Since(client.keepaliveLast) - if diff > 2*time.Duration(client.keepalive)*time.Second { - logger.WithFields(log.Fields{ - "last keepalive": client.keepaliveLast, - "interval": time.Duration(client.keepalive) * time.Second, - }).Warn("No KEEPALIVE was received within expected time frame") - - // TODO: terminate session - } else { - logger.WithField("last keepalive", client.keepaliveLast).Debug( - "Received last KEEPALIVE within expected time frame") - } - - case <-client.keepaliveStopSyn: - close(client.keepaliveStopAck) - return - } - } - */ -} - // handleEstablished manges the established state. func (client *TCPCLClient) handleEstablished() error { if !client.keepaliveStarted { - // TODO - // go client.keepaliveHandler() - // client.keepaliveStarted = true + client.keepaliveTicker = time.NewTicker(time.Duration(client.keepalive) * time.Second) + client.keepaliveLast = time.Now() + client.keepaliveStarted = true } - msg := <-client.msgsIn - switch msg.(type) { - case *KeepaliveMessage: - keepaliveMsg := *msg.(*KeepaliveMessage) - client.keepaliveLast = time.Now() - client.log().WithField("msg", keepaliveMsg).Debug("Received KEEPALIVE message") + select { + case <-client.keepaliveTicker.C: + // Send a keepalive + var keepaliveMsg = NewKeepaliveMessage() + client.msgsOut <- &keepaliveMsg + client.log().WithField("msg", keepaliveMsg).Debug("Sent KEEPALIVE message") + + // Check last received keepalive + var diff = time.Since(client.keepaliveLast) + if diff > 2*time.Duration(client.keepalive)*time.Second { + client.log().WithFields(log.Fields{ + "last keepalive": client.keepaliveLast, + "interval": time.Duration(client.keepalive) * time.Second, + }).Warn("No KEEPALIVE was received within expected time frame") + + // TODO: terminate session + } else { + client.log().WithField("last keepalive", client.keepaliveLast).Debug( + "Received last KEEPALIVE within expected time frame") + } - case *SessionTerminationMessage: - sesstermMsg := *msg.(*SessionTerminationMessage) - client.log().WithField("msg", sesstermMsg).Info("Received SESS_TERM") - client.state.Terminate() + case msg := <-client.msgsIn: + switch msg.(type) { + case *KeepaliveMessage: + keepaliveMsg := *msg.(*KeepaliveMessage) + client.keepaliveLast = time.Now() + client.log().WithField("msg", keepaliveMsg).Debug("Received KEEPALIVE message") - case *DataTransmissionMessage: - dataTransMsg := *msg.(*DataTransmissionMessage) - client.log().WithField("msg", dataTransMsg).Info("Received XFER_SEGMENT") + case *SessionTerminationMessage: + sesstermMsg := *msg.(*SessionTerminationMessage) + client.log().WithField("msg", sesstermMsg).Info("Received SESS_TERM") + client.state.Terminate() - default: - client.log().WithField("msg", msg).Warn("Received unexpected message") + case *DataTransmissionMessage: + dataTransMsg := *msg.(*DataTransmissionMessage) + client.log().WithField("msg", dataTransMsg).Info("Received XFER_SEGMENT") + + default: + client.log().WithField("msg", msg).Warn("Received unexpected message") + } } return nil From 0ea17ee2994faeefc306f85fa956f75b3fa550ca Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 23 Sep 2019 17:37:17 +0200 Subject: [PATCH 27/53] TCPCL: Synchronize shutting down handlers --- cla/tcpcl/client.go | 85 +++++++++++++++++++++++---------- cla/tcpcl/client_established.go | 25 +++++++--- cla/tcpcl/client_init.go | 5 ++ cla/tcpcl/client_state.go | 5 ++ cla/tcpcl/client_termination.go | 15 ------ 5 files changed, 88 insertions(+), 47 deletions(-) delete mode 100644 cla/tcpcl/client_termination.go diff --git a/cla/tcpcl/client.go b/cla/tcpcl/client.go index 9181635c..96ab050c 100644 --- a/cla/tcpcl/client.go +++ b/cla/tcpcl/client.go @@ -2,9 +2,12 @@ package tcpcl import ( "bufio" + "errors" "fmt" + "io" "net" "strings" + "sync" "time" log "github.com/sirupsen/logrus" @@ -12,6 +15,9 @@ import ( "github.com/dtn7/dtn7-go/bundle" ) +// sessTermErr will be returned from a state handler iff a SESS_TERM was received. +var sessTermErr = errors.New("SESS_TERM received") + type TCPCLClient struct { address string endpointID bundle.EndpointID @@ -22,6 +28,9 @@ type TCPCLClient struct { msgsOut chan Message msgsIn chan Message + closed bool + closedWg sync.WaitGroup + active bool state ClientState @@ -100,6 +109,7 @@ func (client *TCPCLClient) Start() (err error, retry bool) { log.Info("Starting client") + client.closedWg.Add(2) go client.handleConnection() go client.handleState() @@ -108,8 +118,11 @@ func (client *TCPCLClient) Start() (err error, retry bool) { func (client *TCPCLClient) handleConnection() { defer func() { - // TODO client.log().Debug("Leaving connection handler function") + client.state.Terminate() + + client.closed = true + client.closedWg.Done() }() var rw = bufio.NewReadWriter(bufio.NewReader(client.conn), bufio.NewWriter(client.conn)) @@ -127,6 +140,15 @@ func (client *TCPCLClient) handleConnection() { client.log().WithField("msg", msg).Debug("Sent message") } + if _, ok := msg.(*SessionTerminationMessage); ok { + client.log().WithField("msg", msg).Debug("Closing connection after sending SESS_TERM") + + if err := client.conn.Close(); err != nil { + client.log().WithError(err).Warn("Failed to close TCP connection") + } + return + } + default: if err := client.conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)); err != nil { client.log().WithError(err).Error("Setting read deadline errored") @@ -136,11 +158,15 @@ func (client *TCPCLClient) handleConnection() { if msg, err := ReadMessage(rw); err == nil { client.log().WithField("msg", msg).Debug("Received message") client.msgsIn <- msg + } else if err == io.EOF { + client.log().Info("Read EOF, closing down.") + return } else if netErr, ok := err.(net.Error); ok && !netErr.Timeout() { client.log().WithError(netErr).Error("Network error occured") return } else if !ok { - client.log().WithError(err).Warn("Parsing next message errored") + client.log().WithError(err).Error("Parsing next message errored") + return } } } @@ -148,43 +174,51 @@ func (client *TCPCLClient) handleConnection() { func (client *TCPCLClient) handleState() { defer func() { - // TODO client.log().Debug("Leaving state handler function") + + client.closed = true + client.closedWg.Done() }() for { switch client.state { - case Contact: - if err := client.handleContact(); err != nil { - client.log().WithError(err).Warn("Error occured during contact header exchange") - - client.terminate(TerminationContactFailure) - return + case Contact, Init, Established: + var stateHandler func() error + + switch client.state { + case Contact: + stateHandler = client.handleContact + case Init: + stateHandler = client.handleSessInit + case Established: + stateHandler = client.handleEstablished } - case Init: - if err := client.handleSessInit(); err != nil { - client.log().WithError(err).Warn("Error occured during session initialization") + if err := stateHandler(); err != nil { + if err == sessTermErr { + client.log().Info("Received SESS_TERM, switching to Termination state") + } else { + client.log().WithError(err).Warn("State handler errored") + } - client.terminate(TerminationUnknown) - return + client.state.Terminate() + goto terminationCase } + break - case Established: - if err := client.handleEstablished(); err != nil { - client.log().WithError(err).Warn("Error occured during established session") - - client.terminate(TerminationUnknown) - return - } + terminationCase: + fallthrough case Termination: - // TODO - client.log().Debug("Entering Termination state") + client.log().Info("Entering Termination state") + + var sessTerm = NewSessionTerminationMessage(0, TerminationUnknown) + client.msgsOut <- &sessTerm - client.terminate(TerminationUnknown) + return + } - client.log().Info("rip in pieces") + if client.closed { return } } @@ -192,4 +226,5 @@ func (client *TCPCLClient) handleState() { func (client *TCPCLClient) Close() { client.state.Terminate() + client.closedWg.Wait() } diff --git a/cla/tcpcl/client_established.go b/cla/tcpcl/client_established.go index 79dda3f7..2df6ddb5 100644 --- a/cla/tcpcl/client_established.go +++ b/cla/tcpcl/client_established.go @@ -1,6 +1,7 @@ package tcpcl import ( + "fmt" "io" "time" @@ -12,7 +13,13 @@ import ( // This file contains code for the Client's established state. // handleEstablished manges the established state. -func (client *TCPCLClient) handleEstablished() error { +func (client *TCPCLClient) handleEstablished() (err error) { + defer func() { + if err != nil && client.keepaliveStarted { + client.keepaliveTicker.Stop() + } + }() + if !client.keepaliveStarted { client.keepaliveTicker = time.NewTicker(time.Duration(client.keepalive) * time.Second) client.keepaliveLast = time.Now() @@ -34,7 +41,7 @@ func (client *TCPCLClient) handleEstablished() error { "interval": time.Duration(client.keepalive) * time.Second, }).Warn("No KEEPALIVE was received within expected time frame") - // TODO: terminate session + return fmt.Errorf("No KEEPALIVE was received within expected time frame") } else { client.log().WithField("last keepalive", client.keepaliveLast).Debug( "Received last KEEPALIVE within expected time frame") @@ -47,18 +54,22 @@ func (client *TCPCLClient) handleEstablished() error { client.keepaliveLast = time.Now() client.log().WithField("msg", keepaliveMsg).Debug("Received KEEPALIVE message") - case *SessionTerminationMessage: - sesstermMsg := *msg.(*SessionTerminationMessage) - client.log().WithField("msg", sesstermMsg).Info("Received SESS_TERM") - client.state.Terminate() - case *DataTransmissionMessage: dataTransMsg := *msg.(*DataTransmissionMessage) client.log().WithField("msg", dataTransMsg).Info("Received XFER_SEGMENT") + case *SessionTerminationMessage: + sesstermMsg := *msg.(*SessionTerminationMessage) + client.log().WithField("msg", sesstermMsg).Info("Received SESS_TERM") + return sessTermErr + default: client.log().WithField("msg", msg).Warn("Received unexpected message") } + + case <-time.After(100 * time.Millisecond): + // This case prevents blocking. Otherwise the select statement would wait + // for the keepaliveTicker or an incoming message. } return nil diff --git a/cla/tcpcl/client_init.go b/cla/tcpcl/client_init.go index a1011e9a..c6c84ce1 100644 --- a/cla/tcpcl/client_init.go +++ b/cla/tcpcl/client_init.go @@ -35,6 +35,11 @@ func (client *TCPCLClient) handleSessInit() error { client.initRecv = true client.log().WithField("msg", client.sessInitRecv).Debug("Received SESS_INIT message") + case *SessionTerminationMessage: + sesstermMsg := *msg.(*SessionTerminationMessage) + client.log().WithField("msg", sesstermMsg).Info("Received SESS_TERM") + return sessTermErr + default: client.log().WithField("msg", msg).Warn("Received wrong message") return fmt.Errorf("Wrong message type") diff --git a/cla/tcpcl/client_state.go b/cla/tcpcl/client_state.go index db4f6a2d..aa8b2738 100644 --- a/cla/tcpcl/client_state.go +++ b/cla/tcpcl/client_state.go @@ -47,3 +47,8 @@ func (cs *ClientState) Next() { func (cs *ClientState) Terminate() { *cs = Termination } + +// IsTerminated checks if the ClientState is in a terminated state. +func (cs ClientState) IsTerminated() bool { + return cs == Termination +} diff --git a/cla/tcpcl/client_termination.go b/cla/tcpcl/client_termination.go deleted file mode 100644 index 3ee48563..00000000 --- a/cla/tcpcl/client_termination.go +++ /dev/null @@ -1,15 +0,0 @@ -package tcpcl - -// This file contains code for the Client's termination state. - -// terminate sends a SESS_TERM message to its peer and closes the session afterwards. -func (client *TCPCLClient) terminate(code SessionTerminationCode) { - var sessTerm = NewSessionTerminationMessage(0, code) - client.msgsOut <- &sessTerm - - if err := client.conn.Close(); err != nil { - client.log().WithError(err).Warn("Failed to close TCP connection") - } else { - client.log().Info("Terminated session") - } -} From ffce4b6b224445ed1d2a4f8cd64d35e0a6beb8fb Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Tue, 24 Sep 2019 13:34:41 +0200 Subject: [PATCH 28/53] TCPCL: Protect against race conditions --- cla/tcpcl/client.go | 46 ++++++++++++----------- cla/tcpcl/client_state.go | 78 ++++++++++++++++++++++++++++----------- 2 files changed, 81 insertions(+), 43 deletions(-) diff --git a/cla/tcpcl/client.go b/cla/tcpcl/client.go index 96ab050c..cebc5a28 100644 --- a/cla/tcpcl/client.go +++ b/cla/tcpcl/client.go @@ -7,7 +7,7 @@ import ( "io" "net" "strings" - "sync" + "sync/atomic" "time" log "github.com/sirupsen/logrus" @@ -28,11 +28,10 @@ type TCPCLClient struct { msgsOut chan Message msgsIn chan Message - closed bool - closedWg sync.WaitGroup + handleCounter int32 active bool - state ClientState + state *ClientState // Contact state fields: contactSent bool @@ -62,6 +61,7 @@ func NewTCPCLClient(conn net.Conn, endpointID bundle.EndpointID) *TCPCLClient { return &TCPCLClient{ conn: conn, active: false, + state: new(ClientState), msgsOut: make(chan Message, 100), msgsIn: make(chan Message), endpointID: endpointID, @@ -72,13 +72,14 @@ func Dial(address string, endpointID bundle.EndpointID) *TCPCLClient { return &TCPCLClient{ address: address, active: true, + state: new(ClientState), msgsOut: make(chan Message, 100), msgsIn: make(chan Message), endpointID: endpointID, } } -func (client TCPCLClient) String() string { +func (client *TCPCLClient) String() string { var b strings.Builder fmt.Fprintf(&b, "TCPCL(") @@ -90,9 +91,9 @@ func (client TCPCLClient) String() string { } // log prepares a new log entry with predefined session data. -func (client TCPCLClient) log() *log.Entry { +func (client *TCPCLClient) log() *log.Entry { return log.WithFields(log.Fields{ - "session": client.String(), + "session": client, "state": client.state, }) } @@ -107,9 +108,9 @@ func (client *TCPCLClient) Start() (err error, retry bool) { } } - log.Info("Starting client") + client.log().Info("Starting client") - client.closedWg.Add(2) + client.handleCounter = 2 go client.handleConnection() go client.handleState() @@ -121,8 +122,7 @@ func (client *TCPCLClient) handleConnection() { client.log().Debug("Leaving connection handler function") client.state.Terminate() - client.closed = true - client.closedWg.Done() + atomic.AddInt32(&client.handleCounter, -1) }() var rw = bufio.NewReadWriter(bufio.NewReader(client.conn), bufio.NewWriter(client.conn)) @@ -176,21 +176,20 @@ func (client *TCPCLClient) handleState() { defer func() { client.log().Debug("Leaving state handler function") - client.closed = true - client.closedWg.Done() + atomic.AddInt32(&client.handleCounter, -1) }() for { - switch client.state { - case Contact, Init, Established: + switch { + case !client.state.IsTerminated(): var stateHandler func() error - switch client.state { - case Contact: + switch { + case client.state.IsContact(): stateHandler = client.handleContact - case Init: + case client.state.IsInit(): stateHandler = client.handleSessInit - case Established: + case client.state.IsEstablished(): stateHandler = client.handleEstablished } @@ -209,7 +208,7 @@ func (client *TCPCLClient) handleState() { terminationCase: fallthrough - case Termination: + default: client.log().Info("Entering Termination state") var sessTerm = NewSessionTerminationMessage(0, TerminationUnknown) @@ -218,7 +217,7 @@ func (client *TCPCLClient) handleState() { return } - if client.closed { + if atomic.LoadInt32(&client.handleCounter) != 2 { return } } @@ -226,5 +225,8 @@ func (client *TCPCLClient) handleState() { func (client *TCPCLClient) Close() { client.state.Terminate() - client.closedWg.Wait() + + for atomic.LoadInt32(&client.handleCounter) > 0 { + time.Sleep(time.Millisecond) + } } diff --git a/cla/tcpcl/client_state.go b/cla/tcpcl/client_state.go index aa8b2738..7ac7eab9 100644 --- a/cla/tcpcl/client_state.go +++ b/cla/tcpcl/client_state.go @@ -1,35 +1,43 @@ package tcpcl +import "sync" + // ClientState describes the state of a TCPCL Client. Each Client can always // upgrade its state to a later one, but cannot go back to a previous state. // A transition can be made into the following or the termination state. -type ClientState int +type ClientState struct { + phase int + mutex sync.Mutex +} const ( - // Contact is the initial Contact Header exchange state, entered directly + // phaseContact is the initial Contact Header exchange state, entered directly // after a TCP connection was established. - Contact ClientState = iota + phaseContact int = iota - // Init is the SESS_INIT state. - Init ClientState = iota + // init is the SESS_INIT state. + phaseInit int = iota - // Established describes an established connection, allowing Bundles to be exchanged. - Established ClientState = iota + // phaseEstablished describes an established connection, allowing Bundles to be exchanged. + phaseEstablished int = iota - // Termination is the final state, entered when at least one client wants to + // phaseTermination is the final state, entered when at least one client wants to // terminate/close the session. - Termination ClientState = iota + phaseTermination int = iota ) -func (cs ClientState) String() string { - switch cs { - case Contact: +func (cs *ClientState) String() string { + cs.mutex.Lock() + defer cs.mutex.Unlock() + + switch cs.phase { + case phaseContact: return "contact" - case Init: + case phaseInit: return "initialization" - case Established: + case phaseEstablished: return "established" - case Termination: + case phaseTermination: return "termination" default: return "INVALID" @@ -38,17 +46,45 @@ func (cs ClientState) String() string { // Next enters the following ClientState. func (cs *ClientState) Next() { - if *cs != Termination { - *cs += 1 + cs.mutex.Lock() + defer cs.mutex.Unlock() + + if cs.phase != phaseTermination { + cs.phase += 1 } } // Terminate sets the ClientState into the termination state. func (cs *ClientState) Terminate() { - *cs = Termination + cs.mutex.Lock() + defer cs.mutex.Unlock() + + cs.phase = phaseTermination +} + +func (cs *ClientState) isPhase(phase int) bool { + cs.mutex.Lock() + defer cs.mutex.Unlock() + + return cs.phase == phase +} + +// IsContact checks if the ClientState is in the contact state. +func (cs *ClientState) IsContact() bool { + return cs.isPhase(phaseContact) +} + +// IsInit checks if the ClientState is in the initialization state. +func (cs *ClientState) IsInit() bool { + return cs.isPhase(phaseInit) +} + +// IsEstablished checks if the ClientState is in the established state. +func (cs *ClientState) IsEstablished() bool { + return cs.isPhase(phaseEstablished) } -// IsTerminated checks if the ClientState is in a terminated state. -func (cs ClientState) IsTerminated() bool { - return cs == Termination +// IsTerminated checks if the ClientState is in the terminated state. +func (cs *ClientState) IsTerminated() bool { + return cs.isPhase(phaseTermination) } From dfe7e1055ec40b154815333d4f90324f271df4b1 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Tue, 24 Sep 2019 14:33:18 +0200 Subject: [PATCH 29/53] TCPCL: Channel to link Send and handleEstablished --- cla/tcpcl/client.go | 33 ++++++++++++++-------- cla/tcpcl/client_established.go | 20 ++++++++++--- cla/tcpcl/{transfer.go => transfer_out.go} | 20 ++++++------- 3 files changed, 47 insertions(+), 26 deletions(-) rename cla/tcpcl/{transfer.go => transfer_out.go} (58%) diff --git a/cla/tcpcl/client.go b/cla/tcpcl/client.go index cebc5a28..5132bf59 100644 --- a/cla/tcpcl/client.go +++ b/cla/tcpcl/client.go @@ -7,6 +7,7 @@ import ( "io" "net" "strings" + "sync" "sync/atomic" "time" @@ -54,28 +55,36 @@ type TCPCLClient struct { keepaliveLast time.Time keepaliveTicker *time.Ticker + transferOutMutex sync.Mutex + transferOutSend chan Message + transferOutAck chan Message + transferIdOut uint64 } func NewTCPCLClient(conn net.Conn, endpointID bundle.EndpointID) *TCPCLClient { return &TCPCLClient{ - conn: conn, - active: false, - state: new(ClientState), - msgsOut: make(chan Message, 100), - msgsIn: make(chan Message), - endpointID: endpointID, + conn: conn, + active: false, + state: new(ClientState), + msgsOut: make(chan Message, 100), + msgsIn: make(chan Message), + transferOutSend: make(chan Message), + transferOutAck: make(chan Message), + endpointID: endpointID, } } func Dial(address string, endpointID bundle.EndpointID) *TCPCLClient { return &TCPCLClient{ - address: address, - active: true, - state: new(ClientState), - msgsOut: make(chan Message, 100), - msgsIn: make(chan Message), - endpointID: endpointID, + address: address, + active: true, + state: new(ClientState), + msgsOut: make(chan Message, 100), + msgsIn: make(chan Message), + transferOutSend: make(chan Message), + transferOutAck: make(chan Message), + endpointID: endpointID, } } diff --git a/cla/tcpcl/client_established.go b/cla/tcpcl/client_established.go index 2df6ddb5..bbe69dca 100644 --- a/cla/tcpcl/client_established.go +++ b/cla/tcpcl/client_established.go @@ -67,7 +67,16 @@ func (client *TCPCLClient) handleEstablished() (err error) { client.log().WithField("msg", msg).Warn("Received unexpected message") } - case <-time.After(100 * time.Millisecond): + case msg := <-client.transferOutSend: + if _, ok := msg.(*DataTransmissionMessage); !ok { + client.log().WithField("msg", msg).Warn( + "Transfer Out received a non XFER_SEGMENT message") + } else { + client.msgsOut <- msg + client.log().WithField("msg", msg).Debug("Forwarded XFER_SEGMENT") + } + + case <-time.After(time.Millisecond): // This case prevents blocking. Otherwise the select statement would wait // for the keepaliveTicker or an incoming message. } @@ -76,8 +85,11 @@ func (client *TCPCLClient) handleEstablished() (err error) { } func (client *TCPCLClient) Send(bndl *bundle.Bundle) error { + client.transferOutMutex.Lock() + defer client.transferOutMutex.Unlock() + client.transferIdOut += 1 - var t = NewBundleTransfer(client.transferIdOut, *bndl) + var t = NewBundleOutgoingTransfer(client.transferIdOut, *bndl) client.log().WithFields(log.Fields{ "bundle": bndl, @@ -95,7 +107,7 @@ func (client *TCPCLClient) Send(bndl *bundle.Bundle) error { return err } - client.msgsOut <- &dtm - client.log().WithField("msg", dtm).Debug("Sent XFER_SEGMENT") + client.transferOutSend <- &dtm + client.log().WithField("msg", dtm).Debug("Send disposed XFER_SEGMENT") } } diff --git a/cla/tcpcl/transfer.go b/cla/tcpcl/transfer_out.go similarity index 58% rename from cla/tcpcl/transfer.go rename to cla/tcpcl/transfer_out.go index 82d4f5be..46c2f1b4 100644 --- a/cla/tcpcl/transfer.go +++ b/cla/tcpcl/transfer_out.go @@ -8,18 +8,18 @@ import ( "github.com/dtn7/dtn7-go/bundle" ) -// Transfer represents a Bundle Transfer for the TCPCL. -type Transfer struct { +// OutgoingTransfer represents a Bundle OutgoingTransfer for the TCPCL. +type OutgoingTransfer struct { Id uint64 startFlag bool dataStream io.Reader } -// NewTransfer creates a new Transfer for data written into the returned Writer. -func NewTransfer(id uint64) (t *Transfer, w io.Writer) { +// NewOutgoingTransfer creates a new OutgoingTransfer for data written into the returned Writer. +func NewOutgoingTransfer(id uint64) (t *OutgoingTransfer, w io.Writer) { r, w := io.Pipe() - t = &Transfer{ + t = &OutgoingTransfer{ Id: id, startFlag: true, dataStream: r, @@ -28,13 +28,13 @@ func NewTransfer(id uint64) (t *Transfer, w io.Writer) { return } -func (t Transfer) String() string { +func (t OutgoingTransfer) String() string { return fmt.Sprintf("%d", t.Id) } -// NewBundleTransfer creates a new Transfer for a Bundle. -func NewBundleTransfer(id uint64, b bundle.Bundle) *Transfer { - var t, w = NewTransfer(id) +// NewBundleOutgoingTransfer creates a new OutgoingTransfer for a Bundle. +func NewBundleOutgoingTransfer(id uint64, b bundle.Bundle) *OutgoingTransfer { + var t, w = NewOutgoingTransfer(id) go func(w *io.PipeWriter) { bw := bufio.NewWriter(w) @@ -49,7 +49,7 @@ func NewBundleTransfer(id uint64, b bundle.Bundle) *Transfer { // NextSegment creates the next XFER_SEGMENT for the given MRU or an EOF in case // of a finished Writer. -func (t *Transfer) NextSegment(mru uint64) (dtm DataTransmissionMessage, err error) { +func (t *OutgoingTransfer) NextSegment(mru uint64) (dtm DataTransmissionMessage, err error) { var segFlags SegmentFlags if t.startFlag { From 11f9d8af4392209a1cbfe0f5397857bad7d61a06 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Tue, 24 Sep 2019 14:59:20 +0200 Subject: [PATCH 30/53] TCPCL: XFER_ACK for Transfers --- cla/tcpcl/client.go | 3 +-- cla/tcpcl/client_established.go | 40 ++++++++++++++++++++++++++------- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/cla/tcpcl/client.go b/cla/tcpcl/client.go index 5132bf59..7fc8fc9f 100644 --- a/cla/tcpcl/client.go +++ b/cla/tcpcl/client.go @@ -56,10 +56,9 @@ type TCPCLClient struct { keepaliveTicker *time.Ticker transferOutMutex sync.Mutex + transferOutId uint64 transferOutSend chan Message transferOutAck chan Message - - transferIdOut uint64 } func NewTCPCLClient(conn net.Conn, endpointID bundle.EndpointID) *TCPCLClient { diff --git a/cla/tcpcl/client_established.go b/cla/tcpcl/client_established.go index bbe69dca..cc7a372f 100644 --- a/cla/tcpcl/client_established.go +++ b/cla/tcpcl/client_established.go @@ -56,7 +56,15 @@ func (client *TCPCLClient) handleEstablished() (err error) { case *DataTransmissionMessage: dataTransMsg := *msg.(*DataTransmissionMessage) - client.log().WithField("msg", dataTransMsg).Info("Received XFER_SEGMENT") + client.log().WithField("msg", dataTransMsg).Debug("Received XFER_SEGMENT") + + // TODO: create correct ACK + ackMsg := NewDataAcknowledgementMessage(dataTransMsg.Flags, dataTransMsg.TransferId, 0) + client.msgsOut <- &ackMsg + client.log().WithField("msg", ackMsg).Debug("Sent XFER_ACK") + + case *DataAcknowledgementMessage, *TransferRefusalMessage: + client.transferOutAck <- msg case *SessionTerminationMessage: sesstermMsg := *msg.(*SessionTerminationMessage) @@ -88,26 +96,42 @@ func (client *TCPCLClient) Send(bndl *bundle.Bundle) error { client.transferOutMutex.Lock() defer client.transferOutMutex.Unlock() - client.transferIdOut += 1 - var t = NewBundleOutgoingTransfer(client.transferIdOut, *bndl) + client.transferOutId += 1 + var t = NewBundleOutgoingTransfer(client.transferOutId, *bndl) - client.log().WithFields(log.Fields{ + var tlog = client.log().WithFields(log.Fields{ "bundle": bndl, "transfer": t, - }).Info("Started Bundle Transfer") + }) + tlog.Info("Started Bundle Transfer") for { dtm, err := t.NextSegment(client.segmentMru) if err == io.EOF { - client.log().Info("Finished Transfer") + tlog.Info("Finished Transfer") return nil } else if err != nil { - client.log().WithError(err).Warn("Fetching Segment errored") + tlog.WithError(err).Warn("Fetching Segment errored") return err } client.transferOutSend <- &dtm - client.log().WithField("msg", dtm).Debug("Send disposed XFER_SEGMENT") + tlog.WithField("msg", dtm).Debug("Send disposed XFER_SEGMENT") + + ackMsg := <-client.transferOutAck + switch ackMsg.(type) { + case *DataAcknowledgementMessage: + tlog.WithField("msg", ackMsg).Debug("Received XFER_ACK") + // TODO: insepct ack + + case *TransferRefusalMessage: + tlog.WithField("msg", ackMsg).Warn("Received XFER_REFUSE, aborting transfer") + return fmt.Errorf("Received XFER_REFUSE, aborting transfer") + + default: + tlog.WithField("msg", ackMsg).Warn("Received wrong message type") + return fmt.Errorf("Received wrong message type") + } } } From c9665c1b4126ccdddfc72b7485de352231dbbb47 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Tue, 24 Sep 2019 15:59:02 +0200 Subject: [PATCH 31/53] TCPCL: Manage incoming Transfers --- cla/tcpcl/client.go | 2 ++ cla/tcpcl/client_established.go | 59 ++++++++++++++++++++++++++++----- cla/tcpcl/transfer_in.go | 54 ++++++++++++++++++++++++++++++ cla/tcpcl/transfer_out.go | 2 +- 4 files changed, 107 insertions(+), 10 deletions(-) create mode 100644 cla/tcpcl/transfer_in.go diff --git a/cla/tcpcl/client.go b/cla/tcpcl/client.go index 7fc8fc9f..a2ee8032 100644 --- a/cla/tcpcl/client.go +++ b/cla/tcpcl/client.go @@ -59,6 +59,8 @@ type TCPCLClient struct { transferOutId uint64 transferOutSend chan Message transferOutAck chan Message + + transferIn *IncomingTransfer } func NewTCPCLClient(conn net.Conn, endpointID bundle.EndpointID) *TCPCLClient { diff --git a/cla/tcpcl/client_established.go b/cla/tcpcl/client_established.go index cc7a372f..8e3617ce 100644 --- a/cla/tcpcl/client_established.go +++ b/cla/tcpcl/client_established.go @@ -55,13 +55,49 @@ func (client *TCPCLClient) handleEstablished() (err error) { client.log().WithField("msg", keepaliveMsg).Debug("Received KEEPALIVE message") case *DataTransmissionMessage: - dataTransMsg := *msg.(*DataTransmissionMessage) - client.log().WithField("msg", dataTransMsg).Debug("Received XFER_SEGMENT") - - // TODO: create correct ACK - ackMsg := NewDataAcknowledgementMessage(dataTransMsg.Flags, dataTransMsg.TransferId, 0) - client.msgsOut <- &ackMsg - client.log().WithField("msg", ackMsg).Debug("Sent XFER_ACK") + dtm := *msg.(*DataTransmissionMessage) + client.log().WithField("msg", dtm).Debug("Received XFER_SEGMENT") + + if client.transferIn != nil && dtm.Flags&SegmentStart != 0 { + client.log().WithField("msg", dtm).Warn( + "Received XFER_SEGMENT with START flag, but has old transfer; resetting") + + client.transferIn = NewIncomingTransfer(dtm.TransferId) + } else if client.transferIn == nil { + if dtm.Flags&SegmentStart == 0 { + client.log().WithField("msg", dtm).Warn( + "Received XFER_SEGMENT without a START flag, but no transfer state") + + ackMsg := NewTransferRefusalMessage(RefusalUnknown, dtm.TransferId) + client.msgsOut <- &ackMsg + } else { + client.log().WithField("msg", dtm).Debug("Create new incoming transfer") + + client.transferIn = NewIncomingTransfer(dtm.TransferId) + } + } + + if client.transferIn != nil { + if dam, err := client.transferIn.NextSegment(dtm); err != nil { + client.log().WithError(err).WithField("msg", dtm).Warn( + "Parsing next incoming segment errored") + + ackMsg := NewTransferRefusalMessage(RefusalUnknown, dtm.TransferId) + client.msgsOut <- &ackMsg + } else { + client.msgsOut <- &dam + client.log().WithField("msg", dam).Debug("Sent XFER_ACK") + } + + if client.transferIn.IsFinished() { + client.log().WithField("transfer", client.transferIn).Info( + "Finished incoming transfer") + + // TODO: extract and forward bundle + + client.transferIn = nil + } + } case *DataAcknowledgementMessage, *TransferRefusalMessage: client.transferOutAck <- msg @@ -122,8 +158,13 @@ func (client *TCPCLClient) Send(bndl *bundle.Bundle) error { ackMsg := <-client.transferOutAck switch ackMsg.(type) { case *DataAcknowledgementMessage: - tlog.WithField("msg", ackMsg).Debug("Received XFER_ACK") - // TODO: insepct ack + dam := ackMsg.(*DataAcknowledgementMessage) + tlog.WithField("msg", dam).Debug("Received XFER_ACK") + + if dam.TransferId != dtm.TransferId || dam.Flags != dtm.Flags { + tlog.WithField("msg", dam).Warn("XFER_ACK does not match XFER_SEGMENT") + return fmt.Errorf("XFER_ACK does not match XFER_SEGMENT") + } case *TransferRefusalMessage: tlog.WithField("msg", ackMsg).Warn("Received XFER_REFUSE, aborting transfer") diff --git a/cla/tcpcl/transfer_in.go b/cla/tcpcl/transfer_in.go new file mode 100644 index 00000000..f096524b --- /dev/null +++ b/cla/tcpcl/transfer_in.go @@ -0,0 +1,54 @@ +package tcpcl + +import ( + "bytes" + "fmt" + "io" +) + +type IncomingTransfer struct { + Id uint64 + + endFlag bool + buf *bytes.Buffer +} + +func NewIncomingTransfer(id uint64) *IncomingTransfer { + return &IncomingTransfer{ + Id: id, + buf: new(bytes.Buffer), + } +} + +func (t IncomingTransfer) String() string { + return fmt.Sprintf("INCOMING_TRANSFER(%d)", t.Id) +} + +func (t *IncomingTransfer) NextSegment(dtm DataTransmissionMessage) (dam DataAcknowledgementMessage, err error) { + if t.endFlag { + err = fmt.Errorf("Transfer has already received an end flag") + return + } + + if t.Id != dtm.TransferId { + err = fmt.Errorf("XFER_SEGMENT's Transfer ID %d mismatches %d", dtm.TransferId, t.Id) + return + } + + dtmReader := bytes.NewBuffer(dtm.Data) + if _, cpyErr := io.Copy(t.buf, dtmReader); cpyErr != nil { + err = cpyErr + return + } + + if dtm.Flags&SegmentEnd != 0 { + t.endFlag = true + } + + dam = NewDataAcknowledgementMessage(dtm.Flags, dtm.TransferId, uint64(t.buf.Len())) + return +} + +func (t *IncomingTransfer) IsFinished() bool { + return t.endFlag +} diff --git a/cla/tcpcl/transfer_out.go b/cla/tcpcl/transfer_out.go index 46c2f1b4..e43f2657 100644 --- a/cla/tcpcl/transfer_out.go +++ b/cla/tcpcl/transfer_out.go @@ -29,7 +29,7 @@ func NewOutgoingTransfer(id uint64) (t *OutgoingTransfer, w io.Writer) { } func (t OutgoingTransfer) String() string { - return fmt.Sprintf("%d", t.Id) + return fmt.Sprintf("OUTGOING_TRANSFER(%d)", t.Id) } // NewBundleOutgoingTransfer creates a new OutgoingTransfer for a Bundle. From 5a94f00799d92f9f264666ca87625a048dd8b6bf Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Tue, 24 Sep 2019 16:18:39 +0200 Subject: [PATCH 32/53] TCPCL: Deserialize Bundle from incoming Transfer --- cla/tcpcl/client_established.go | 11 ++++++++++- cla/tcpcl/transfer_in.go | 23 ++++++++++++++++++++--- cla/tcpcl/transfer_out.go | 2 +- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/cla/tcpcl/client_established.go b/cla/tcpcl/client_established.go index 8e3617ce..0f860699 100644 --- a/cla/tcpcl/client_established.go +++ b/cla/tcpcl/client_established.go @@ -93,7 +93,16 @@ func (client *TCPCLClient) handleEstablished() (err error) { client.log().WithField("transfer", client.transferIn).Info( "Finished incoming transfer") - // TODO: extract and forward bundle + if bndl, err := client.transferIn.ToBundle(); err != nil { + client.log().WithError(err).WithField("transfer", client.transferIn).Warn( + "Unmarshalling Bundle errored") + } else { + client.log().WithFields(log.Fields{ + "transfer": client.transferIn, + "bundle": bndl, + }).Info("Received Bundle") + } + // TODO: forward bundle client.transferIn = nil } diff --git a/cla/tcpcl/transfer_in.go b/cla/tcpcl/transfer_in.go index f096524b..ad338fd5 100644 --- a/cla/tcpcl/transfer_in.go +++ b/cla/tcpcl/transfer_in.go @@ -4,8 +4,11 @@ import ( "bytes" "fmt" "io" + + "github.com/dtn7/dtn7-go/bundle" ) +// IncomingTransfer represents an incoming Bundle Transfer for the TCPCL. type IncomingTransfer struct { Id uint64 @@ -13,6 +16,7 @@ type IncomingTransfer struct { buf *bytes.Buffer } +// NewIncomingTransfer creates a new IncomingTransfer for the given Transfer ID. func NewIncomingTransfer(id uint64) *IncomingTransfer { return &IncomingTransfer{ Id: id, @@ -24,8 +28,14 @@ func (t IncomingTransfer) String() string { return fmt.Sprintf("INCOMING_TRANSFER(%d)", t.Id) } +// IsFinished indicates if this Transfer is finished. +func (t IncomingTransfer) IsFinished() bool { + return t.endFlag +} + +// NextSegment reads data from a XFER_SEGMENT and retruns a XFER_ACK or an error. func (t *IncomingTransfer) NextSegment(dtm DataTransmissionMessage) (dam DataAcknowledgementMessage, err error) { - if t.endFlag { + if t.IsFinished() { err = fmt.Errorf("Transfer has already received an end flag") return } @@ -49,6 +59,13 @@ func (t *IncomingTransfer) NextSegment(dtm DataTransmissionMessage) (dam DataAck return } -func (t *IncomingTransfer) IsFinished() bool { - return t.endFlag +// ToBundle returns the Bundle for a finished Transfer. +func (t *IncomingTransfer) ToBundle() (bndl bundle.Bundle, err error) { + if !t.IsFinished() { + err = fmt.Errorf("Transfer has not been finished") + return + } + + err = bndl.UnmarshalCbor(t.buf) + return } diff --git a/cla/tcpcl/transfer_out.go b/cla/tcpcl/transfer_out.go index e43f2657..ab6fced9 100644 --- a/cla/tcpcl/transfer_out.go +++ b/cla/tcpcl/transfer_out.go @@ -8,7 +8,7 @@ import ( "github.com/dtn7/dtn7-go/bundle" ) -// OutgoingTransfer represents a Bundle OutgoingTransfer for the TCPCL. +// OutgoingTransfer represents an outgoing Bundle Transfer for the TCPCL. type OutgoingTransfer struct { Id uint64 From 58313450a35dc1fce18614cccca502ebd5675776 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 7 Oct 2019 10:13:53 +0200 Subject: [PATCH 33/53] TCPCL: TCPCLient is Convergence{Receiver,Sender} --- cla/tcpcl/client.go | 31 ++++++++++++++++++++++++++++++- cla/tcpcl/client_established.go | 5 ++++- cla/tcpcl/server.go | 31 +++++++++++++------------------ 3 files changed, 47 insertions(+), 20 deletions(-) diff --git a/cla/tcpcl/client.go b/cla/tcpcl/client.go index a2ee8032..faf1c13c 100644 --- a/cla/tcpcl/client.go +++ b/cla/tcpcl/client.go @@ -14,6 +14,7 @@ import ( log "github.com/sirupsen/logrus" "github.com/dtn7/dtn7-go/bundle" + "github.com/dtn7/dtn7-go/cla" ) // sessTermErr will be returned from a state handler iff a SESS_TERM was received. @@ -21,6 +22,7 @@ var sessTermErr = errors.New("SESS_TERM received") type TCPCLClient struct { address string + permanent bool endpointID bundle.EndpointID peerEndpointID bundle.EndpointID @@ -61,10 +63,14 @@ type TCPCLClient struct { transferOutAck chan Message transferIn *IncomingTransfer + + reportChan chan cla.ConvergenceStatus } func NewTCPCLClient(conn net.Conn, endpointID bundle.EndpointID) *TCPCLClient { + address, _, _ := net.SplitHostPort(conn.RemoteAddr().String()) return &TCPCLClient{ + address: address, conn: conn, active: false, state: new(ClientState), @@ -76,9 +82,10 @@ func NewTCPCLClient(conn net.Conn, endpointID bundle.EndpointID) *TCPCLClient { } } -func Dial(address string, endpointID bundle.EndpointID) *TCPCLClient { +func Dial(address string, endpointID bundle.EndpointID, permanent bool) *TCPCLClient { return &TCPCLClient{ address: address, + permanent: permanent, active: true, state: new(ClientState), msgsOut: make(chan Message, 100), @@ -120,6 +127,8 @@ func (client *TCPCLClient) Start() (err error, retry bool) { client.log().Info("Starting client") + client.reportChan = make(chan cla.ConvergenceStatus) + client.handleCounter = 2 go client.handleConnection() go client.handleState() @@ -240,3 +249,23 @@ func (client *TCPCLClient) Close() { time.Sleep(time.Millisecond) } } + +func (client *TCPCLClient) Channel() chan cla.ConvergenceStatus { + return client.reportChan +} + +func (client *TCPCLClient) Address() string { + return client.address +} + +func (client *TCPCLClient) IsPermanent() bool { + return client.permanent +} + +func (client *TCPCLClient) GetEndpointID() bundle.EndpointID { + return client.endpointID +} + +func (client *TCPCLClient) GetPeerEndpointID() bundle.EndpointID { + return client.peerEndpointID +} diff --git a/cla/tcpcl/client_established.go b/cla/tcpcl/client_established.go index 0f860699..79876185 100644 --- a/cla/tcpcl/client_established.go +++ b/cla/tcpcl/client_established.go @@ -8,6 +8,7 @@ import ( log "github.com/sirupsen/logrus" "github.com/dtn7/dtn7-go/bundle" + "github.com/dtn7/dtn7-go/cla" ) // This file contains code for the Client's established state. @@ -101,8 +102,10 @@ func (client *TCPCLClient) handleEstablished() (err error) { "transfer": client.transferIn, "bundle": bndl, }).Info("Received Bundle") + + client.reportChan <- cla.NewConvergenceReceivedBundle( + client, client.endpointID, &bndl) } - // TODO: forward bundle client.transferIn = nil } diff --git a/cla/tcpcl/server.go b/cla/tcpcl/server.go index 820703d1..843eee7a 100644 --- a/cla/tcpcl/server.go +++ b/cla/tcpcl/server.go @@ -15,7 +15,6 @@ type TCPCLServer struct { listenAddress string reportChan chan cla.ConvergenceStatus endpointID bundle.EndpointID - permanent bool stopSyn chan struct{} stopAck chan struct{} @@ -26,7 +25,6 @@ func NewTCPCLServer(listenAddress string, endpointID bundle.EndpointID, permanen listenAddress: listenAddress, reportChan: make(chan cla.ConvergenceStatus), endpointID: endpointID, - permanent: permanent, stopSyn: make(chan struct{}), stopAck: make(chan struct{}), } @@ -62,9 +60,18 @@ func (serv *TCPCLServer) Start() (error, bool) { serv.Close() } else if conn, err := ln.Accept(); err == nil { - // TODO - client := NewTCPCLClient(conn, serv.endpointID) - _, _ = client.Start() + go func() { + client := NewTCPCLClient(conn, serv.endpointID) + + if err, _ := client.Start(); err != nil { + log.WithError(err).WithField("cla", serv).Warn("Starting Client errored") + return + } + + for { + serv.reportChan <- <-client.Channel() + } + }() } } } @@ -82,18 +89,6 @@ func (serv *TCPCLServer) Close() { <-serv.stopAck } -func (serv TCPCLServer) GetEndpointID() bundle.EndpointID { - return serv.endpointID -} - -func (serv TCPCLServer) Address() string { - return fmt.Sprintf("tcpcl://%s", serv.listenAddress) -} - -func (serv TCPCLServer) IsPermanent() bool { - return serv.permanent -} - func (serv TCPCLServer) String() string { - return serv.Address() + return fmt.Sprintf("tcpcl://%s", serv.listenAddress) } From 08710ef65495f5db28bb597c85ccee5518b20718 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 7 Oct 2019 12:37:38 +0200 Subject: [PATCH 34/53] TCPCL: Handle TCPCLClient's ConvergenceMessages --- cla/tcpcl/client.go | 2 ++ cla/tcpcl/client_established.go | 4 ++++ cla/tcpcl/client_init.go | 3 +++ 3 files changed, 9 insertions(+) diff --git a/cla/tcpcl/client.go b/cla/tcpcl/client.go index faf1c13c..d30e3698 100644 --- a/cla/tcpcl/client.go +++ b/cla/tcpcl/client.go @@ -233,6 +233,8 @@ func (client *TCPCLClient) handleState() { var sessTerm = NewSessionTerminationMessage(0, TerminationUnknown) client.msgsOut <- &sessTerm + client.reportChan <- cla.NewConvergencePeerDisappeared(client, client.peerEndpointID) + return } diff --git a/cla/tcpcl/client_established.go b/cla/tcpcl/client_established.go index 79876185..aaca2e29 100644 --- a/cla/tcpcl/client_established.go +++ b/cla/tcpcl/client_established.go @@ -144,6 +144,10 @@ func (client *TCPCLClient) Send(bndl *bundle.Bundle) error { client.transferOutMutex.Lock() defer client.transferOutMutex.Unlock() + if !client.state.IsEstablished() { + return fmt.Errorf("TCPCLClient is not in an established state") + } + client.transferOutId += 1 var t = NewBundleOutgoingTransfer(client.transferOutId, *bndl) diff --git a/cla/tcpcl/client_init.go b/cla/tcpcl/client_init.go index c6c84ce1..001a45c8 100644 --- a/cla/tcpcl/client_init.go +++ b/cla/tcpcl/client_init.go @@ -6,6 +6,7 @@ import ( log "github.com/sirupsen/logrus" "github.com/dtn7/dtn7-go/bundle" + "github.com/dtn7/dtn7-go/cla" ) // This file contains code for the Client's contact state. @@ -72,6 +73,8 @@ func (client *TCPCLClient) handleSessInit() error { "transfer MRU": client.transferMru, }).Debug("Exchanged SESS_INIT messages") client.state.Next() + + client.reportChan <- cla.NewConvergencePeerAppeared(client, client.peerEndpointID) } return nil From 7b65132cb7f69a44142cc198efb5e7bbe1dde806 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 7 Oct 2019 12:37:58 +0200 Subject: [PATCH 35/53] TCPCL: Network test --- cla/tcpcl/network_test.go | 139 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 cla/tcpcl/network_test.go diff --git a/cla/tcpcl/network_test.go b/cla/tcpcl/network_test.go new file mode 100644 index 00000000..c5510a53 --- /dev/null +++ b/cla/tcpcl/network_test.go @@ -0,0 +1,139 @@ +package tcpcl + +import ( + "fmt" + "net" + "sync" + "testing" + "time" + + log "github.com/sirupsen/logrus" + + "github.com/dtn7/dtn7-go/bundle" + "github.com/dtn7/dtn7-go/cla" +) + +func getRandomPort(t *testing.T) int { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + if err != nil { + t.Fatal(err) + } + + l, err := net.ListenTCP("tcp", addr) + if err != nil { + t.Fatal(err) + } + + defer l.Close() + return l.Addr().(*net.TCPAddr).Port +} + +func handleServer(serverAddr string, wg *sync.WaitGroup, errs chan error) { + serv := NewTCPCLServer(serverAddr, bundle.MustNewEndpointID("dtn://server/"), true) + if err, _ := serv.Start(); err != nil { + errs <- err + return + } + + go func(serv *TCPCLServer) { + for { + <-serv.Channel() + } + }(serv) + + wg.Wait() +} + +func handleClient(serverAddr string, clientNo, msgs int, wg *sync.WaitGroup, errs chan error) { + defer wg.Done() + + clientEid := fmt.Sprintf("dtn://client-%d/", clientNo) + client := Dial(serverAddr, bundle.MustNewEndpointID(clientEid), false) + if err, _ := client.Start(); err != nil { + errs <- err + return + } + + var clientWg sync.WaitGroup + clientWg.Add(1) + + go func(client cla.Convergence) { + for { + <-client.Channel() + } + }(client) + + go func(client *TCPCLClient, clientEid string, msgs int, clientWg *sync.WaitGroup, errs chan error) { + defer clientWg.Done() + + for !client.state.IsEstablished() { + } + + for i := 0; i < msgs; i++ { + bndl, err := bundle.Builder(). + CRC(bundle.CRC32). + Source(clientEid). + Destination("dtn://server/"). + CreationTimestampNow(). + Lifetime("30m"). + HopCountBlock(64). + PayloadBlock([]byte("hello world!")). + Build() + + if err != nil { + errs <- err + return + } else if err := client.Send(&bndl); err != nil { + errs <- err + return + } + } + }(client, clientEid, msgs, wg, errs) + + clientWg.Wait() + client.Close() +} + +func startTestTCPCLNetwork(msgs, clients int, t *testing.T) { + log.SetLevel(log.DebugLevel) + + var serverAddr = fmt.Sprintf("localhost:%d", getRandomPort(t)) + var errs = make(chan error) + var wg sync.WaitGroup + + wg.Add(clients) + + go handleServer(serverAddr, &wg, errs) + time.Sleep(100 * time.Millisecond) + + for i := 0; i < clients; i++ { + go handleClient(serverAddr, i, msgs, &wg, errs) + } + + go func(wg *sync.WaitGroup, errs chan error) { + wg.Wait() + close(errs) + }(&wg, errs) + + for err := range errs { + if err != nil { + t.Fatal(err) + } + } +} + +func TestTCPCLNetwork(t *testing.T) { + tests := []struct { + clients int + msgs int + }{{clients: 1, msgs: 1}, + {clients: 1, msgs: 25}, + {clients: 5, msgs: 25}, + {clients: 10, msgs: 25}} + + for _, test := range tests { + t.Run(fmt.Sprintf("%d_clients_%d_msgs", test.clients, test.msgs), func(t *testing.T) { + startTestTCPCLNetwork(test.msgs, test.clients, t) + }) + } +} From ff218ffc4c4a23b13e1063935f0c8688c37fe235 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 7 Oct 2019 14:42:14 +0200 Subject: [PATCH 36/53] TCPCL: Count messages in tests --- cla/tcpcl/network_test.go | 67 +++++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/cla/tcpcl/network_test.go b/cla/tcpcl/network_test.go index c5510a53..078e4fa6 100644 --- a/cla/tcpcl/network_test.go +++ b/cla/tcpcl/network_test.go @@ -4,6 +4,7 @@ import ( "fmt" "net" "sync" + "sync/atomic" "testing" "time" @@ -28,20 +29,51 @@ func getRandomPort(t *testing.T) int { return l.Addr().(*net.TCPAddr).Port } -func handleServer(serverAddr string, wg *sync.WaitGroup, errs chan error) { +func handleServer(serverAddr string, msgs, clients int, clientWg, serverWg *sync.WaitGroup, errs chan error) { + var ( + msgsRecv uint32 + msgsDsprd uint32 + msgsApprd uint32 + ) + + defer serverWg.Done() + serv := NewTCPCLServer(serverAddr, bundle.MustNewEndpointID("dtn://server/"), true) if err, _ := serv.Start(); err != nil { errs <- err return } - go func(serv *TCPCLServer) { + go func() { for { - <-serv.Channel() + switch cs := <-serv.Channel(); cs.MessageType { + case cla.ReceivedBundle: + atomic.AddUint32(&msgsRecv, 1) + + case cla.PeerDisappeared: + atomic.AddUint32(&msgsDsprd, 1) + + case cla.PeerAppeared: + atomic.AddUint32(&msgsApprd, 1) + } } - }(serv) + }() + + clientWg.Wait() + // TODO + // serv.Close() - wg.Wait() + time.Sleep(250 * time.Millisecond) + + if r := atomic.LoadUint32(&msgsRecv); r != uint32(msgs*clients) { + errs <- fmt.Errorf("Server received %d messages instead of %d", r, msgs*clients) + } + if d := atomic.LoadUint32(&msgsDsprd); d != uint32(clients) { + errs <- fmt.Errorf("Server received %d disappeared peers instead of %d", d, clients) + } + if a := atomic.LoadUint32(&msgsApprd); a != uint32(clients) { + errs <- fmt.Errorf("Server received %d appeared peers instead of %d", a, clients) + } } func handleClient(serverAddr string, clientNo, msgs int, wg *sync.WaitGroup, errs chan error) { @@ -57,13 +89,13 @@ func handleClient(serverAddr string, clientNo, msgs int, wg *sync.WaitGroup, err var clientWg sync.WaitGroup clientWg.Add(1) - go func(client cla.Convergence) { + go func() { for { <-client.Channel() } - }(client) + }() - go func(client *TCPCLClient, clientEid string, msgs int, clientWg *sync.WaitGroup, errs chan error) { + go func() { defer clientWg.Done() for !client.state.IsEstablished() { @@ -88,7 +120,7 @@ func handleClient(serverAddr string, clientNo, msgs int, wg *sync.WaitGroup, err return } } - }(client, clientEid, msgs, wg, errs) + }() clientWg.Wait() client.Close() @@ -99,21 +131,24 @@ func startTestTCPCLNetwork(msgs, clients int, t *testing.T) { var serverAddr = fmt.Sprintf("localhost:%d", getRandomPort(t)) var errs = make(chan error) - var wg sync.WaitGroup - wg.Add(clients) + var clientWg sync.WaitGroup + var serverWg sync.WaitGroup + + clientWg.Add(clients) + serverWg.Add(1) - go handleServer(serverAddr, &wg, errs) + go handleServer(serverAddr, msgs, clients, &clientWg, &serverWg, errs) time.Sleep(100 * time.Millisecond) for i := 0; i < clients; i++ { - go handleClient(serverAddr, i, msgs, &wg, errs) + go handleClient(serverAddr, i, msgs, &clientWg, errs) } - go func(wg *sync.WaitGroup, errs chan error) { - wg.Wait() + go func() { + serverWg.Wait() close(errs) - }(&wg, errs) + }() for err := range errs { if err != nil { From 4a40fa58e328184c31c6b53cd213f5db3361a9b7 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 7 Oct 2019 16:06:38 +0200 Subject: [PATCH 37/53] TCPCL: Server to Listener, bound to cla.Manager --- cla/tcpcl/client.go | 17 ++++++- cla/tcpcl/listener.go | 77 ++++++++++++++++++++++++++++++++ cla/tcpcl/network_test.go | 7 +-- cla/tcpcl/server.go | 94 --------------------------------------- 4 files changed, 96 insertions(+), 99 deletions(-) create mode 100644 cla/tcpcl/listener.go delete mode 100644 cla/tcpcl/server.go diff --git a/cla/tcpcl/client.go b/cla/tcpcl/client.go index d30e3698..f37490fe 100644 --- a/cla/tcpcl/client.go +++ b/cla/tcpcl/client.go @@ -22,6 +22,7 @@ var sessTermErr = errors.New("SESS_TERM received") type TCPCLClient struct { address string + started bool permanent bool endpointID bundle.EndpointID peerEndpointID bundle.EndpointID @@ -68,9 +69,8 @@ type TCPCLClient struct { } func NewTCPCLClient(conn net.Conn, endpointID bundle.EndpointID) *TCPCLClient { - address, _, _ := net.SplitHostPort(conn.RemoteAddr().String()) return &TCPCLClient{ - address: address, + address: conn.RemoteAddr().String(), conn: conn, active: false, state: new(ClientState), @@ -116,12 +116,25 @@ func (client *TCPCLClient) log() *log.Entry { } func (client *TCPCLClient) Start() (err error, retry bool) { + if client.started { + if client.active { + client.conn = nil + } else { + err = fmt.Errorf("Passive client cannot be restarted") + retry = false + return + } + } + + client.started = true + if client.conn == nil { if conn, connErr := net.DialTimeout("tcp", client.address, time.Second); connErr != nil { err = connErr return } else { client.conn = conn + client.address = conn.RemoteAddr().String() } } diff --git a/cla/tcpcl/listener.go b/cla/tcpcl/listener.go new file mode 100644 index 00000000..19480b71 --- /dev/null +++ b/cla/tcpcl/listener.go @@ -0,0 +1,77 @@ +package tcpcl + +import ( + "fmt" + "net" + "time" + + log "github.com/sirupsen/logrus" + + "github.com/dtn7/dtn7-go/bundle" + "github.com/dtn7/dtn7-go/cla" +) + +type TCPCLListener struct { + listenAddress string + endpointID bundle.EndpointID + manager *cla.Manager + + stopSyn chan struct{} + stopAck chan struct{} +} + +func NewTCPCLListener(listenAddress string, endpointID bundle.EndpointID, manager *cla.Manager) *TCPCLListener { + return &TCPCLListener{ + listenAddress: listenAddress, + endpointID: endpointID, + manager: manager, + + stopSyn: make(chan struct{}), + stopAck: make(chan struct{}), + } +} + +func (listener *TCPCLListener) Start() error { + tcpAddr, err := net.ResolveTCPAddr("tcp", listener.listenAddress) + if err != nil { + return err + } + + ln, err := net.ListenTCP("tcp", tcpAddr) + if err != nil { + return err + } + + go func(ln *net.TCPListener) { + for { + select { + case <-listener.stopSyn: + ln.Close() + close(listener.stopAck) + + return + + default: + if err := ln.SetDeadline(time.Now().Add(50 * time.Millisecond)); err != nil { + log.WithError(err).WithField("cla", listener).Warn( + "TCPCLListener failed to set deadline on TCP socket") + + listener.Close() + } else if conn, err := ln.Accept(); err == nil { + listener.manager.Register(NewTCPCLClient(conn, listener.endpointID)) + } + } + } + }(ln) + + return nil +} + +func (listener *TCPCLListener) Close() { + close(listener.stopSyn) + <-listener.stopAck +} + +func (listener TCPCLListener) String() string { + return fmt.Sprintf("tcpcl://%s", listener.listenAddress) +} diff --git a/cla/tcpcl/network_test.go b/cla/tcpcl/network_test.go index 078e4fa6..126f822b 100644 --- a/cla/tcpcl/network_test.go +++ b/cla/tcpcl/network_test.go @@ -38,15 +38,16 @@ func handleServer(serverAddr string, msgs, clients int, clientWg, serverWg *sync defer serverWg.Done() - serv := NewTCPCLServer(serverAddr, bundle.MustNewEndpointID("dtn://server/"), true) - if err, _ := serv.Start(); err != nil { + manager := cla.NewManager() + listener := NewTCPCLListener(serverAddr, bundle.MustNewEndpointID("dtn://server/"), manager) + if err := listener.Start(); err != nil { errs <- err return } go func() { for { - switch cs := <-serv.Channel(); cs.MessageType { + switch cs := <-manager.Channel(); cs.MessageType { case cla.ReceivedBundle: atomic.AddUint32(&msgsRecv, 1) diff --git a/cla/tcpcl/server.go b/cla/tcpcl/server.go deleted file mode 100644 index 843eee7a..00000000 --- a/cla/tcpcl/server.go +++ /dev/null @@ -1,94 +0,0 @@ -package tcpcl - -import ( - "fmt" - "net" - "time" - - log "github.com/sirupsen/logrus" - - "github.com/dtn7/dtn7-go/bundle" - "github.com/dtn7/dtn7-go/cla" -) - -type TCPCLServer struct { - listenAddress string - reportChan chan cla.ConvergenceStatus - endpointID bundle.EndpointID - - stopSyn chan struct{} - stopAck chan struct{} -} - -func NewTCPCLServer(listenAddress string, endpointID bundle.EndpointID, permanent bool) *TCPCLServer { - return &TCPCLServer{ - listenAddress: listenAddress, - reportChan: make(chan cla.ConvergenceStatus), - endpointID: endpointID, - stopSyn: make(chan struct{}), - stopAck: make(chan struct{}), - } -} - -func (serv *TCPCLServer) Start() (error, bool) { - tcpAddr, err := net.ResolveTCPAddr("tcp", serv.listenAddress) - if err != nil { - return err, false - } - - ln, err := net.ListenTCP("tcp", tcpAddr) - if err != nil { - return err, true - } - - go func(ln *net.TCPListener) { - for { - select { - case <-serv.stopSyn: - ln.Close() - close(serv.reportChan) - close(serv.stopAck) - - return - - default: - if err := ln.SetDeadline(time.Now().Add(50 * time.Millisecond)); err != nil { - log.WithFields(log.Fields{ - "cla": serv, - "error": err, - }).Warn("TCPCLServer failed to set deadline on TCP socket") - - serv.Close() - } else if conn, err := ln.Accept(); err == nil { - go func() { - client := NewTCPCLClient(conn, serv.endpointID) - - if err, _ := client.Start(); err != nil { - log.WithError(err).WithField("cla", serv).Warn("Starting Client errored") - return - } - - for { - serv.reportChan <- <-client.Channel() - } - }() - } - } - } - }(ln) - - return nil, true -} - -func (serv *TCPCLServer) Channel() chan cla.ConvergenceStatus { - return serv.reportChan -} - -func (serv *TCPCLServer) Close() { - close(serv.stopSyn) - <-serv.stopAck -} - -func (serv TCPCLServer) String() string { - return fmt.Sprintf("tcpcl://%s", serv.listenAddress) -} From 3de7939366b9de0f0e0320556c575c07e307e71e Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 7 Oct 2019 16:40:04 +0200 Subject: [PATCH 38/53] TCPCL: Closing Listener will also close Clients --- cla/tcpcl/listener.go | 9 ++++++++- cla/tcpcl/network_test.go | 13 ++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/cla/tcpcl/listener.go b/cla/tcpcl/listener.go index 19480b71..cb1ac62b 100644 --- a/cla/tcpcl/listener.go +++ b/cla/tcpcl/listener.go @@ -15,6 +15,7 @@ type TCPCLListener struct { listenAddress string endpointID bundle.EndpointID manager *cla.Manager + clas []cla.Convergence stopSyn chan struct{} stopAck chan struct{} @@ -46,6 +47,10 @@ func (listener *TCPCLListener) Start() error { for { select { case <-listener.stopSyn: + for _, c := range listener.clas { + listener.manager.Unregister(c) + } + ln.Close() close(listener.stopAck) @@ -58,7 +63,9 @@ func (listener *TCPCLListener) Start() error { listener.Close() } else if conn, err := ln.Accept(); err == nil { - listener.manager.Register(NewTCPCLClient(conn, listener.endpointID)) + client := NewTCPCLClient(conn, listener.endpointID) + listener.clas = append(listener.clas, client) + listener.manager.Register(client) } } } diff --git a/cla/tcpcl/network_test.go b/cla/tcpcl/network_test.go index 126f822b..710439bd 100644 --- a/cla/tcpcl/network_test.go +++ b/cla/tcpcl/network_test.go @@ -29,7 +29,7 @@ func getRandomPort(t *testing.T) int { return l.Addr().(*net.TCPAddr).Port } -func handleServer(serverAddr string, msgs, clients int, clientWg, serverWg *sync.WaitGroup, errs chan error) { +func handleListener(serverAddr string, msgs, clients int, clientWg, serverWg *sync.WaitGroup, errs chan error) { var ( msgsRecv uint32 msgsDsprd uint32 @@ -61,19 +61,18 @@ func handleServer(serverAddr string, msgs, clients int, clientWg, serverWg *sync }() clientWg.Wait() - // TODO - // serv.Close() + listener.Close() time.Sleep(250 * time.Millisecond) if r := atomic.LoadUint32(&msgsRecv); r != uint32(msgs*clients) { - errs <- fmt.Errorf("Server received %d messages instead of %d", r, msgs*clients) + errs <- fmt.Errorf("Listener received %d messages instead of %d", r, msgs*clients) } if d := atomic.LoadUint32(&msgsDsprd); d != uint32(clients) { - errs <- fmt.Errorf("Server received %d disappeared peers instead of %d", d, clients) + errs <- fmt.Errorf("Listener received %d disappeared peers instead of %d", d, clients) } if a := atomic.LoadUint32(&msgsApprd); a != uint32(clients) { - errs <- fmt.Errorf("Server received %d appeared peers instead of %d", a, clients) + errs <- fmt.Errorf("Listener received %d appeared peers instead of %d", a, clients) } } @@ -139,7 +138,7 @@ func startTestTCPCLNetwork(msgs, clients int, t *testing.T) { clientWg.Add(clients) serverWg.Add(1) - go handleServer(serverAddr, msgs, clients, &clientWg, &serverWg, errs) + go handleListener(serverAddr, msgs, clients, &clientWg, &serverWg, errs) time.Sleep(100 * time.Millisecond) for i := 0; i < clients; i++ { From c59c6977c5c384e26f963c76f9dfc8f895a53e98 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 7 Oct 2019 20:17:27 +0200 Subject: [PATCH 39/53] TCPCL: Send bundles bidirectionally for tests --- cla/tcpcl/network_test.go | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/cla/tcpcl/network_test.go b/cla/tcpcl/network_test.go index 710439bd..9fd201ca 100644 --- a/cla/tcpcl/network_test.go +++ b/cla/tcpcl/network_test.go @@ -56,6 +56,27 @@ func handleListener(serverAddr string, msgs, clients int, clientWg, serverWg *sy case cla.PeerAppeared: atomic.AddUint32(&msgsApprd, 1) + + go func(c cla.Convergence) { + if sender, ok := c.(cla.ConvergenceSender); !ok { + errs <- fmt.Errorf("New peer is not a ConvergenceSender; %v", cs) + } else { + bndl, err := bundle.Builder(). + CRC(bundle.CRC32). + Source("dtn://server/"). + Destination(cs.Message). + CreationTimestampNow(). + Lifetime("30m"). + HopCountBlock(64). + PayloadBlock([]byte("hello back!")). + Build() + if err != nil { + errs <- err + } else if err := sender.Send(&bndl); err != nil { + errs <- err + } + } + }(cs.Sender) } } }() @@ -79,6 +100,8 @@ func handleListener(serverAddr string, msgs, clients int, clientWg, serverWg *sy func handleClient(serverAddr string, clientNo, msgs int, wg *sync.WaitGroup, errs chan error) { defer wg.Done() + var msgsRecv uint32 + clientEid := fmt.Sprintf("dtn://client-%d/", clientNo) client := Dial(serverAddr, bundle.MustNewEndpointID(clientEid), false) if err, _ := client.Start(); err != nil { @@ -91,7 +114,10 @@ func handleClient(serverAddr string, clientNo, msgs int, wg *sync.WaitGroup, err go func() { for { - <-client.Channel() + switch cs := <-client.Channel(); cs.MessageType { + case cla.ReceivedBundle: + atomic.AddUint32(&msgsRecv, 1) + } } }() @@ -99,6 +125,7 @@ func handleClient(serverAddr string, clientNo, msgs int, wg *sync.WaitGroup, err defer clientWg.Done() for !client.state.IsEstablished() { + // Busy waiting.. } for i := 0; i < msgs; i++ { @@ -124,6 +151,10 @@ func handleClient(serverAddr string, clientNo, msgs int, wg *sync.WaitGroup, err clientWg.Wait() client.Close() + + if r := atomic.LoadUint32(&msgsRecv); r != 1 { + errs <- fmt.Errorf("Client received %d messages instead of 1", r) + } } func startTestTCPCLNetwork(msgs, clients int, t *testing.T) { From 590fccd3ce15888654e62bcd1b9daac275ffd50e Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 7 Oct 2019 22:54:30 +0200 Subject: [PATCH 40/53] ConvergenceProvider type for middleware CLAs --- cla/convergence_layer.go | 40 ++++++++++++++++++-- cla/manager.go | 79 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 107 insertions(+), 12 deletions(-) diff --git a/cla/convergence_layer.go b/cla/convergence_layer.go index faa7757f..2162c56a 100644 --- a/cla/convergence_layer.go +++ b/cla/convergence_layer.go @@ -1,4 +1,4 @@ -// Package cla defines two interfaces for convergence layers. +// Package cla defines two interfaces for Convergence Layer Adapters. // // The ConvergenceReceiver specifies a type which receives bundles and forwards // those to an exposed channel. @@ -9,23 +9,38 @@ // An implemented convergence layer can be a ConvergenceReceiver, // ConvergenceSender or even both. This depends on the convergence layer's // specification and is an implemention matter. +// +// Furthermore, the ConvergenceProvider provides the ability to create new +// instances of Convergence objects. +// +// Those types are generalized by the Convergable interface. +// +// A centralized instance for CLA management offers the Manager, designed to +// work seamlessly with the types above. package cla import "github.com/dtn7/dtn7-go/bundle" +// Convergable describes any kind of type which supports convergence layer- +// related services. This can be both a more specified Convergence interface +// type or a ConvergenceProvider. +type Convergable interface { + // Close signals this Convergable to shut down. + Close() +} + // Convergence is an interface to describe all kinds of Convergence Layer // Adapters. There should not be a direct implemention of this interface. One // must implement ConvergenceReceiver and/or ConvergenceSender, which are both // extending this interface. // A type can be both a ConvergenceReceiver and ConvergenceSender. type Convergence interface { + Convergable + // Start starts this Convergence{Receiver,Sender} and might return an error // and a boolean indicating if another Start should be tried later. Start() (error, bool) - // Close signals this Convergence{Receiver,Send} to shut down. - Close() - // Channel represents a return channel for transmitted bundles, status // messages, etc. Channel() chan ConvergenceStatus @@ -62,3 +77,20 @@ type ConvergenceSender interface { // if it's known. Otherwise the zero endpoint will be returned. GetPeerEndpointID() bundle.EndpointID } + +// ConvergenceProvider is a more general kind of CLA service which does not +// transfer any Bundles by itself, but supplies/creates new Convergence types. +// Those Convergence objects will be passed to a Manager. Thus, one might think +// of a ConvergenceProvider as some kind of middleware. +type ConvergenceProvider interface { + Convergable + + // RegisterManager tells the ConvergenceProvider where to report new instances + // of Convergence to. + RegisterManager(*Manager) + + // Start starts this ConvergenceProvider. Before being started, the the + // RegisterManager method tells this ConvergenceProvider its Manager. However, + // the Manager will both call the RegisterManager and Start methods. + Start() error +} diff --git a/cla/manager.go b/cla/manager.go index d5c4e7a5..b74838c8 100644 --- a/cla/manager.go +++ b/cla/manager.go @@ -24,6 +24,11 @@ type Manager struct { // convs: Map[string]*convergenceElem convs *sync.Map + // providers is an array of ConvergenceProvider. Those will report their + // created Convergence objects to this Manager, which also supervises it. + providers []ConvergenceProvider + providersMutex sync.Mutex + // inChnl receives ConvergenceStatus while outChnl passes it on. Both channels // are not buffered. While this is not a problem for inChnl, outChnl must // always be read, otherwise the Manager will block. @@ -48,7 +53,7 @@ func NewManager() *Manager { convs: new(sync.Map), - inChnl: make(chan ConvergenceStatus), + inChnl: make(chan ConvergenceStatus, 100), outChnl: make(chan ConvergenceStatus), stopSyn: make(chan struct{}), @@ -70,13 +75,19 @@ func (manager *Manager) handler() { for { select { case <-manager.stopSyn: - log.Debug("CLA Manager received closing-signal") + log.Debug("CLA Manager received closing signal") manager.convs.Range(func(_, convElem interface{}) bool { manager.Unregister(convElem.(*convergenceElem).conv) return true }) + manager.providersMutex.Lock() + for _, provider := range manager.providers { + provider.Close() + } + manager.providersMutex.Unlock() + close(manager.inChnl) close(manager.outChnl) @@ -146,12 +157,22 @@ func (manager *Manager) Close() { <-manager.stopAck } -// Register a new CLA. -func (manager *Manager) Register(conv Convergence) { +// Register any kind of Convergable. +func (manager *Manager) Register(conv Convergable) { if manager.isStopped() { return } + if c, ok := conv.(Convergence); ok { + manager.registerConvergence(c) + } else if c, ok := conv.(ConvergenceProvider); ok { + manager.registerProvider(c) + } else { + log.WithField("convergence", conv).Warn("Unknown kind of Convergable") + } +} + +func (manager *Manager) registerConvergence(conv Convergence) { // Check if this CLA is already known. Re-activate a deactivated CLA or abort. var ce *convergenceElem if convElem, exists := manager.convs.Load(conv.Address()); exists { @@ -192,8 +213,38 @@ func (manager *Manager) Register(conv Convergence) { } } -// Unregister an already known CLA. -func (manager *Manager) Unregister(conv Convergence) { +func (manager *Manager) registerProvider(conv ConvergenceProvider) { + manager.providersMutex.Lock() + defer manager.providersMutex.Unlock() + + for _, provider := range manager.providers { + if conv == provider { + log.WithField("provider", conv).Debug("Provider registration aborted, already known") + return + } + } + + manager.providers = append(manager.providers, conv) + + conv.RegisterManager(manager) + + if err := conv.Start(); err != nil { + log.WithError(err).WithField("provider", conv).Warn("Starting Provider errored") + } +} + +// Unregister any kind of Convergable. +func (manager *Manager) Unregister(conv Convergable) { + if c, ok := conv.(Convergence); ok { + manager.unregisterConvergence(c) + } else if c, ok := conv.(ConvergenceProvider); ok { + manager.unregisterProvider(c) + } else { + log.WithField("convergence", conv).Warn("Unknown kind of Convergable") + } +} + +func (manager *Manager) unregisterConvergence(conv Convergence) { convElem, exists := manager.convs.Load(conv.Address()) if !exists { log.WithFields(log.Fields{ @@ -208,8 +259,20 @@ func (manager *Manager) Unregister(conv Convergence) { manager.convs.Delete(conv.Address()) } -// Restart an already known CLA. -func (manager *Manager) Restart(conv Convergence) { +func (manager *Manager) unregisterProvider(conv ConvergenceProvider) { + manager.providersMutex.Lock() + defer manager.providersMutex.Unlock() + + for i, provider := range manager.providers { + if conv == provider { + manager.providers = append(manager.providers[:i], manager.providers[i+1:]...) + return + } + } +} + +// Restart a known Convergable. +func (manager *Manager) Restart(conv Convergable) { manager.Unregister(conv) manager.Register(conv) } From 979e264222a83661ab1488533cd7fb7b2a90227f Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 7 Oct 2019 22:55:11 +0200 Subject: [PATCH 41/53] TCPCL: Listener implements ConvergenceProvider --- cla/tcpcl/client.go | 6 +++--- cla/tcpcl/listener.go | 11 +++++------ cla/tcpcl/network_test.go | 17 +++-------------- 3 files changed, 11 insertions(+), 23 deletions(-) diff --git a/cla/tcpcl/client.go b/cla/tcpcl/client.go index f37490fe..a1c333d4 100644 --- a/cla/tcpcl/client.go +++ b/cla/tcpcl/client.go @@ -75,7 +75,7 @@ func NewTCPCLClient(conn net.Conn, endpointID bundle.EndpointID) *TCPCLClient { active: false, state: new(ClientState), msgsOut: make(chan Message, 100), - msgsIn: make(chan Message), + msgsIn: make(chan Message, 100), transferOutSend: make(chan Message), transferOutAck: make(chan Message), endpointID: endpointID, @@ -89,7 +89,7 @@ func Dial(address string, endpointID bundle.EndpointID, permanent bool) *TCPCLCl active: true, state: new(ClientState), msgsOut: make(chan Message, 100), - msgsIn: make(chan Message), + msgsIn: make(chan Message, 100), transferOutSend: make(chan Message), transferOutAck: make(chan Message), endpointID: endpointID, @@ -140,7 +140,7 @@ func (client *TCPCLClient) Start() (err error, retry bool) { client.log().Info("Starting client") - client.reportChan = make(chan cla.ConvergenceStatus) + client.reportChan = make(chan cla.ConvergenceStatus, 100) client.handleCounter = 2 go client.handleConnection() diff --git a/cla/tcpcl/listener.go b/cla/tcpcl/listener.go index cb1ac62b..6fdc9b32 100644 --- a/cla/tcpcl/listener.go +++ b/cla/tcpcl/listener.go @@ -21,17 +21,20 @@ type TCPCLListener struct { stopAck chan struct{} } -func NewTCPCLListener(listenAddress string, endpointID bundle.EndpointID, manager *cla.Manager) *TCPCLListener { +func NewTCPCLListener(listenAddress string, endpointID bundle.EndpointID) *TCPCLListener { return &TCPCLListener{ listenAddress: listenAddress, endpointID: endpointID, - manager: manager, stopSyn: make(chan struct{}), stopAck: make(chan struct{}), } } +func (listener *TCPCLListener) RegisterManager(manager *cla.Manager) { + listener.manager = manager +} + func (listener *TCPCLListener) Start() error { tcpAddr, err := net.ResolveTCPAddr("tcp", listener.listenAddress) if err != nil { @@ -47,10 +50,6 @@ func (listener *TCPCLListener) Start() error { for { select { case <-listener.stopSyn: - for _, c := range listener.clas { - listener.manager.Unregister(c) - } - ln.Close() close(listener.stopAck) diff --git a/cla/tcpcl/network_test.go b/cla/tcpcl/network_test.go index 9fd201ca..195691c7 100644 --- a/cla/tcpcl/network_test.go +++ b/cla/tcpcl/network_test.go @@ -32,18 +32,13 @@ func getRandomPort(t *testing.T) int { func handleListener(serverAddr string, msgs, clients int, clientWg, serverWg *sync.WaitGroup, errs chan error) { var ( msgsRecv uint32 - msgsDsprd uint32 msgsApprd uint32 ) defer serverWg.Done() manager := cla.NewManager() - listener := NewTCPCLListener(serverAddr, bundle.MustNewEndpointID("dtn://server/"), manager) - if err := listener.Start(); err != nil { - errs <- err - return - } + manager.Register(NewTCPCLListener(serverAddr, bundle.MustNewEndpointID("dtn://server/"))) go func() { for { @@ -51,9 +46,6 @@ func handleListener(serverAddr string, msgs, clients int, clientWg, serverWg *sy case cla.ReceivedBundle: atomic.AddUint32(&msgsRecv, 1) - case cla.PeerDisappeared: - atomic.AddUint32(&msgsDsprd, 1) - case cla.PeerAppeared: atomic.AddUint32(&msgsApprd, 1) @@ -82,16 +74,13 @@ func handleListener(serverAddr string, msgs, clients int, clientWg, serverWg *sy }() clientWg.Wait() - listener.Close() + manager.Close() time.Sleep(250 * time.Millisecond) if r := atomic.LoadUint32(&msgsRecv); r != uint32(msgs*clients) { errs <- fmt.Errorf("Listener received %d messages instead of %d", r, msgs*clients) } - if d := atomic.LoadUint32(&msgsDsprd); d != uint32(clients) { - errs <- fmt.Errorf("Listener received %d disappeared peers instead of %d", d, clients) - } if a := atomic.LoadUint32(&msgsApprd); a != uint32(clients) { errs <- fmt.Errorf("Listener received %d appeared peers instead of %d", a, clients) } @@ -170,7 +159,7 @@ func startTestTCPCLNetwork(msgs, clients int, t *testing.T) { serverWg.Add(1) go handleListener(serverAddr, msgs, clients, &clientWg, &serverWg, errs) - time.Sleep(100 * time.Millisecond) + time.Sleep(250 * time.Millisecond) for i := 0; i < clients; i++ { go handleClient(serverAddr, i, msgs, &clientWg, errs) From 09ae00023b782cbd36a61e80d5e166b0a76dd600 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 7 Oct 2019 23:11:39 +0200 Subject: [PATCH 42/53] TCPCL: Simplify type switches --- cla/tcpcl/client_contact.go | 4 ++-- cla/tcpcl/client_established.go | 17 ++++++++--------- cla/tcpcl/client_init.go | 6 +++--- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/cla/tcpcl/client_contact.go b/cla/tcpcl/client_contact.go index 7d015c08..eecd3561 100644 --- a/cla/tcpcl/client_contact.go +++ b/cla/tcpcl/client_contact.go @@ -18,9 +18,9 @@ func (client *TCPCLClient) handleContact() error { case !client.active && !client.contactRecv, client.active && client.contactSent && !client.contactRecv: msg := <-client.msgsIn - switch msg.(type) { + switch msg := msg.(type) { case *ContactHeader: - client.chRecv = *msg.(*ContactHeader) + client.chRecv = *msg client.contactRecv = true client.log().WithField("msg", client.chRecv).Debug("Received Contact Header") diff --git a/cla/tcpcl/client_established.go b/cla/tcpcl/client_established.go index aaca2e29..91b823c5 100644 --- a/cla/tcpcl/client_established.go +++ b/cla/tcpcl/client_established.go @@ -49,14 +49,14 @@ func (client *TCPCLClient) handleEstablished() (err error) { } case msg := <-client.msgsIn: - switch msg.(type) { + switch msg := msg.(type) { case *KeepaliveMessage: - keepaliveMsg := *msg.(*KeepaliveMessage) + keepaliveMsg := *msg client.keepaliveLast = time.Now() client.log().WithField("msg", keepaliveMsg).Debug("Received KEEPALIVE message") case *DataTransmissionMessage: - dtm := *msg.(*DataTransmissionMessage) + dtm := *msg client.log().WithField("msg", dtm).Debug("Received XFER_SEGMENT") if client.transferIn != nil && dtm.Flags&SegmentStart != 0 { @@ -115,7 +115,7 @@ func (client *TCPCLClient) handleEstablished() (err error) { client.transferOutAck <- msg case *SessionTerminationMessage: - sesstermMsg := *msg.(*SessionTerminationMessage) + sesstermMsg := *msg client.log().WithField("msg", sesstermMsg).Info("Received SESS_TERM") return sessTermErr @@ -172,13 +172,12 @@ func (client *TCPCLClient) Send(bndl *bundle.Bundle) error { tlog.WithField("msg", dtm).Debug("Send disposed XFER_SEGMENT") ackMsg := <-client.transferOutAck - switch ackMsg.(type) { + switch ackMsg := ackMsg.(type) { case *DataAcknowledgementMessage: - dam := ackMsg.(*DataAcknowledgementMessage) - tlog.WithField("msg", dam).Debug("Received XFER_ACK") + tlog.WithField("msg", ackMsg).Debug("Received XFER_ACK") - if dam.TransferId != dtm.TransferId || dam.Flags != dtm.Flags { - tlog.WithField("msg", dam).Warn("XFER_ACK does not match XFER_SEGMENT") + if ackMsg.TransferId != dtm.TransferId || ackMsg.Flags != dtm.Flags { + tlog.WithField("msg", ackMsg).Warn("XFER_ACK does not match XFER_SEGMENT") return fmt.Errorf("XFER_ACK does not match XFER_SEGMENT") } diff --git a/cla/tcpcl/client_init.go b/cla/tcpcl/client_init.go index 001a45c8..16feaa0f 100644 --- a/cla/tcpcl/client_init.go +++ b/cla/tcpcl/client_init.go @@ -30,14 +30,14 @@ func (client *TCPCLClient) handleSessInit() error { case !client.active && !client.initRecv, client.active && client.initSent && !client.initRecv: msg := <-client.msgsIn - switch msg.(type) { + switch msg := msg.(type) { case *SessionInitMessage: - client.sessInitRecv = *msg.(*SessionInitMessage) + client.sessInitRecv = *msg client.initRecv = true client.log().WithField("msg", client.sessInitRecv).Debug("Received SESS_INIT message") case *SessionTerminationMessage: - sesstermMsg := *msg.(*SessionTerminationMessage) + sesstermMsg := *msg client.log().WithField("msg", sesstermMsg).Info("Received SESS_TERM") return sessTermErr From d4388246ec95a2727dc638ad0e05e21ab654b84e Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Tue, 8 Oct 2019 10:56:31 +0200 Subject: [PATCH 43/53] Implement TCPCL in discovery --- discovery/discovery.go | 10 +++++----- discovery/discovery_test.go | 6 +++--- discovery/service.go | 14 +++++++++++--- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/discovery/discovery.go b/discovery/discovery.go index bfc32a97..8e0c9a70 100644 --- a/discovery/discovery.go +++ b/discovery/discovery.go @@ -27,9 +27,9 @@ const ( type CLAType uint const ( - // TCPCLV4 is the "Delay-Tolerant Networking TCP Convergence Layer Protocol - // Version 4" as specified in draft-ietf-dtn-tcpclv4-10 or newer documents. - TCPCLV4 CLAType = 0 + // TCPCL is the "Delay-Tolerant Networking TCP Convergence Layer Protocol + // Version 4" as specified in draft-ietf-dtn-tcpclv4-14 or newer. + TCPCL CLAType = 0 // MTCP is the "Minimal TCP Convergence-Layer Protocol" as specified in // draft-ietf-dtn-mtcpcl-01 or newer documents. @@ -134,8 +134,8 @@ func (dm DiscoveryMessage) String() string { fmt.Fprintf(&builder, "DiscoveryMessage(") switch dm.Type { - case TCPCLV4: - fmt.Fprintf(&builder, "TCPCLv4") + case TCPCL: + fmt.Fprintf(&builder, "TCPCL") case MTCP: fmt.Fprintf(&builder, "MTCP") default: diff --git a/discovery/discovery_test.go b/discovery/discovery_test.go index 91518bbb..81969089 100644 --- a/discovery/discovery_test.go +++ b/discovery/discovery_test.go @@ -15,17 +15,17 @@ func TestDiscoveryMessageCbor(t *testing.T) { Port: 8000, }, { - Type: MTCP, + Type: TCPCL, Endpoint: bundle.MustNewEndpointID("dtn:foobar"), Port: 8000, }, { - Type: TCPCLV4, + Type: MTCP, Endpoint: bundle.MustNewEndpointID("ipn:1337.23"), Port: 12345, }, { - Type: TCPCLV4, + Type: TCPCL, Endpoint: bundle.MustNewEndpointID("ipn:1337.23"), Port: 12345, }, diff --git a/discovery/service.go b/discovery/service.go index 4e0684fd..5d2de74f 100644 --- a/discovery/service.go +++ b/discovery/service.go @@ -6,7 +6,9 @@ import ( log "github.com/sirupsen/logrus" + "github.com/dtn7/dtn7-go/cla" "github.com/dtn7/dtn7-go/cla/mtcp" + "github.com/dtn7/dtn7-go/cla/tcpcl" "github.com/dtn7/dtn7-go/core" "github.com/schollz/peerdiscovery" ) @@ -50,7 +52,15 @@ func (ds *DiscoveryService) handleDiscovery(dm DiscoveryMessage, addr string) { "message": dm, }).Debug("Peer discovery received a message") - if dm.Type != MTCP { + var client cla.Convergence + switch dm.Type { + case MTCP: + client = mtcp.NewMTCPClient(fmt.Sprintf("%s:%d", addr, dm.Port), dm.Endpoint, false) + + case TCPCL: + client = tcpcl.Dial(fmt.Sprintf("%s:%d", addr, dm.Port), dm.Endpoint, false) + + default: log.WithFields(log.Fields{ "discovery": ds, "peer": addr, @@ -59,8 +69,6 @@ func (ds *DiscoveryService) handleDiscovery(dm DiscoveryMessage, addr string) { return } - client := mtcp.NewMTCPClient( - fmt.Sprintf("%s:%d", addr, dm.Port), dm.Endpoint, false) ds.c.RegisterConvergence(client) } From 702bf6ef15f1eb9f2c7df13710a8b7f6076cc048 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Tue, 8 Oct 2019 11:30:58 +0200 Subject: [PATCH 44/53] Add TCPCL to dtnd --- cmd/dtnd/configuration.go | 59 +++++++++++++++++++++++-------------- cmd/dtnd/configuration.toml | 12 ++++---- core/core.go | 4 +-- discovery/service.go | 2 +- 4 files changed, 46 insertions(+), 31 deletions(-) diff --git a/cmd/dtnd/configuration.go b/cmd/dtnd/configuration.go index 966d375f..7bb2edeb 100644 --- a/cmd/dtnd/configuration.go +++ b/cmd/dtnd/configuration.go @@ -12,6 +12,7 @@ import ( "github.com/dtn7/dtn7-go/bundle" "github.com/dtn7/dtn7-go/cla" "github.com/dtn7/dtn7-go/cla/mtcp" + "github.com/dtn7/dtn7-go/cla/tcpcl" "github.com/dtn7/dtn7-go/core" "github.com/dtn7/dtn7-go/discovery" ) @@ -62,15 +63,19 @@ type convergenceConf struct { Endpoint string } -// parseListen inspects a "listen" convergenceConf and returns a ConvergenceReceiver. -func parseListen(conv convergenceConf, nodeId bundle.EndpointID) (cla.ConvergenceReceiver, discovery.DiscoveryMessage, error) { - var defaultDisc = discovery.DiscoveryMessage{} +// parseListen inspects a "listen" convergenceConf and returns a Convergable. +func parseListen(conv convergenceConf, nodeId bundle.EndpointID) (cla.Convergable, discovery.DiscoveryMessage, error) { + _, portStr, err := net.SplitHostPort(conv.Endpoint) + if err != nil { + return nil, discovery.DiscoveryMessage{}, err + } + portInt, err := strconv.Atoi(portStr) + if err != nil { + return nil, discovery.DiscoveryMessage{}, err + } switch conv.Protocol { case "mtcp": - _, portStr, _ := net.SplitHostPort(conv.Endpoint) - portInt, _ := strconv.Atoi(portStr) - msg := discovery.DiscoveryMessage{ Type: discovery.MTCP, Endpoint: nodeId, @@ -79,21 +84,35 @@ func parseListen(conv convergenceConf, nodeId bundle.EndpointID) (cla.Convergenc return mtcp.NewMTCPServer(conv.Endpoint, nodeId, true), msg, nil + case "tcpcl": + listener := tcpcl.NewTCPCLListener(conv.Endpoint, nodeId) + + msg := discovery.DiscoveryMessage{ + Type: discovery.TCPCL, + Endpoint: nodeId, + Port: uint(portInt), + } + + return listener, msg, nil + default: - return nil, defaultDisc, fmt.Errorf("Unknown listen.protocol \"%s\"", conv.Protocol) + return nil, discovery.DiscoveryMessage{}, fmt.Errorf("Unknown listen.protocol \"%s\"", conv.Protocol) } } func parsePeer(conv convergenceConf) (cla.ConvergenceSender, error) { + endpointID, err := bundle.NewEndpointID(conv.Node) + if err != nil { + return nil, err + } + switch conv.Protocol { case "mtcp": - endpointID, err := bundle.NewEndpointID(conv.Node) - if err != nil { - return nil, err - } - return mtcp.NewMTCPClient(conv.Endpoint, endpointID, true), nil + case "tcpcl": + return tcpcl.Dial(conv.Endpoint, endpointID, true), nil + default: return nil, fmt.Errorf("Unknown peer.protocol \"%s\"", conv.Protocol) } @@ -182,17 +201,13 @@ func parseCore(filename string) (c *core.Core, ds *discovery.DiscoveryService, e // Listen/ConvergenceReceiver for _, conv := range conf.Listen { - var convRec cla.ConvergenceReceiver - var discoMsg discovery.DiscoveryMessage - - convRec, discoMsg, err = parseListen(conv, c.NodeId) - if err != nil { + if convRec, discoMsg, lErr := parseListen(conv, c.NodeId); lErr != nil { + err = lErr return + } else { + discoveryMsgs = append(discoveryMsgs, discoMsg) + c.RegisterConvergable(convRec) } - - discoveryMsgs = append(discoveryMsgs, discoMsg) - - c.RegisterConvergence(convRec) } // Peer/ConvergenceSender @@ -206,7 +221,7 @@ func parseCore(filename string) (c *core.Core, ds *discovery.DiscoveryService, e continue } - c.RegisterConvergence(convRec) + c.RegisterConvergable(convRec) } // Discovery diff --git a/cmd/dtnd/configuration.toml b/cmd/dtnd/configuration.toml index ba5027b0..47d590fe 100644 --- a/cmd/dtnd/configuration.toml +++ b/cmd/dtnd/configuration.toml @@ -43,19 +43,19 @@ listen = "127.0.0.1:8080" # Each listen is another convergence layer adapter (CLA). Multiple [[listen]] # blocks are usable. [[listen]] -# Protocol to use, currently only MTCP. -protocol = "mtcp" +# Protocol to use, one of tcpcl, mtcp. +protocol = "tcpcl" # Address to bind this CLA to. -endpoint = ":35037" +endpoint = ":4556" # Multiple [[peers]] might be configured. [[peer]] # The name/endpoint ID of this peer. node = "dtn://beta/" -# Protocol to use, currently only MTCP. -protocol = "mtcp" +# Protocol to use, one of tcpcl, mtcp. +protocol = "tcpcl" # Address to connect to this CLA. -endpoint = "10.0.0.2:35037" +endpoint = "10.0.0.2:4556" # Another peer example.. [[peer]] diff --git a/core/core.go b/core/core.go index c8f058bd..fb6b8ec0 100644 --- a/core/core.go +++ b/core/core.go @@ -271,7 +271,7 @@ func (c *Core) SendStatusReport(bp BundlePack, c.SendBundle(&outBndl) } -// RegisterCla is the exposed Register method from the CLA Manager. -func (c *Core) RegisterConvergence(conv cla.Convergence) { +// RegisterConvergable is the exposed Register method from the CLA Manager. +func (c *Core) RegisterConvergable(conv cla.Convergable) { c.claManager.Register(conv) } diff --git a/discovery/service.go b/discovery/service.go index 5d2de74f..938baf57 100644 --- a/discovery/service.go +++ b/discovery/service.go @@ -69,7 +69,7 @@ func (ds *DiscoveryService) handleDiscovery(dm DiscoveryMessage, addr string) { return } - ds.c.RegisterConvergence(client) + ds.c.RegisterConvergable(client) } // Close shuts the DiscoveryService down. From db52c4648f0fc02d4f0b7af2c8dfbe60e89340f6 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Tue, 8 Oct 2019 14:38:04 +0200 Subject: [PATCH 45/53] Discovery: Stop discovering yourself --- discovery/service.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/discovery/service.go b/discovery/service.go index 938baf57..a18cf7b2 100644 --- a/discovery/service.go +++ b/discovery/service.go @@ -52,13 +52,23 @@ func (ds *DiscoveryService) handleDiscovery(dm DiscoveryMessage, addr string) { "message": dm, }).Debug("Peer discovery received a message") + if dm.Endpoint == ds.c.NodeId { + log.WithFields(log.Fields{ + "discovery": ds, + "peer": addr, + "message": dm, + }).Debug("Peer discovery is from this node, dropping") + + return + } + var client cla.Convergence switch dm.Type { case MTCP: client = mtcp.NewMTCPClient(fmt.Sprintf("%s:%d", addr, dm.Port), dm.Endpoint, false) case TCPCL: - client = tcpcl.Dial(fmt.Sprintf("%s:%d", addr, dm.Port), dm.Endpoint, false) + client = tcpcl.Dial(fmt.Sprintf("%s:%d", addr, dm.Port), ds.c.NodeId, false) default: log.WithFields(log.Fields{ From 81a3f140424138769388a777b99e39beb4cf9235 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Tue, 8 Oct 2019 15:08:09 +0200 Subject: [PATCH 46/53] Configure dtnd's CLAs with correct node IDs --- cmd/dtnd/configuration.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/dtnd/configuration.go b/cmd/dtnd/configuration.go index 7bb2edeb..b77683e3 100644 --- a/cmd/dtnd/configuration.go +++ b/cmd/dtnd/configuration.go @@ -100,7 +100,7 @@ func parseListen(conv convergenceConf, nodeId bundle.EndpointID) (cla.Convergabl } } -func parsePeer(conv convergenceConf) (cla.ConvergenceSender, error) { +func parsePeer(conv convergenceConf, nodeId bundle.EndpointID) (cla.ConvergenceSender, error) { endpointID, err := bundle.NewEndpointID(conv.Node) if err != nil { return nil, err @@ -111,7 +111,7 @@ func parsePeer(conv convergenceConf) (cla.ConvergenceSender, error) { return mtcp.NewMTCPClient(conv.Endpoint, endpointID, true), nil case "tcpcl": - return tcpcl.Dial(conv.Endpoint, endpointID, true), nil + return tcpcl.Dial(conv.Endpoint, nodeId, true), nil default: return nil, fmt.Errorf("Unknown peer.protocol \"%s\"", conv.Protocol) @@ -212,7 +212,7 @@ func parseCore(filename string) (c *core.Core, ds *discovery.DiscoveryService, e // Peer/ConvergenceSender for _, conv := range conf.Peer { - convRec, err := parsePeer(conv) + convRec, err := parsePeer(conv, c.NodeId) if err != nil { log.WithFields(log.Fields{ "peer": conv.Endpoint, From 253fda5eb13b60fa45789435a585403df26649c5 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Tue, 8 Oct 2019 16:08:08 +0200 Subject: [PATCH 47/53] TCPCL: Fix XFER_SEGMENT's END flag --- cla/tcpcl/client_init.go | 2 +- cla/tcpcl/transfer_out.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cla/tcpcl/client_init.go b/cla/tcpcl/client_init.go index 16feaa0f..bae23e6e 100644 --- a/cla/tcpcl/client_init.go +++ b/cla/tcpcl/client_init.go @@ -16,7 +16,7 @@ func (client *TCPCLClient) handleSessInit() error { // XXX const ( keepalive = 10 - segmentMru = 1500 + segmentMru = 65535 transferMru = 0xFFFFFFFF ) diff --git a/cla/tcpcl/transfer_out.go b/cla/tcpcl/transfer_out.go index ab6fced9..fc3b9073 100644 --- a/cla/tcpcl/transfer_out.go +++ b/cla/tcpcl/transfer_out.go @@ -58,12 +58,12 @@ func (t *OutgoingTransfer) NextSegment(mru uint64) (dtm DataTransmissionMessage, } var buf = make([]byte, mru) - if n, rErr := t.dataStream.Read(buf); rErr != nil { - err = rErr - return - } else if uint64(n) < mru { + if n, rErr := io.ReadFull(t.dataStream, buf); rErr == io.ErrUnexpectedEOF { buf = buf[:n] segFlags |= SegmentEnd + } else if rErr != nil { + err = rErr + return } dtm = NewDataTransmissionMessage(segFlags, t.Id, buf) From 3213969207395108a6f98d2c7cba609239a5ca13 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Wed, 9 Oct 2019 12:25:10 +0200 Subject: [PATCH 48/53] TCPCL: Ensure to fill buffer completely --- cla/tcpcl/client_init.go | 2 +- cla/tcpcl/message.go | 5 +---- cla/tcpcl/message_sess_init.go | 10 ++-------- cla/tcpcl/message_xfer_segment.go | 12 ++---------- 4 files changed, 6 insertions(+), 23 deletions(-) diff --git a/cla/tcpcl/client_init.go b/cla/tcpcl/client_init.go index bae23e6e..436a1280 100644 --- a/cla/tcpcl/client_init.go +++ b/cla/tcpcl/client_init.go @@ -16,7 +16,7 @@ func (client *TCPCLClient) handleSessInit() error { // XXX const ( keepalive = 10 - segmentMru = 65535 + segmentMru = 10485760 transferMru = 0xFFFFFFFF ) diff --git a/cla/tcpcl/message.go b/cla/tcpcl/message.go index ae53c375..7b8f8f2a 100644 --- a/cla/tcpcl/message.go +++ b/cla/tcpcl/message.go @@ -43,12 +43,9 @@ func NewMessage(typeCode uint8) (msg Message, err error) { // ReadMessage parses the next TCPCL message from the Reader. func ReadMessage(r io.Reader) (msg Message, err error) { msgTypeBytes := make([]byte, 1) - if n, msgTypeErr := r.Read(msgTypeBytes); msgTypeErr != nil { + if _, msgTypeErr := io.ReadFull(r, msgTypeBytes); msgTypeErr != nil { err = msgTypeErr return - } else if n != 1 { - err = fmt.Errorf("Expected one byte, got %d bytes", n) - return } msg, msgErr := NewMessage(msgTypeBytes[0]) diff --git a/cla/tcpcl/message_sess_init.go b/cla/tcpcl/message_sess_init.go index cde0c553..7b1e65f1 100644 --- a/cla/tcpcl/message_sess_init.go +++ b/cla/tcpcl/message_sess_init.go @@ -87,10 +87,8 @@ func (si *SessionInitMessage) Unmarshal(r io.Reader) error { } var eidBuff = make([]byte, eidLength) - if n, err := r.Read(eidBuff); err != nil { + if _, err := io.ReadFull(r, eidBuff); err != nil { return err - } else if uint16(n) != eidLength { - return fmt.Errorf("SESS_INIT's EID length differs: expected %d and got %d", eidLength, n) } else { si.Eid = string(eidBuff) } @@ -102,12 +100,8 @@ func (si *SessionInitMessage) Unmarshal(r io.Reader) error { } else if sessionExtsLen > 0 { sessionExtsBuff := make([]byte, sessionExtsLen) - if n, err := r.Read(sessionExtsBuff); err != nil { + if _, err := io.ReadFull(r, sessionExtsBuff); err != nil { return err - } else if uint32(n) != sessionExtsLen { - return fmt.Errorf( - "SESS_INIT's Session Extension Length differs: expected %d and got %d", - sessionExtsLen, n) } } diff --git a/cla/tcpcl/message_xfer_segment.go b/cla/tcpcl/message_xfer_segment.go index 9670c033..616b7173 100644 --- a/cla/tcpcl/message_xfer_segment.go +++ b/cla/tcpcl/message_xfer_segment.go @@ -102,12 +102,8 @@ func (dtm *DataTransmissionMessage) Unmarshal(r io.Reader) error { if transferExtLen > 0 { transferExtBuff := make([]byte, transferExtLen) - if n, err := r.Read(transferExtBuff); err != nil { + if _, err := io.ReadFull(r, transferExtBuff); err != nil { return err - } else if uint32(n) != transferExtLen { - return fmt.Errorf( - "XFER_SEGMENT's Transfer Extension Length differs: expected %d and got %d", - transferExtLen, n) } } @@ -117,12 +113,8 @@ func (dtm *DataTransmissionMessage) Unmarshal(r io.Reader) error { } else if dataLen > 0 { dataBuff := make([]byte, dataLen) - if n, err := r.Read(dataBuff); err != nil { + if _, err := io.ReadFull(r, dataBuff); err != nil { return err - } else if uint64(n) != dataLen { - return fmt.Errorf( - "XFER_SEGMENT's Data length differs: expected %d and got %d", - dataLen, n) } else { dtm.Data = dataBuff } From 9f3ad3c675343b0c51f36a12f784f3be9b83f811 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Wed, 9 Oct 2019 16:08:44 +0200 Subject: [PATCH 49/53] TCPCL: Merge binary.Reads and Writes --- cla/tcpcl/client_init.go | 2 +- cla/tcpcl/message_reject.go | 13 +++---------- cla/tcpcl/message_xfer_ack.go | 14 +++----------- cla/tcpcl/message_xfer_refuse.go | 10 +++------- 4 files changed, 10 insertions(+), 29 deletions(-) diff --git a/cla/tcpcl/client_init.go b/cla/tcpcl/client_init.go index 436a1280..f0116325 100644 --- a/cla/tcpcl/client_init.go +++ b/cla/tcpcl/client_init.go @@ -16,7 +16,7 @@ func (client *TCPCLClient) handleSessInit() error { // XXX const ( keepalive = 10 - segmentMru = 10485760 + segmentMru = 1048576 transferMru = 0xFFFFFFFF ) diff --git a/cla/tcpcl/message_reject.go b/cla/tcpcl/message_reject.go index cf27b358..5a56b797 100644 --- a/cla/tcpcl/message_reject.go +++ b/cla/tcpcl/message_reject.go @@ -69,10 +69,7 @@ func (mrm MessageRejectionMessage) String() string { } func (mrm MessageRejectionMessage) Marshal(w io.Writer) error { - var fields = []interface{}{ - MSG_REJECT, - mrm.ReasonCode, - mrm.MessageHeader} + var fields = []interface{}{MSG_REJECT, mrm} for _, field := range fields { if err := binary.Write(w, binary.BigEndian, field); err != nil { @@ -93,12 +90,8 @@ func (mrm *MessageRejectionMessage) Unmarshal(r io.Reader) error { messageHeader, MSG_REJECT) } - var fields = []interface{}{&mrm.ReasonCode, &mrm.MessageHeader} - - for _, field := range fields { - if err := binary.Read(r, binary.BigEndian, field); err != nil { - return err - } + if err := binary.Read(r, binary.BigEndian, mrm); err != nil { + return err } if !mrm.ReasonCode.IsValid() { diff --git a/cla/tcpcl/message_xfer_ack.go b/cla/tcpcl/message_xfer_ack.go index ba5ffc8f..71a94150 100644 --- a/cla/tcpcl/message_xfer_ack.go +++ b/cla/tcpcl/message_xfer_ack.go @@ -32,11 +32,7 @@ func (dam DataAcknowledgementMessage) String() string { } func (dam DataAcknowledgementMessage) Marshal(w io.Writer) error { - var fields = []interface{}{ - XFER_ACK, - dam.Flags, - dam.TransferId, - dam.AckLen} + var fields = []interface{}{XFER_ACK, dam} for _, field := range fields { if err := binary.Write(w, binary.BigEndian, field); err != nil { @@ -55,12 +51,8 @@ func (dam *DataAcknowledgementMessage) Unmarshal(r io.Reader) error { return fmt.Errorf("XFER_ACK's Message Header is wrong: %d instead of %d", messageHeader, XFER_ACK) } - var fields = []interface{}{&dam.Flags, &dam.TransferId, &dam.AckLen} - - for _, field := range fields { - if err := binary.Read(r, binary.BigEndian, field); err != nil { - return err - } + if err := binary.Read(r, binary.BigEndian, dam); err != nil { + return err } return nil diff --git a/cla/tcpcl/message_xfer_refuse.go b/cla/tcpcl/message_xfer_refuse.go index a975a1c5..a7b866e2 100644 --- a/cla/tcpcl/message_xfer_refuse.go +++ b/cla/tcpcl/message_xfer_refuse.go @@ -79,7 +79,7 @@ func (trm TransferRefusalMessage) String() string { } func (trm TransferRefusalMessage) Marshal(w io.Writer) error { - var fields = []interface{}{XFER_REFUSE, trm.ReasonCode, trm.TransferId} + var fields = []interface{}{XFER_REFUSE, trm} for _, field := range fields { if err := binary.Write(w, binary.BigEndian, field); err != nil { @@ -98,12 +98,8 @@ func (trm *TransferRefusalMessage) Unmarshal(r io.Reader) error { return fmt.Errorf("XFER_REFUSE's Message Header is wrong: %d instead of %d", messageHeader, XFER_REFUSE) } - var fields = []interface{}{&trm.ReasonCode, &trm.TransferId} - - for _, field := range fields { - if err := binary.Read(r, binary.BigEndian, field); err != nil { - return err - } + if err := binary.Read(r, binary.BigEndian, trm); err != nil { + return err } if !trm.ReasonCode.IsValid() { From 0d13b254f33c7482a63cbe8ec4bbbe5a6cb298c4 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Wed, 9 Oct 2019 16:44:54 +0200 Subject: [PATCH 50/53] TCPCL: Test Transfers --- cla/tcpcl/transfer_test.go | 66 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 cla/tcpcl/transfer_test.go diff --git a/cla/tcpcl/transfer_test.go b/cla/tcpcl/transfer_test.go new file mode 100644 index 00000000..17f6a10d --- /dev/null +++ b/cla/tcpcl/transfer_test.go @@ -0,0 +1,66 @@ +package tcpcl + +import ( + "fmt" + "io" + "math/rand" + "reflect" + "testing" + + "github.com/dtn7/dtn7-go/bundle" +) + +func testGetRandomData(size int) []byte { + payload := make([]byte, size) + + rand.Seed(0) + rand.Read(payload) + + return payload +} + +func TestTransfer(t *testing.T) { + var sizes = []int{1, 1024, 1048576, 10485760} + + for _, size := range sizes { + t.Run(fmt.Sprintf("%d", size), func(t *testing.T) { + bndlOut, err := bundle.Builder(). + CRC(bundle.CRC32). + Source("dtn://src/"). + Destination("dtn://dst/"). + CreationTimestampNow(). + Lifetime("30m"). + HopCountBlock(64). + PayloadBlock(testGetRandomData(size)). + Build() + if err != nil { + t.Fatal(err) + } + + out := NewBundleOutgoingTransfer(42, bndlOut) + in := NewIncomingTransfer(42) + + for { + if dtm, err := out.NextSegment(1400); err == nil { + if _, err := in.NextSegment(dtm); err != nil { + t.Fatal(err) + } + } else if err == io.EOF { + if !in.IsFinished() { + t.Fatalf("Out has finished, In has not.") + } + + break + } else { + t.Fatal(err) + } + } + + if bndlIn, err := in.ToBundle(); err != nil { + t.Fatal(err) + } else if !reflect.DeepEqual(bndlOut, bndlIn) { + t.Fatalf("Bundles differ") + } + }) + } +} From 4f94560642b80691680beb45e4446327deebe135 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Thu, 10 Oct 2019 14:17:10 +0200 Subject: [PATCH 51/53] TCPCL: Increase Client's verbosity --- cla/tcpcl/client.go | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/cla/tcpcl/client.go b/cla/tcpcl/client.go index a1c333d4..4abd70f1 100644 --- a/cla/tcpcl/client.go +++ b/cla/tcpcl/client.go @@ -100,7 +100,11 @@ func (client *TCPCLClient) String() string { var b strings.Builder fmt.Fprintf(&b, "TCPCL(") - fmt.Fprintf(&b, "peer=%v, ", client.conn.RemoteAddr()) + if client.conn != nil { + fmt.Fprintf(&b, "peer=%v, ", client.conn.RemoteAddr()) + } else { + fmt.Fprintf(&b, "peer=NONE, ") + } fmt.Fprintf(&b, "active peer=%t", client.active) fmt.Fprintf(&b, ")") @@ -131,6 +135,7 @@ func (client *TCPCLClient) Start() (err error, retry bool) { if client.conn == nil { if conn, connErr := net.DialTimeout("tcp", client.address, time.Second); connErr != nil { err = connErr + retry = true return } else { client.conn = conn @@ -157,15 +162,17 @@ func (client *TCPCLClient) handleConnection() { atomic.AddInt32(&client.handleCounter, -1) }() - var rw = bufio.NewReadWriter(bufio.NewReader(client.conn), bufio.NewWriter(client.conn)) + // var rw = bufio.NewReadWriter(bufio.NewReader(client.conn), bufio.NewWriter(client.conn)) + var r = bufio.NewReader(client.conn) + var w = bufio.NewWriter(client.conn) for { select { case msg := <-client.msgsOut: - if err := msg.Marshal(rw); err != nil { + if err := msg.Marshal(w); err != nil { client.log().WithError(err).WithField("msg", msg).Error("Sending message errored") return - } else if err := rw.Flush(); err != nil { + } else if err := w.Flush(); err != nil { client.log().WithError(err).WithField("msg", msg).Error("Flushing errored") return } else { @@ -187,7 +194,7 @@ func (client *TCPCLClient) handleConnection() { return } - if msg, err := ReadMessage(rw); err == nil { + if msg, err := ReadMessage(r); err == nil { client.log().WithField("msg", msg).Debug("Received message") client.msgsIn <- msg } else if err == io.EOF { @@ -223,6 +230,8 @@ func (client *TCPCLClient) handleState() { stateHandler = client.handleSessInit case client.state.IsEstablished(): stateHandler = client.handleEstablished + default: + client.log().WithField("state", client.state).Fatal("Illegal state") } if err := stateHandler(); err != nil { @@ -246,7 +255,10 @@ func (client *TCPCLClient) handleState() { var sessTerm = NewSessionTerminationMessage(0, TerminationUnknown) client.msgsOut <- &sessTerm - client.reportChan <- cla.NewConvergencePeerDisappeared(client, client.peerEndpointID) + emptyEndpoint := bundle.EndpointID{} + if client.endpointID != emptyEndpoint { + client.reportChan <- cla.NewConvergencePeerDisappeared(client, client.peerEndpointID) + } return } From 93bdcdffbc36ad7f0fc4f630ba400fa6d6ef549a Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Fri, 11 Oct 2019 10:36:26 +0200 Subject: [PATCH 52/53] TCPCL: Seperate handlers, fix race condition --- cla/tcpcl/client.go | 142 +++----------------------- cla/tcpcl/client_handler.go | 159 ++++++++++++++++++++++++++++++ cla/tcpcl/message.go | 2 +- cla/tcpcl/message_xfer_segment.go | 9 +- cla/tcpcl/network_test.go | 29 +++--- cla/tcpcl/transfer_in.go | 11 ++- 6 files changed, 204 insertions(+), 148 deletions(-) create mode 100644 cla/tcpcl/client_handler.go diff --git a/cla/tcpcl/client.go b/cla/tcpcl/client.go index 4abd70f1..76103ea8 100644 --- a/cla/tcpcl/client.go +++ b/cla/tcpcl/client.go @@ -1,14 +1,11 @@ package tcpcl import ( - "bufio" "errors" "fmt" - "io" "net" "strings" "sync" - "sync/atomic" "time" log "github.com/sirupsen/logrus" @@ -32,7 +29,11 @@ type TCPCLClient struct { msgsOut chan Message msgsIn chan Message - handleCounter int32 + handleMetaStop chan struct{} + handleMetaStopAck chan struct{} + handlerConnInStop chan struct{} + handlerConnOutStop chan struct{} + handlerStateStop chan struct{} active bool state *ClientState @@ -145,136 +146,25 @@ func (client *TCPCLClient) Start() (err error, retry bool) { client.log().Info("Starting client") + client.handleMetaStop = make(chan struct{}, 10) + client.handleMetaStopAck = make(chan struct{}, 2) + client.handlerConnInStop = make(chan struct{}, 2) + client.handlerConnOutStop = make(chan struct{}, 2) + client.handlerStateStop = make(chan struct{}, 2) + client.reportChan = make(chan cla.ConvergenceStatus, 100) - client.handleCounter = 2 - go client.handleConnection() + go client.handleMeta() + go client.handleConnIn() + go client.handleConnOut() go client.handleState() return } -func (client *TCPCLClient) handleConnection() { - defer func() { - client.log().Debug("Leaving connection handler function") - client.state.Terminate() - - atomic.AddInt32(&client.handleCounter, -1) - }() - - // var rw = bufio.NewReadWriter(bufio.NewReader(client.conn), bufio.NewWriter(client.conn)) - var r = bufio.NewReader(client.conn) - var w = bufio.NewWriter(client.conn) - - for { - select { - case msg := <-client.msgsOut: - if err := msg.Marshal(w); err != nil { - client.log().WithError(err).WithField("msg", msg).Error("Sending message errored") - return - } else if err := w.Flush(); err != nil { - client.log().WithError(err).WithField("msg", msg).Error("Flushing errored") - return - } else { - client.log().WithField("msg", msg).Debug("Sent message") - } - - if _, ok := msg.(*SessionTerminationMessage); ok { - client.log().WithField("msg", msg).Debug("Closing connection after sending SESS_TERM") - - if err := client.conn.Close(); err != nil { - client.log().WithError(err).Warn("Failed to close TCP connection") - } - return - } - - default: - if err := client.conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)); err != nil { - client.log().WithError(err).Error("Setting read deadline errored") - return - } - - if msg, err := ReadMessage(r); err == nil { - client.log().WithField("msg", msg).Debug("Received message") - client.msgsIn <- msg - } else if err == io.EOF { - client.log().Info("Read EOF, closing down.") - return - } else if netErr, ok := err.(net.Error); ok && !netErr.Timeout() { - client.log().WithError(netErr).Error("Network error occured") - return - } else if !ok { - client.log().WithError(err).Error("Parsing next message errored") - return - } - } - } -} - -func (client *TCPCLClient) handleState() { - defer func() { - client.log().Debug("Leaving state handler function") - - atomic.AddInt32(&client.handleCounter, -1) - }() - - for { - switch { - case !client.state.IsTerminated(): - var stateHandler func() error - - switch { - case client.state.IsContact(): - stateHandler = client.handleContact - case client.state.IsInit(): - stateHandler = client.handleSessInit - case client.state.IsEstablished(): - stateHandler = client.handleEstablished - default: - client.log().WithField("state", client.state).Fatal("Illegal state") - } - - if err := stateHandler(); err != nil { - if err == sessTermErr { - client.log().Info("Received SESS_TERM, switching to Termination state") - } else { - client.log().WithError(err).Warn("State handler errored") - } - - client.state.Terminate() - goto terminationCase - } - break - - terminationCase: - fallthrough - - default: - client.log().Info("Entering Termination state") - - var sessTerm = NewSessionTerminationMessage(0, TerminationUnknown) - client.msgsOut <- &sessTerm - - emptyEndpoint := bundle.EndpointID{} - if client.endpointID != emptyEndpoint { - client.reportChan <- cla.NewConvergencePeerDisappeared(client, client.peerEndpointID) - } - - return - } - - if atomic.LoadInt32(&client.handleCounter) != 2 { - return - } - } -} - func (client *TCPCLClient) Close() { - client.state.Terminate() - - for atomic.LoadInt32(&client.handleCounter) > 0 { - time.Sleep(time.Millisecond) - } + client.handleMetaStop <- struct{}{} + <-client.handleMetaStopAck } func (client *TCPCLClient) Channel() chan cla.ConvergenceStatus { diff --git a/cla/tcpcl/client_handler.go b/cla/tcpcl/client_handler.go new file mode 100644 index 00000000..4303c70d --- /dev/null +++ b/cla/tcpcl/client_handler.go @@ -0,0 +1,159 @@ +package tcpcl + +import ( + "bufio" + "io" + "net" + "time" + + "github.com/dtn7/dtn7-go/bundle" + "github.com/dtn7/dtn7-go/cla" +) + +func (client *TCPCLClient) handleMeta() { + for range client.handleMetaStop { + client.log().Info("Handler received stop signal") + + client.state.Terminate() + + chans := []chan struct{}{client.handlerConnInStop, client.handlerConnOutStop, client.handlerStateStop} + for _, chn := range chans { + close(chn) + } + + close(client.handleMetaStopAck) + + return + } +} + +func (client *TCPCLClient) handleConnIn() { + defer func() { + client.log().Debug("Leaving incoming connection handler") + client.handleMetaStop <- struct{}{} + }() + + var r = bufio.NewReader(client.conn) + + for { + select { + case <-client.handlerConnInStop: + return + + default: + if err := client.conn.SetReadDeadline(time.Now().Add(5 * time.Second)); err != nil { + client.log().WithError(err).Error("Setting read deadline errored") + return + } + + if msg, err := ReadMessage(r); err == nil { + client.log().WithField("msg", msg).Debug("Received message") + client.msgsIn <- msg + } else if err == io.EOF { + client.log().Info("Read EOF, closing down.") + return + } else if netErr, ok := err.(net.Error); ok && !netErr.Timeout() { + client.log().WithError(netErr).Error("Network error occurred") + return + } else if !ok { + client.log().WithError(err).Error("Parsing next message errored") + return + } + } + } +} + +func (client *TCPCLClient) handleConnOut() { + defer func() { + client.log().Debug("Leaving outgoing connection handler") + client.handleMetaStop <- struct{}{} + }() + + var w = bufio.NewWriter(client.conn) + + for { + select { + case <-client.handlerConnOutStop: + return + + case msg := <-client.msgsOut: + if err := msg.Marshal(w); err != nil { + client.log().WithError(err).WithField("msg", msg).Error("Sending message errored") + return + } else if err := w.Flush(); err != nil { + client.log().WithError(err).WithField("msg", msg).Error("Flushing errored") + return + } else { + client.log().WithField("msg", msg).Debug("Sent message") + } + + if _, ok := msg.(*SessionTerminationMessage); ok { + client.log().WithField("msg", msg).Debug("Closing connection after sending SESS_TERM") + + if err := client.conn.Close(); err != nil { + client.log().WithError(err).Warn("Failed to close TCP connection") + } + return + } + } + } +} + +func (client *TCPCLClient) handleState() { + defer func() { + client.log().Debug("Leaving state handler") + client.handleMetaStop <- struct{}{} + }() + + for { + select { + case <-client.handlerStateStop: + return + + default: + switch { + case !client.state.IsTerminated(): + var stateHandler func() error + + switch { + case client.state.IsContact(): + stateHandler = client.handleContact + case client.state.IsInit(): + stateHandler = client.handleSessInit + case client.state.IsEstablished(): + stateHandler = client.handleEstablished + default: + client.log().WithField("state", client.state).Fatal("Illegal state") + } + + if err := stateHandler(); err != nil { + if err == sessTermErr { + client.log().Info("Received SESS_TERM, switching to Termination state") + } else { + client.log().WithError(err).Warn("State handler errored") + } + + client.state.Terminate() + goto terminationCase + } + break + + terminationCase: + fallthrough + + default: + client.log().Info("Entering Termination state") + + var sessTerm = NewSessionTerminationMessage(0, TerminationUnknown) + client.msgsOut <- &sessTerm + + emptyEndpoint := bundle.EndpointID{} + if client.endpointID != emptyEndpoint { + client.reportChan <- cla.NewConvergencePeerDisappeared(client, client.peerEndpointID) + } + + return + } + } + } +} diff --git a/cla/tcpcl/message.go b/cla/tcpcl/message.go index 7b8f8f2a..0b2e2f51 100644 --- a/cla/tcpcl/message.go +++ b/cla/tcpcl/message.go @@ -31,7 +31,7 @@ var messages = map[uint8]Message{ func NewMessage(typeCode uint8) (msg Message, err error) { msgType, exists := messages[typeCode] if !exists { - err = fmt.Errorf("No TCPCL Message registered for type code %d", typeCode) + err = fmt.Errorf("No TCPCL Message registered for type code %X", typeCode) return } diff --git a/cla/tcpcl/message_xfer_segment.go b/cla/tcpcl/message_xfer_segment.go index 616b7173..898a772b 100644 --- a/cla/tcpcl/message_xfer_segment.go +++ b/cla/tcpcl/message_xfer_segment.go @@ -111,12 +111,11 @@ func (dtm *DataTransmissionMessage) Unmarshal(r io.Reader) error { if err := binary.Read(r, binary.BigEndian, &dataLen); err != nil { return err } else if dataLen > 0 { - dataBuff := make([]byte, dataLen) - - if _, err := io.ReadFull(r, dataBuff); err != nil { + dtm.Data = make([]byte, dataLen) + if _, err := io.ReadFull(r, dtm.Data); err != nil { return err - } else { - dtm.Data = dataBuff + } else if dataLen != uint64(len(dtm.Data)) { + return fmt.Errorf("XFER_SEGMENT's data length should be %d, got %d bytes", dataLen, len(dtm.Data)) } } diff --git a/cla/tcpcl/network_test.go b/cla/tcpcl/network_test.go index 195691c7..51c95d11 100644 --- a/cla/tcpcl/network_test.go +++ b/cla/tcpcl/network_test.go @@ -8,8 +8,6 @@ import ( "testing" "time" - log "github.com/sirupsen/logrus" - "github.com/dtn7/dtn7-go/bundle" "github.com/dtn7/dtn7-go/cla" ) @@ -74,10 +72,11 @@ func handleListener(serverAddr string, msgs, clients int, clientWg, serverWg *sy }() clientWg.Wait() - manager.Close() - + // Wait for last transmission to be finished time.Sleep(250 * time.Millisecond) + manager.Close() + if r := atomic.LoadUint32(&msgsRecv); r != uint32(msgs*clients) { errs <- fmt.Errorf("Listener received %d messages instead of %d", r, msgs*clients) } @@ -86,7 +85,7 @@ func handleListener(serverAddr string, msgs, clients int, clientWg, serverWg *sy } } -func handleClient(serverAddr string, clientNo, msgs int, wg *sync.WaitGroup, errs chan error) { +func handleClient(serverAddr string, clientNo, msgs, payload int, wg *sync.WaitGroup, errs chan error) { defer wg.Done() var msgsRecv uint32 @@ -125,7 +124,7 @@ func handleClient(serverAddr string, clientNo, msgs int, wg *sync.WaitGroup, err CreationTimestampNow(). Lifetime("30m"). HopCountBlock(64). - PayloadBlock([]byte("hello world!")). + PayloadBlock(testGetRandomData(payload)). Build() if err != nil { @@ -146,9 +145,7 @@ func handleClient(serverAddr string, clientNo, msgs int, wg *sync.WaitGroup, err } } -func startTestTCPCLNetwork(msgs, clients int, t *testing.T) { - log.SetLevel(log.DebugLevel) - +func startTestTCPCLNetwork(msgs, clients, payload int, t *testing.T) { var serverAddr = fmt.Sprintf("localhost:%d", getRandomPort(t)) var errs = make(chan error) @@ -162,7 +159,7 @@ func startTestTCPCLNetwork(msgs, clients int, t *testing.T) { time.Sleep(250 * time.Millisecond) for i := 0; i < clients; i++ { - go handleClient(serverAddr, i, msgs, &clientWg, errs) + go handleClient(serverAddr, i, msgs, payload, &clientWg, errs) } go func() { @@ -188,7 +185,17 @@ func TestTCPCLNetwork(t *testing.T) { for _, test := range tests { t.Run(fmt.Sprintf("%d_clients_%d_msgs", test.clients, test.msgs), func(t *testing.T) { - startTestTCPCLNetwork(test.msgs, test.clients, t) + startTestTCPCLNetwork(test.msgs, test.clients, 64, t) + }) + } +} + +func TestTCPCLPayloadNetwork(t *testing.T) { + sizes := []int{64, 1024, 1048576, 10485760} + + for _, size := range sizes { + t.Run(fmt.Sprintf("%d", size), func(t *testing.T) { + startTestTCPCLNetwork(10, 1, size, t) }) } } diff --git a/cla/tcpcl/transfer_in.go b/cla/tcpcl/transfer_in.go index ad338fd5..61f74661 100644 --- a/cla/tcpcl/transfer_in.go +++ b/cla/tcpcl/transfer_in.go @@ -3,9 +3,8 @@ package tcpcl import ( "bytes" "fmt" - "io" - "github.com/dtn7/dtn7-go/bundle" + "io" ) // IncomingTransfer represents an incoming Bundle Transfer for the TCPCL. @@ -45,9 +44,11 @@ func (t *IncomingTransfer) NextSegment(dtm DataTransmissionMessage) (dam DataAck return } - dtmReader := bytes.NewBuffer(dtm.Data) - if _, cpyErr := io.Copy(t.buf, dtmReader); cpyErr != nil { - err = cpyErr + if n, dtmErr := t.buf.Write(dtm.Data); dtmErr != nil && dtmErr != io.EOF { + err = dtmErr + return + } else if n != len(dtm.Data) { + err = fmt.Errorf("Expected %d bytes instead of %d", len(dtm.Data), n) return } From 744b30c26681dbd890883eebdb18b9e887cab7c4 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Fri, 11 Oct 2019 11:04:10 +0200 Subject: [PATCH 53/53] TCPCL: Refactore and document package --- cla/tcpcl/0doc.go | 6 ++++++ cla/tcpcl/client.go | 33 +++++++++++++++++++-------------- cla/tcpcl/client_contact.go | 2 +- cla/tcpcl/client_established.go | 6 +++--- cla/tcpcl/client_handler.go | 31 ++++++++++++++++--------------- cla/tcpcl/client_init.go | 2 +- cla/tcpcl/listener.go | 22 +++++++++++++--------- cla/tcpcl/network_test.go | 17 +++++++++++------ cmd/dtnd/configuration.go | 4 ++-- discovery/service.go | 2 +- 10 files changed, 73 insertions(+), 52 deletions(-) create mode 100644 cla/tcpcl/0doc.go diff --git a/cla/tcpcl/0doc.go b/cla/tcpcl/0doc.go new file mode 100644 index 00000000..0878b752 --- /dev/null +++ b/cla/tcpcl/0doc.go @@ -0,0 +1,6 @@ +// Package tcpcl provides a library for the Delay-Tolerant Networking TCP Convergence Layer Protocol Version 4, +// draft-ietf-dtn-tcpclv4-14. +// +// A new TCPCL server can be started by the Listener, which provides multiple connection to its Clients. To reach +// a remote server, a new Client connection can be dialed. +package tcpcl diff --git a/cla/tcpcl/client.go b/cla/tcpcl/client.go index 76103ea8..1fa5cd8f 100644 --- a/cla/tcpcl/client.go +++ b/cla/tcpcl/client.go @@ -17,7 +17,10 @@ import ( // sessTermErr will be returned from a state handler iff a SESS_TERM was received. var sessTermErr = errors.New("SESS_TERM received") -type TCPCLClient struct { +// Client is a TCPCL client for a bidirectional Bundle exchange. Thus, the Client type implements both +// cla.ConvergenceReceiver and cla.ConvergenceSender. A Client can be created by the Listener for incoming +// connections or dialed for outbounding connections. +type Client struct { address string started bool permanent bool @@ -69,8 +72,9 @@ type TCPCLClient struct { reportChan chan cla.ConvergenceStatus } -func NewTCPCLClient(conn net.Conn, endpointID bundle.EndpointID) *TCPCLClient { - return &TCPCLClient{ +// NewClient creates a new Client on an existing connection. This function is used from the Listener. +func NewClient(conn net.Conn, endpointID bundle.EndpointID) *Client { + return &Client{ address: conn.RemoteAddr().String(), conn: conn, active: false, @@ -83,8 +87,9 @@ func NewTCPCLClient(conn net.Conn, endpointID bundle.EndpointID) *TCPCLClient { } } -func Dial(address string, endpointID bundle.EndpointID, permanent bool) *TCPCLClient { - return &TCPCLClient{ +// DialClient tries to establish a new TCPCL Client to a remote server. +func DialClient(address string, endpointID bundle.EndpointID, permanent bool) *Client { + return &Client{ address: address, permanent: permanent, active: true, @@ -97,7 +102,7 @@ func Dial(address string, endpointID bundle.EndpointID, permanent bool) *TCPCLCl } } -func (client *TCPCLClient) String() string { +func (client *Client) String() string { var b strings.Builder fmt.Fprintf(&b, "TCPCL(") @@ -113,14 +118,14 @@ func (client *TCPCLClient) String() string { } // log prepares a new log entry with predefined session data. -func (client *TCPCLClient) log() *log.Entry { +func (client *Client) log() *log.Entry { return log.WithFields(log.Fields{ "session": client, "state": client.state, }) } -func (client *TCPCLClient) Start() (err error, retry bool) { +func (client *Client) Start() (err error, retry bool) { if client.started { if client.active { client.conn = nil @@ -162,27 +167,27 @@ func (client *TCPCLClient) Start() (err error, retry bool) { return } -func (client *TCPCLClient) Close() { +func (client *Client) Close() { client.handleMetaStop <- struct{}{} <-client.handleMetaStopAck } -func (client *TCPCLClient) Channel() chan cla.ConvergenceStatus { +func (client *Client) Channel() chan cla.ConvergenceStatus { return client.reportChan } -func (client *TCPCLClient) Address() string { +func (client *Client) Address() string { return client.address } -func (client *TCPCLClient) IsPermanent() bool { +func (client *Client) IsPermanent() bool { return client.permanent } -func (client *TCPCLClient) GetEndpointID() bundle.EndpointID { +func (client *Client) GetEndpointID() bundle.EndpointID { return client.endpointID } -func (client *TCPCLClient) GetPeerEndpointID() bundle.EndpointID { +func (client *Client) GetPeerEndpointID() bundle.EndpointID { return client.peerEndpointID } diff --git a/cla/tcpcl/client_contact.go b/cla/tcpcl/client_contact.go index eecd3561..f20f937f 100644 --- a/cla/tcpcl/client_contact.go +++ b/cla/tcpcl/client_contact.go @@ -7,7 +7,7 @@ import ( // This file contains code for the Client's contact state. // handleContact manges the contact state for the Contact Header exchange. -func (client *TCPCLClient) handleContact() error { +func (client *Client) handleContact() error { switch { case client.active && !client.contactSent, !client.active && !client.contactSent && client.contactRecv: client.chSent = NewContactHeader(0) diff --git a/cla/tcpcl/client_established.go b/cla/tcpcl/client_established.go index 91b823c5..de2f2d90 100644 --- a/cla/tcpcl/client_established.go +++ b/cla/tcpcl/client_established.go @@ -14,7 +14,7 @@ import ( // This file contains code for the Client's established state. // handleEstablished manges the established state. -func (client *TCPCLClient) handleEstablished() (err error) { +func (client *Client) handleEstablished() (err error) { defer func() { if err != nil && client.keepaliveStarted { client.keepaliveTicker.Stop() @@ -140,12 +140,12 @@ func (client *TCPCLClient) handleEstablished() (err error) { return nil } -func (client *TCPCLClient) Send(bndl *bundle.Bundle) error { +func (client *Client) Send(bndl *bundle.Bundle) error { client.transferOutMutex.Lock() defer client.transferOutMutex.Unlock() if !client.state.IsEstablished() { - return fmt.Errorf("TCPCLClient is not in an established state") + return fmt.Errorf("Client is not in an established state") } client.transferOutId += 1 diff --git a/cla/tcpcl/client_handler.go b/cla/tcpcl/client_handler.go index 4303c70d..f155cffd 100644 --- a/cla/tcpcl/client_handler.go +++ b/cla/tcpcl/client_handler.go @@ -10,24 +10,23 @@ import ( "github.com/dtn7/dtn7-go/cla" ) -func (client *TCPCLClient) handleMeta() { - for range client.handleMetaStop { - client.log().Info("Handler received stop signal") +// handleMeta supervises the other handlers and propagates shutdown signals. +func (client *Client) handleMeta() { + <-client.handleMetaStop + client.log().Info("Handler received stop signal") - client.state.Terminate() + client.state.Terminate() - chans := []chan struct{}{client.handlerConnInStop, client.handlerConnOutStop, client.handlerStateStop} - for _, chn := range chans { - close(chn) - } - - close(client.handleMetaStopAck) - - return + chans := []chan struct{}{client.handlerConnInStop, client.handlerConnOutStop, client.handlerStateStop} + for _, chn := range chans { + close(chn) } + + close(client.handleMetaStopAck) } -func (client *TCPCLClient) handleConnIn() { +// handleConnIn handles incoming connections. +func (client *Client) handleConnIn() { defer func() { client.log().Debug("Leaving incoming connection handler") client.handleMetaStop <- struct{}{} @@ -63,7 +62,8 @@ func (client *TCPCLClient) handleConnIn() { } } -func (client *TCPCLClient) handleConnOut() { +// handleConnOut handles outgoing connections. +func (client *Client) handleConnOut() { defer func() { client.log().Debug("Leaving outgoing connection handler") client.handleMetaStop <- struct{}{} @@ -99,7 +99,8 @@ func (client *TCPCLClient) handleConnOut() { } } -func (client *TCPCLClient) handleState() { +// handleState handles the current or future state and starts the state's handler. +func (client *Client) handleState() { defer func() { client.log().Debug("Leaving state handler") client.handleMetaStop <- struct{}{} diff --git a/cla/tcpcl/client_init.go b/cla/tcpcl/client_init.go index f0116325..7b8d104a 100644 --- a/cla/tcpcl/client_init.go +++ b/cla/tcpcl/client_init.go @@ -12,7 +12,7 @@ import ( // This file contains code for the Client's contact state. // handleSessInit manges the initialization state. -func (client *TCPCLClient) handleSessInit() error { +func (client *Client) handleSessInit() error { // XXX const ( keepalive = 10 diff --git a/cla/tcpcl/listener.go b/cla/tcpcl/listener.go index 6fdc9b32..259598a9 100644 --- a/cla/tcpcl/listener.go +++ b/cla/tcpcl/listener.go @@ -11,7 +11,9 @@ import ( "github.com/dtn7/dtn7-go/cla" ) -type TCPCLListener struct { +// Listener is a TCPCL server bound to a TCP port to accept incoming TCPCL connections. +// This type implements the cla.ConvergenceProvider and should be supervised by a cla.Manager. +type Listener struct { listenAddress string endpointID bundle.EndpointID manager *cla.Manager @@ -21,8 +23,10 @@ type TCPCLListener struct { stopAck chan struct{} } -func NewTCPCLListener(listenAddress string, endpointID bundle.EndpointID) *TCPCLListener { - return &TCPCLListener{ +// NewListener creates a new Listener which should be bound to the given address and advertises the endpoint ID as +// its own node identifier. +func NewListener(listenAddress string, endpointID bundle.EndpointID) *Listener { + return &Listener{ listenAddress: listenAddress, endpointID: endpointID, @@ -31,11 +35,11 @@ func NewTCPCLListener(listenAddress string, endpointID bundle.EndpointID) *TCPCL } } -func (listener *TCPCLListener) RegisterManager(manager *cla.Manager) { +func (listener *Listener) RegisterManager(manager *cla.Manager) { listener.manager = manager } -func (listener *TCPCLListener) Start() error { +func (listener *Listener) Start() error { tcpAddr, err := net.ResolveTCPAddr("tcp", listener.listenAddress) if err != nil { return err @@ -58,11 +62,11 @@ func (listener *TCPCLListener) Start() error { default: if err := ln.SetDeadline(time.Now().Add(50 * time.Millisecond)); err != nil { log.WithError(err).WithField("cla", listener).Warn( - "TCPCLListener failed to set deadline on TCP socket") + "Listener failed to set deadline on TCP socket") listener.Close() } else if conn, err := ln.Accept(); err == nil { - client := NewTCPCLClient(conn, listener.endpointID) + client := NewClient(conn, listener.endpointID) listener.clas = append(listener.clas, client) listener.manager.Register(client) } @@ -73,11 +77,11 @@ func (listener *TCPCLListener) Start() error { return nil } -func (listener *TCPCLListener) Close() { +func (listener *Listener) Close() { close(listener.stopSyn) <-listener.stopAck } -func (listener TCPCLListener) String() string { +func (listener Listener) String() string { return fmt.Sprintf("tcpcl://%s", listener.listenAddress) } diff --git a/cla/tcpcl/network_test.go b/cla/tcpcl/network_test.go index 51c95d11..40054895 100644 --- a/cla/tcpcl/network_test.go +++ b/cla/tcpcl/network_test.go @@ -12,7 +12,7 @@ import ( "github.com/dtn7/dtn7-go/cla" ) -func getRandomPort(t *testing.T) int { +func getRandomPort(t *testing.T) (port int) { addr, err := net.ResolveTCPAddr("tcp", "localhost:0") if err != nil { t.Fatal(err) @@ -23,8 +23,13 @@ func getRandomPort(t *testing.T) int { t.Fatal(err) } - defer l.Close() - return l.Addr().(*net.TCPAddr).Port + port = l.Addr().(*net.TCPAddr).Port + + if err := l.Close(); err != nil { + t.Fatal(err) + } + + return } func handleListener(serverAddr string, msgs, clients int, clientWg, serverWg *sync.WaitGroup, errs chan error) { @@ -36,7 +41,7 @@ func handleListener(serverAddr string, msgs, clients int, clientWg, serverWg *sy defer serverWg.Done() manager := cla.NewManager() - manager.Register(NewTCPCLListener(serverAddr, bundle.MustNewEndpointID("dtn://server/"))) + manager.Register(NewListener(serverAddr, bundle.MustNewEndpointID("dtn://server/"))) go func() { for { @@ -73,7 +78,7 @@ func handleListener(serverAddr string, msgs, clients int, clientWg, serverWg *sy clientWg.Wait() // Wait for last transmission to be finished - time.Sleep(250 * time.Millisecond) + time.Sleep(time.Second) manager.Close() @@ -91,7 +96,7 @@ func handleClient(serverAddr string, clientNo, msgs, payload int, wg *sync.WaitG var msgsRecv uint32 clientEid := fmt.Sprintf("dtn://client-%d/", clientNo) - client := Dial(serverAddr, bundle.MustNewEndpointID(clientEid), false) + client := DialClient(serverAddr, bundle.MustNewEndpointID(clientEid), false) if err, _ := client.Start(); err != nil { errs <- err return diff --git a/cmd/dtnd/configuration.go b/cmd/dtnd/configuration.go index b77683e3..5e4fa4ba 100644 --- a/cmd/dtnd/configuration.go +++ b/cmd/dtnd/configuration.go @@ -85,7 +85,7 @@ func parseListen(conv convergenceConf, nodeId bundle.EndpointID) (cla.Convergabl return mtcp.NewMTCPServer(conv.Endpoint, nodeId, true), msg, nil case "tcpcl": - listener := tcpcl.NewTCPCLListener(conv.Endpoint, nodeId) + listener := tcpcl.NewListener(conv.Endpoint, nodeId) msg := discovery.DiscoveryMessage{ Type: discovery.TCPCL, @@ -111,7 +111,7 @@ func parsePeer(conv convergenceConf, nodeId bundle.EndpointID) (cla.ConvergenceS return mtcp.NewMTCPClient(conv.Endpoint, endpointID, true), nil case "tcpcl": - return tcpcl.Dial(conv.Endpoint, nodeId, true), nil + return tcpcl.DialClient(conv.Endpoint, nodeId, true), nil default: return nil, fmt.Errorf("Unknown peer.protocol \"%s\"", conv.Protocol) diff --git a/discovery/service.go b/discovery/service.go index a18cf7b2..5b143b1c 100644 --- a/discovery/service.go +++ b/discovery/service.go @@ -68,7 +68,7 @@ func (ds *DiscoveryService) handleDiscovery(dm DiscoveryMessage, addr string) { client = mtcp.NewMTCPClient(fmt.Sprintf("%s:%d", addr, dm.Port), dm.Endpoint, false) case TCPCL: - client = tcpcl.Dial(fmt.Sprintf("%s:%d", addr, dm.Port), ds.c.NodeId, false) + client = tcpcl.DialClient(fmt.Sprintf("%s:%d", addr, dm.Port), ds.c.NodeId, false) default: log.WithFields(log.Fields{