private void doAuthentication(PGStream pgStream, String user, String password, Logger logger)
      throws IOException, SQLException {
    // Now get the response from the backend, either an error message
    // or an authentication request

    while (true) {
      int beresp = pgStream.ReceiveChar();

      switch (beresp) {
        case 'E':
          // An error occured, so pass the error message to the
          // user.
          //
          // The most common one to be thrown here is:
          // "User authentication failed"
          //
          int l_elen = pgStream.ReceiveInteger4();
          if (l_elen > 30000) {
            // if the error length is > than 30000 we assume this is really a v2 protocol
            // server, so trigger fallback.
            throw new UnsupportedProtocolException();
          }

          ServerErrorMessage errorMsg =
              new ServerErrorMessage(pgStream.ReceiveString(l_elen - 4), logger.getLogLevel());
          if (logger.logDebug()) logger.debug(" <=BE ErrorMessage(" + errorMsg + ")");
          throw new PSQLException(errorMsg);

        case 'R':
          // Authentication request.
          // Get the message length
          int l_msgLen = pgStream.ReceiveInteger4();

          // Get the type of request
          int areq = pgStream.ReceiveInteger4();

          // Process the request.
          switch (areq) {
            case AUTH_REQ_CRYPT:
              {
                byte[] rst = new byte[2];
                rst[0] = (byte) pgStream.ReceiveChar();
                rst[1] = (byte) pgStream.ReceiveChar();
                String salt = new String(rst, 0, 2, "US-ASCII");

                if (logger.logDebug())
                  logger.debug(" <=BE AuthenticationReqCrypt(salt='" + salt + "')");

                if (password == null)
                  throw new PSQLException(
                      GT.tr(
                          "The server requested password-based authentication, but no password was provided."),
                      PSQLState.CONNECTION_REJECTED);

                String result = UnixCrypt.crypt(salt, password);
                byte[] encodedResult = result.getBytes("US-ASCII");

                if (logger.logDebug()) logger.debug(" FE=> Password(crypt='" + result + "')");

                pgStream.SendChar('p');
                pgStream.SendInteger4(4 + encodedResult.length + 1);
                pgStream.Send(encodedResult);
                pgStream.SendChar(0);
                pgStream.flush();

                break;
              }

            case AUTH_REQ_MD5:
              {
                byte[] md5Salt = pgStream.Receive(4);
                if (logger.logDebug()) {
                  logger.debug(
                      " <=BE AuthenticationReqMD5(salt=" + Utils.toHexString(md5Salt) + ")");
                }

                if (password == null)
                  throw new PSQLException(
                      GT.tr(
                          "The server requested password-based authentication, but no password was provided."),
                      PSQLState.CONNECTION_REJECTED);

                byte[] digest = MD5Digest.encode(user, password, md5Salt);

                if (logger.logDebug()) {
                  logger.debug(" FE=> Password(md5digest=" + new String(digest, "US-ASCII") + ")");
                }

                pgStream.SendChar('p');
                pgStream.SendInteger4(4 + digest.length + 1);
                pgStream.Send(digest);
                pgStream.SendChar(0);
                pgStream.flush();

                break;
              }

            case AUTH_REQ_PASSWORD:
              {
                if (logger.logDebug()) {
                  logger.debug(" <=BE AuthenticationReqPassword");
                  logger.debug(" FE=> Password(password=<not shown>)");
                }

                if (password == null)
                  throw new PSQLException(
                      GT.tr(
                          "The server requested password-based authentication, but no password was provided."),
                      PSQLState.CONNECTION_REJECTED);

                byte[] encodedPassword = password.getBytes("US-ASCII");

                pgStream.SendChar('p');
                pgStream.SendInteger4(4 + encodedPassword.length + 1);
                pgStream.Send(encodedPassword);
                pgStream.SendChar(0);
                pgStream.flush();

                break;
              }

            case AUTH_REQ_OK:
              if (logger.logDebug()) logger.debug(" <=BE AuthenticationOk");

              return; // We're done.

            default:
              if (logger.logDebug())
                logger.debug(" <=BE AuthenticationReq (unsupported type " + ((int) areq) + ")");

              throw new PSQLException(
                  GT.tr(
                      "The authentication type {0} is not supported. Check that you have configured the pg_hba.conf file to include the client''s IP address or subnet, and that it is using an authentication scheme supported by the driver.",
                      new Integer(areq)),
                  PSQLState.CONNECTION_REJECTED);
          }

          break;

        default:
          throw new PSQLException(
              GT.tr("Protocol error.  Session setup failed."),
              PSQLState.CONNECTION_UNABLE_TO_CONNECT);
      }
    }
  }
  public void put(StoredBlock storedBlock, StoredUndoableBlock undoableBlock)
      throws BlockStoreException {
    maybeConnect();
    // We skip the first 4 bytes because (on prodnet) the minimum target has 4 0-bytes
    byte[] hashBytes = new byte[28];
    System.arraycopy(storedBlock.getHeader().getHash().getBytes(), 3, hashBytes, 0, 28);
    int height = storedBlock.getHeight();
    byte[] transactions = null;
    byte[] txOutChanges = null;
    try {
      ByteArrayOutputStream bos = new ByteArrayOutputStream();
      if (undoableBlock.getTxOutChanges() != null) {
        undoableBlock.getTxOutChanges().serializeToStream(bos);
        txOutChanges = bos.toByteArray();
      } else {
        int numTxn = undoableBlock.getTransactions().size();
        bos.write((int) (0xFF & (numTxn >> 0)));
        bos.write((int) (0xFF & (numTxn >> 8)));
        bos.write((int) (0xFF & (numTxn >> 16)));
        bos.write((int) (0xFF & (numTxn >> 24)));
        for (Transaction tx : undoableBlock.getTransactions()) tx.rimbitSerialize(bos);
        transactions = bos.toByteArray();
      }
      bos.close();
    } catch (IOException e) {
      throw new BlockStoreException(e);
    }

    try {
      if (log.isDebugEnabled())
        log.debug("Looking for undoable block with hash: " + Utils.bytesToHexString(hashBytes));

      PreparedStatement findS =
          conn.get().prepareStatement("select 1 from undoableBlocks where hash = ?");
      findS.setBytes(1, hashBytes);

      ResultSet rs = findS.executeQuery();
      if (rs.next()) {
        // We already have this output, update it.
        findS.close();

        // Postgres insert-or-updates are very complex (and finnicky).  This level of transaction
        // isolation
        // seems to work for rimbitj
        PreparedStatement s =
            conn.get()
                .prepareStatement(
                    "UPDATE undoableBlocks SET txOutChanges=?, transactions=?" + " WHERE hash = ?");
        s.setBytes(3, hashBytes);

        if (log.isDebugEnabled())
          log.debug("Updating undoable block with hash: " + Utils.bytesToHexString(hashBytes));

        if (transactions == null) {
          s.setBytes(1, txOutChanges);
          s.setNull(2, Types.BINARY);
        } else {
          s.setNull(1, Types.BINARY);
          s.setBytes(2, transactions);
        }
        s.executeUpdate();
        s.close();

        return;
      }

      PreparedStatement s =
          conn.get()
              .prepareStatement(
                  "INSERT INTO undoableBlocks(hash, height, txOutChanges, transactions)"
                      + " VALUES(?, ?, ?, ?)");
      s.setBytes(1, hashBytes);
      s.setInt(2, height);

      if (log.isDebugEnabled())
        log.debug(
            "Inserting undoable block with hash: "
                + Utils.bytesToHexString(hashBytes)
                + " at height "
                + height);

      if (transactions == null) {
        s.setBytes(3, txOutChanges);
        s.setNull(4, Types.BINARY);
      } else {
        s.setNull(3, Types.BINARY);
        s.setBytes(4, transactions);
      }
      s.executeUpdate();
      s.close();
      try {
        putUpdateStoredBlock(storedBlock, true);
      } catch (SQLException e) {
        throw new BlockStoreException(e);
      }
    } catch (SQLException e) {
      if (!e.getSQLState().equals(POSTGRES_DUPLICATE_KEY_ERROR_CODE))
        throw new BlockStoreException(e);
    }
  }