public void Login(Cluster cluster, Connection conn, out byte[] sessionToken, out DateTime?sessionExpiration)
        {
            sessionToken      = null;
            sessionExpiration = null;
            dataOffset        = 8;

            conn.SetTimeout(cluster.loginTimeout);

            try
            {
                if (cluster.authMode == AuthMode.INTERNAL)
                {
                    WriteHeader(LOGIN, 2);
                    WriteField(USER, cluster.user);
                    WriteField(CREDENTIAL, cluster.passwordHash);
                }
                else
                {
                    WriteHeader(LOGIN, 3);
                    WriteField(USER, cluster.user);
                    WriteField(CREDENTIAL, cluster.passwordHash);
                    WriteField(CLEAR_PASSWORD, cluster.password);
                }
                WriteSize();
                conn.Write(dataBuffer, dataOffset);
                conn.ReadFully(dataBuffer, HEADER_SIZE);

                int result = dataBuffer[RESULT_CODE];

                if (result != 0)
                {
                    if (result == INVALID_COMMAND)
                    {
                        // New login not supported.  Try old authentication.
                        AuthenticateOld(cluster, conn);
                        return;
                    }

                    // login failed.
                    throw new AerospikeException(result, "Login failed");
                }

                // Read session token.
                long size        = ByteUtil.BytesToLong(dataBuffer, 0);
                int  receiveSize = ((int)(size & 0xFFFFFFFFFFFFL)) - HEADER_REMAINING;
                int  fieldCount  = dataBuffer[11];

                if (receiveSize <= 0 || receiveSize > dataBuffer.Length || fieldCount <= 0)
                {
                    throw new AerospikeException(result, "Failed to retrieve session token");
                }

                conn.ReadFully(dataBuffer, receiveSize);
                dataOffset = 0;

                for (int i = 0; i < fieldCount; i++)
                {
                    int len = ByteUtil.BytesToInt(dataBuffer, dataOffset);
                    dataOffset += 4;
                    int id = dataBuffer[dataOffset++];
                    len--;

                    if (id == SESSION_TOKEN)
                    {
                        sessionToken = new byte[len];
                        Array.Copy(dataBuffer, dataOffset, sessionToken, 0, len);
                    }
                    else if (id == SESSION_TTL)
                    {
                        // Subtract 60 seconds from ttl so client session expires before server session.
                        long seconds = ByteUtil.BytesToUInt(dataBuffer, dataOffset) - 60;

                        if (seconds > 0)
                        {
                            sessionExpiration = DateTime.UtcNow.AddSeconds(seconds);
                        }
                        else
                        {
                            Log.Warn("Invalid session TTL: " + seconds);
                        }
                    }
                    dataOffset += len;
                }

                if (sessionToken == null)
                {
                    throw new AerospikeException(result, "Failed to retrieve session token");
                }
            }
            finally
            {
                conn.SetTimeout(cluster.connectionTimeout);
            }
        }