All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.cloud.spanner.pgadapter.wireprotocol.StartupMessage Maven / Gradle / Ivy

Go to download

The PGAdapter server implements the PostgreSQL wire-protocol, but sends all received statements to a Cloud Spanner database instead of a PostgreSQL database. The Cloud Spanner database must have been created to use the PostgreSQL dialect. See https://cloud.google.com/spanner/docs/quickstart-console#postgresql for more information on how to create PostgreSQL dialect databases on Cloud Spanner.

The newest version!
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.cloud.spanner.pgadapter.wireprotocol;

import com.google.api.core.InternalApi;
import com.google.auth.Credentials;
import com.google.cloud.spanner.pgadapter.ConnectionHandler;
import com.google.cloud.spanner.pgadapter.ConnectionHandler.ConnectionStatus;
import com.google.cloud.spanner.pgadapter.utils.ClientAutoDetector;
import com.google.cloud.spanner.pgadapter.utils.ClientAutoDetector.WellKnownClient;
import com.google.cloud.spanner.pgadapter.wireoutput.AuthenticationCleartextPasswordResponse;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;
import javax.annotation.Nullable;

/**
 * The first (non-encryption, non-admin) message expected in from a client in a connection loop.
 * Here we handle metadata and authentication if any.
 */
@InternalApi
public class StartupMessage extends BootstrapMessage {
  private static final Logger logger = Logger.getLogger(StartupMessage.class.getName());
  static final String DATABASE_KEY = "database";
  private static final String USER_KEY = "user";
  public static final int IDENTIFIER = 196608; // First Hextet: 3 (version), Second Hextet: 0

  private final boolean authenticate;
  private final Map parameters;

  public StartupMessage(ConnectionHandler connection, int length) throws Exception {
    super(connection, length);
    this.authenticate = connection.getServer().getOptions().shouldAuthenticate();
    String rawParameters = this.readAll();
    this.parameters = this.parseParameters(rawParameters);
    if (connection.getServer().getOptions().shouldAutoDetectClient()) {
      WellKnownClient wellKnownClient =
          ClientAutoDetector.detectClient(this.parseParameterKeys(rawParameters), this.parameters);
      connection.setWellKnownClient(wellKnownClient);
    } else {
      connection.setWellKnownClient(WellKnownClient.UNSPECIFIED);
    }
  }

  @Override
  protected void sendPayload() throws Exception {
    if (!authenticate) {
      createConnectionAndSendStartupMessage(
          this.connection, this.parameters.get(DATABASE_KEY), this.parameters, null);
    } else {
      new AuthenticationCleartextPasswordResponse(this.outputStream).send();
    }
  }

  static void createConnectionAndSendStartupMessage(
      ConnectionHandler connection,
      String database,
      Map parameters,
      @Nullable Credentials credentials)
      throws Exception {
    connection.connectToSpanner(database, credentials);
    for (Entry parameter : parameters.entrySet()) {
      connection
          .getExtendedQueryProtocolHandler()
          .getBackendConnection()
          .initSessionSetting(parameter.getKey(), parameter.getValue());
    }
    if (connection.getServer().getOptions().shouldAutoDetectClient()
        && connection.getWellKnownClient() == WellKnownClient.UNSPECIFIED) {
      WellKnownClient wellKnownClient =
          ClientAutoDetector.detectClient(
              connection
                  .getExtendedQueryProtocolHandler()
                  .getBackendConnection()
                  .getSessionState()
                  .tryGet("spanner", "well_known_client"));
      connection.setWellKnownClient(wellKnownClient);
    }
    for (Entry parameter :
        connection.getWellKnownClient().getDefaultParameters(parameters).entrySet()) {
      connection
          .getExtendedQueryProtocolHandler()
          .getBackendConnection()
          .initSessionSetting(parameter.getKey(), parameter.getValue(), /* overwrite = */ false);
    }
    if (connection.getWellKnownClient() != WellKnownClient.UNSPECIFIED) {
      connection
          .getExtendedQueryProtocolHandler()
          .getBackendConnection()
          .getSessionState()
          .setConnectionStartupValue(
              "spanner", "well_known_client", connection.getWellKnownClient().name());
    }
    sendStartupMessage(
        connection.getConnectionMetadata().getOutputStream(),
        connection.getConnectionId(),
        connection.getSecret(),
        connection.getExtendedQueryProtocolHandler().getBackendConnection().getSessionState(),
        connection.getWellKnownClient().createStartupNoticeResponses(connection));
    connection.setStatus(ConnectionStatus.AUTHENTICATED);
  }

  /** Here we expect the nextHandler to be {@link PasswordMessage} if we authenticate. */
  @Override
  public void nextHandler() throws Exception {
    if (authenticate) {
      char protocol = (char) inputStream.readUnsignedByte();
      if (protocol != PasswordMessage.IDENTIFIER) {
        throw new IOException(
            "Unexpected response, expected '"
                + PasswordMessage.IDENTIFIER
                + "', but got: "
                + protocol);
      }
      this.connection.setMessageState(new PasswordMessage(this.connection, this.parameters));
    } else {
      super.nextHandler();
    }
  }

  @Override
  protected int getHeaderLength() {
    return 8;
  }

  @Override
  protected String getMessageName() {
    return "Start-Up";
  }

  @Override
  protected String getPayloadString() {
    return new MessageFormat("Length: {0}, " + "Parameters: {1}")
        .format(new Object[] {this.length, this.parameters.toString()});
  }

  @Override
  public String getIdentifier() {
    return Integer.toString(IDENTIFIER);
  }

  public Map getParameters() {
    return parameters;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy