1package quic
2
3import (
4	"sync"
5
6	"github.com/lucas-clemente/quic-go/internal/protocol"
7	"github.com/lucas-clemente/quic-go/internal/utils"
8)
9
10// A closedLocalSession is a session that we closed locally.
11// When receiving packets for such a session, we need to retransmit the packet containing the CONNECTION_CLOSE frame,
12// with an exponential backoff.
13type closedLocalSession struct {
14	conn            sendConn
15	connClosePacket []byte
16
17	closeOnce sync.Once
18	closeChan chan struct{} // is closed when the session is closed or destroyed
19
20	receivedPackets chan *receivedPacket
21	counter         uint64 // number of packets received
22
23	perspective protocol.Perspective
24
25	logger utils.Logger
26}
27
28var _ packetHandler = &closedLocalSession{}
29
30// newClosedLocalSession creates a new closedLocalSession and runs it.
31func newClosedLocalSession(
32	conn sendConn,
33	connClosePacket []byte,
34	perspective protocol.Perspective,
35	logger utils.Logger,
36) packetHandler {
37	s := &closedLocalSession{
38		conn:            conn,
39		connClosePacket: connClosePacket,
40		perspective:     perspective,
41		logger:          logger,
42		closeChan:       make(chan struct{}),
43		receivedPackets: make(chan *receivedPacket, 64),
44	}
45	go s.run()
46	return s
47}
48
49func (s *closedLocalSession) run() {
50	for {
51		select {
52		case p := <-s.receivedPackets:
53			s.handlePacketImpl(p)
54		case <-s.closeChan:
55			return
56		}
57	}
58}
59
60func (s *closedLocalSession) handlePacket(p *receivedPacket) {
61	select {
62	case s.receivedPackets <- p:
63	default:
64	}
65}
66
67func (s *closedLocalSession) handlePacketImpl(_ *receivedPacket) {
68	s.counter++
69	// exponential backoff
70	// only send a CONNECTION_CLOSE for the 1st, 2nd, 4th, 8th, 16th, ... packet arriving
71	for n := s.counter; n > 1; n = n / 2 {
72		if n%2 != 0 {
73			return
74		}
75	}
76	s.logger.Debugf("Received %d packets after sending CONNECTION_CLOSE. Retransmitting.", s.counter)
77	if err := s.conn.Write(s.connClosePacket); err != nil {
78		s.logger.Debugf("Error retransmitting CONNECTION_CLOSE: %s", err)
79	}
80}
81
82func (s *closedLocalSession) shutdown() {
83	s.destroy(nil)
84}
85
86func (s *closedLocalSession) destroy(error) {
87	s.closeOnce.Do(func() {
88		close(s.closeChan)
89	})
90}
91
92func (s *closedLocalSession) getPerspective() protocol.Perspective {
93	return s.perspective
94}
95
96// A closedRemoteSession is a session that was closed remotely.
97// For such a session, we might receive reordered packets that were sent before the CONNECTION_CLOSE.
98// We can just ignore those packets.
99type closedRemoteSession struct {
100	perspective protocol.Perspective
101}
102
103var _ packetHandler = &closedRemoteSession{}
104
105func newClosedRemoteSession(pers protocol.Perspective) packetHandler {
106	return &closedRemoteSession{perspective: pers}
107}
108
109func (s *closedRemoteSession) handlePacket(*receivedPacket)         {}
110func (s *closedRemoteSession) shutdown()                            {}
111func (s *closedRemoteSession) destroy(error)                        {}
112func (s *closedRemoteSession) getPerspective() protocol.Perspective { return s.perspective }
113