Repository: nats-io/nats.go
Pull request #996 (2022-06-10 09:23:46)

Expose TLS connection state when possible
I would like to be able to report things like ciphers,
tls or not, verified or not etc in nats account info
where its shows other connection properties but had
no way to get at this information

Signed-off-by: R.I.Pienaar <rip@devco.net>


Patch:
diff --git a/nats.go b/nats.go
index d451bf5..f914458 100644
--- a/nats.go
+++ b/nats.go
@@ -168,6 +168,7 @@ var (
 	ErrStreamNameAlreadyInUse       = errors.New("nats: stream name already in use")
 	ErrMaxConnectionsExceeded       = errors.New("nats: server maximum connections exceeded")
 	ErrBadRequest                   = errors.New("nats: bad request")
+	ErrConnectionNotTLS             = errors.New("nats: connection is not tls")
 )
 
 func init() {
@@ -1807,6 +1808,24 @@ func (nc *Conn) makeTLSConn() error {
 	return nil
 }
 
+// TLSConnectionState retrieves the state of the TLS connection to the server
+func (nc *Conn) TLSConnectionState() (tls.ConnectionState, error) {
+	if !nc.isConnected() {
+		return tls.ConnectionState{}, ErrDisconnected
+	}
+
+	nc.mu.RLock()
+	conn := nc.conn
+	nc.mu.RUnlock()
+
+	tc, ok := conn.(*tls.Conn)
+	if !ok {
+		return tls.ConnectionState{}, ErrConnectionNotTLS
+	}
+
+	return tc.ConnectionState(), nil
+}
+
 // waitForExits will wait for all socket watcher Go routines to
 // be shutdown before proceeding.
 func (nc *Conn) waitForExits() {
diff --git a/object.go b/object.go
index d2124bb..f2646d8 100644
--- a/object.go
+++ b/object.go
@@ -372,43 +372,37 @@ func (obs *obs) Put(meta *ObjectMeta, r io.Reader, opts ...ObjectOpt) (*ObjectIn
 
 		// Actual read.
 		// TODO(dlc) - Deadline?
-		n, readErr := r.Read(chunk)
-
-		// Handle all non EOF errors
-		if readErr != nil && readErr != io.EOF {
-			purgePartial()
-			return nil, readErr
-		}
-
-		// Add chunk only if we received data
-		if n > 0 {
-			// Chunk processing.
-			m.Data = chunk[:n]
-			h.Write(m.Data)
-
-			// Send msg itself.
-			if _, err := js.PublishMsgAsync(m); err != nil {
-				purgePartial()
-				return nil, err
-			}
-			if err := getErr(); err != nil {
-				purgePartial()
-				return nil, err
-			}
-			// Update totals.
-			sent++
-			total += uint64(n)
-		}
+		n, err := r.Read(chunk)
 
 		// EOF Processing.
-		if readErr == io.EOF {
+		if err == io.EOF {
 			// Finalize sha.
 			sha := h.Sum(nil)
 			// Place meta info.
 			info.Size, info.Chunks = uint64(total), uint32(sent)
 			info.Digest = fmt.Sprintf(objDigestTmpl, base64.URLEncoding.EncodeToString(sha[:]))
 			break
+		} else if err != nil {
+			purgePartial()
+			return nil, err
+		}
+
+		// Chunk processing.
+		m.Data = chunk[:n]
+		h.Write(m.Data)
+
+		// Send msg itself.
+		if _, err := js.PublishMsgAsync(m); err != nil {
+			purgePartial()
+			return nil, err
+		}
+		if err := getErr(); err != nil {
+			purgePartial()
+			return nil, err
 		}
+		// Update totals.
+		sent++
+		total += uint64(n)
 	}
 
 	// Publish the metadata.


Test patch:
diff --git a/test/basic_test.go b/test/basic_test.go
index f39c8f1..d6eaaa3 100644
--- a/test/basic_test.go
+++ b/test/basic_test.go
@@ -110,6 +110,19 @@ func TestLeakingGoRoutinesOnFailedConnect(t *testing.T) {
 	checkNoGoroutineLeak(t, base, "failed connect")
 }
 
+func TestTLSConnectionStateNonTLS(t *testing.T) {
+	s := RunDefaultServer()
+	defer s.Shutdown()
+
+	nc := NewDefaultConnection(t)
+	defer nc.Close()
+
+	_, err := nc.TLSConnectionState()
+	if err != nats.ErrConnectionNotTLS {
+		t.Fatalf("Expected a not tls error, got: %v", err)
+	}
+}
+
 func TestConnectedServer(t *testing.T) {
 	s := RunDefaultServer()
 	defer s.Shutdown()
diff --git a/test/conn_test.go b/test/conn_test.go
index 4818be2..3d426b4 100644
--- a/test/conn_test.go
+++ b/test/conn_test.go
@@ -161,6 +161,14 @@ func TestServerSecureConnections(t *testing.T) {
 	}
 	nc.Flush()
 
+	state, err := nc.TLSConnectionState()
+	if err != nil {
+		t.Fatalf("Expected connection state: %v", err)
+	}
+	if !state.HandshakeComplete {
+		t.Fatalf("Expected valid connection state")
+	}
+
 	if err := Wait(checkRecv); err != nil {
 		t.Fatal("Failed receiving message")
 	}