privatize the client, only expose Dial functions

This commit is contained in:
Marten Seemann 2017-02-22 12:21:33 +07:00
parent 48dee2708e
commit 96edca5219
No known key found for this signature in database
GPG key ID: 3603F40B121FCDEA
7 changed files with 214 additions and 188 deletions

View file

@ -19,44 +19,51 @@ import (
var _ = Describe("Client", func() {
var (
client *Client
sess *mockSession
packetConn *mockPacketConn
versionNegotiateCallbackCalled bool
cl *client
config *Config
sess *mockSession
packetConn *mockPacketConn
addr net.Addr
versionNegotiateConnStateCalled bool
)
BeforeEach(func() {
versionNegotiateConnStateCalled = false
packetConn = &mockPacketConn{}
versionNegotiateCallbackCalled = false
client = &Client{
versionNegotiateCallback: func() error {
versionNegotiateCallbackCalled = true
return nil
config = &Config{
ConnState: func(_ Session, state ConnState) {
if state == ConnStateVersionNegotiated {
versionNegotiateConnStateCalled = true
}
},
}
addr := &net.UDPAddr{IP: net.IPv4(192, 168, 100, 200), Port: 1337}
addr = &net.UDPAddr{IP: net.IPv4(192, 168, 100, 200), Port: 1337}
sess = &mockSession{connectionID: 0x1337}
client.connectionID = 0x1337
client.session = sess
client.version = protocol.Version36
client.conn = &conn{pconn: packetConn, currentAddr: addr}
cl = &client{
config: config,
connectionID: 0x1337,
session: sess,
version: protocol.Version36,
conn: &conn{pconn: packetConn, currentAddr: addr},
}
})
It("creates a new client", func() {
packetConn.dataToRead = []byte{0x0, 0x1, 0x0}
var err error
client, err = NewClient("quic.clemente.io:1337", nil, nil, nil)
sess, err := Dial(packetConn, addr, "quic.clemente.io:1337", config)
Expect(err).ToNot(HaveOccurred())
Expect(client.hostname).To(Equal("quic.clemente.io"))
Expect(*(*[]protocol.VersionNumber)(unsafe.Pointer(reflect.ValueOf(client.session.(*session).cryptoSetup).Elem().FieldByName("negotiatedVersions").UnsafeAddr()))).To(BeNil())
Expect(*(*[]protocol.VersionNumber)(unsafe.Pointer(reflect.ValueOf(sess.(*session).cryptoSetup).Elem().FieldByName("negotiatedVersions").UnsafeAddr()))).To(BeNil())
Expect(*(*string)(unsafe.Pointer(reflect.ValueOf(sess.(*session).cryptoSetup).Elem().FieldByName("hostname").UnsafeAddr()))).To(Equal("quic.clemente.io"))
})
It("errors on invalid public header", func() {
err := client.handlePacket(nil, nil)
err := cl.handlePacket(nil, nil)
Expect(err.(*qerr.QuicError).ErrorCode).To(Equal(qerr.InvalidPacketHeader))
})
It("errors on large packets", func() {
err := client.handlePacket(nil, bytes.Repeat([]byte{'a'}, int(protocol.MaxPacketSize)+1))
err := cl.handlePacket(nil, bytes.Repeat([]byte{'a'}, int(protocol.MaxPacketSize)+1))
Expect(err).To(MatchError(qerr.PacketTooLarge))
})
@ -68,51 +75,45 @@ var _ = Describe("Client", func() {
var stoppedListening bool
go func() {
defer GinkgoRecover()
err := client.Listen()
err := cl.Listen()
Expect(err).ToNot(HaveOccurred())
stoppedListening = true
}()
err := client.Close(testErr)
err := cl.Close(testErr)
Expect(err).ToNot(HaveOccurred())
Eventually(sess.closed).Should(BeTrue())
Expect(sess.closeReason).To(MatchError(testErr))
Expect(client.closed).To(Equal(uint32(1)))
Expect(cl.closed).To(Equal(uint32(1)))
Eventually(func() bool { return stoppedListening }).Should(BeTrue())
Eventually(runtime.NumGoroutine()).Should(Equal(numGoRoutines))
close(done)
}, 10)
It("only closes the client once", func() {
client.closed = 1
err := client.Close(errors.New("test error"))
cl.closed = 1
err := cl.Close(errors.New("test error"))
Expect(err).ToNot(HaveOccurred())
Eventually(sess.closed).Should(BeFalse())
Expect(sess.closeReason).ToNot(HaveOccurred())
})
It("creates new sessions with the right parameters", func() {
client.session = nil
client.hostname = "hostname"
err := client.createNewSession(nil)
cl.session = nil
cl.hostname = "hostname"
err := cl.createNewSession(nil)
Expect(err).ToNot(HaveOccurred())
Expect(client.session).ToNot(BeNil())
Expect(client.session.(*session).connectionID).To(Equal(client.connectionID))
Expect(client.session.(*session).version).To(Equal(client.version))
Expect(cl.session).ToNot(BeNil())
Expect(cl.session.(*session).connectionID).To(Equal(cl.connectionID))
Expect(cl.session.(*session).version).To(Equal(cl.version))
err = client.Close(nil)
err = cl.Close(nil)
Expect(err).ToNot(HaveOccurred())
})
It("opens a stream", func() {
stream, err := client.OpenStream()
Expect(err).ToNot(HaveOccurred())
Expect(stream).ToNot(BeNil())
})
Context("handling packets", func() {
It("errors on too large packets", func() {
err := client.handlePacket(nil, bytes.Repeat([]byte{'f'}, int(protocol.MaxPacketSize+1)))
err := cl.handlePacket(nil, bytes.Repeat([]byte{'f'}, int(protocol.MaxPacketSize+1)))
Expect(err).To(MatchError(qerr.PacketTooLarge))
})
@ -130,7 +131,7 @@ var _ = Describe("Client", func() {
Expect(sess.packetCount).To(BeZero())
var stoppedListening bool
go func() {
_ = client.Listen()
_ = cl.Listen()
// it should continue listening when receiving valid packets
stoppedListening = true
}()
@ -142,7 +143,7 @@ var _ = Describe("Client", func() {
It("closes the session when encountering an error while handling a packet", func() {
packetConn.dataToRead = bytes.Repeat([]byte{0xff}, 100)
listenErr := client.Listen()
listenErr := cl.Listen()
Expect(listenErr).To(HaveOccurred())
Expect(sess.closed).To(BeTrue())
Expect(sess.closeReason).To(MatchError(listenErr))
@ -160,7 +161,7 @@ var _ = Describe("Client", func() {
b.Write(s)
}
protocol.SupportedVersionsAsTags = b.Bytes()
packet := composeVersionNegotiation(client.connectionID)
packet := composeVersionNegotiation(cl.connectionID)
protocol.SupportedVersionsAsTags = oldSupportVersionTags
Expect(composeVersionNegotiation(0x1337)).To(Equal(oldVersionNegotiationPacket))
return packet
@ -175,51 +176,51 @@ var _ = Describe("Client", func() {
b := &bytes.Buffer{}
err := ph.Write(b, protocol.VersionWhatever, protocol.PerspectiveServer)
Expect(err).ToNot(HaveOccurred())
err = client.handlePacket(nil, b.Bytes())
err = cl.handlePacket(nil, b.Bytes())
Expect(err).ToNot(HaveOccurred())
Expect(client.versionNegotiated).To(BeTrue())
Expect(versionNegotiateCallbackCalled).To(BeTrue())
Expect(cl.versionNegotiated).To(BeTrue())
Eventually(func() bool { return versionNegotiateConnStateCalled }).Should(BeTrue())
})
It("changes the version after receiving a version negotiation packet", func() {
newVersion := protocol.Version35
Expect(newVersion).ToNot(Equal(client.version))
Expect(newVersion).ToNot(Equal(cl.version))
Expect(sess.packetCount).To(BeZero())
client.connectionID = 0x1337
err := client.handlePacket(nil, getVersionNegotiation([]protocol.VersionNumber{newVersion}))
Expect(client.version).To(Equal(newVersion))
Expect(client.versionNegotiated).To(BeTrue())
Expect(versionNegotiateCallbackCalled).To(BeTrue())
cl.connectionID = 0x1337
err := cl.handlePacket(nil, getVersionNegotiation([]protocol.VersionNumber{newVersion}))
Expect(cl.version).To(Equal(newVersion))
Expect(cl.versionNegotiated).To(BeTrue())
Eventually(func() bool { return versionNegotiateConnStateCalled }).Should(BeTrue())
// it swapped the sessions
Expect(client.session).ToNot(Equal(sess))
Expect(client.connectionID).ToNot(Equal(0x1337)) // it generated a new connection ID
Expect(cl.session).ToNot(Equal(sess))
Expect(cl.connectionID).ToNot(Equal(0x1337)) // it generated a new connection ID
Expect(err).ToNot(HaveOccurred())
// it didn't pass the version negoation packet to the session (since it has no payload)
Expect(sess.packetCount).To(BeZero())
Expect(*(*[]protocol.VersionNumber)(unsafe.Pointer(reflect.ValueOf(client.session.(*session).cryptoSetup).Elem().FieldByName("negotiatedVersions").UnsafeAddr()))).To(Equal([]protocol.VersionNumber{35}))
Expect(*(*[]protocol.VersionNumber)(unsafe.Pointer(reflect.ValueOf(cl.session.(*session).cryptoSetup).Elem().FieldByName("negotiatedVersions").UnsafeAddr()))).To(Equal([]protocol.VersionNumber{35}))
err = client.Close(nil)
err = cl.Close(nil)
Expect(err).ToNot(HaveOccurred())
})
It("errors if no matching version is found", func() {
err := client.handlePacket(nil, getVersionNegotiation([]protocol.VersionNumber{1}))
err := cl.handlePacket(nil, getVersionNegotiation([]protocol.VersionNumber{1}))
Expect(err).To(MatchError(qerr.InvalidVersion))
})
It("ignores delayed version negotiation packets", func() {
// if the version was not yet negotiated, handlePacket would return a VersionNegotiationMismatch error, see above test
client.versionNegotiated = true
cl.versionNegotiated = true
Expect(sess.packetCount).To(BeZero())
err := client.handlePacket(nil, getVersionNegotiation([]protocol.VersionNumber{1}))
err := cl.handlePacket(nil, getVersionNegotiation([]protocol.VersionNumber{1}))
Expect(err).ToNot(HaveOccurred())
Expect(client.versionNegotiated).To(BeTrue())
Expect(cl.versionNegotiated).To(BeTrue())
Expect(sess.packetCount).To(BeZero())
Expect(versionNegotiateCallbackCalled).To(BeFalse())
Consistently(func() bool { return versionNegotiateConnStateCalled }).Should(BeFalse())
})
It("errors if the server should have accepted the offered version", func() {
err := client.handlePacket(nil, getVersionNegotiation([]protocol.VersionNumber{client.version}))
err := cl.handlePacket(nil, getVersionNegotiation([]protocol.VersionNumber{cl.version}))
Expect(err).To(MatchError(qerr.Error(qerr.InvalidVersionNegotiationPacket, "Server already supports client's version and should have accepted the connection.")))
})
})