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

io.grpc.alts.internal.AltsFraming Maven / Gradle / Ivy

There is a newer version: 1.68.1
Show newest version
/*
 * Copyright 2018 The gRPC Authors
 *
 * 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 io.grpc.alts.internal;

import com.google.common.base.Preconditions;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.GeneralSecurityException;

/** Framing and deframing methods and classes used by handshaker. */
public final class AltsFraming {
  // The size of the frame field. Must correspond to the size of int, 4 bytes.
  // Left package-private for testing.
  private static final int FRAME_LENGTH_HEADER_SIZE = 4;
  private static final int FRAME_MESSAGE_TYPE_HEADER_SIZE = 4;
  private static final int MAX_DATA_LENGTH = 1024 * 1024;
  private static final int INITIAL_BUFFER_CAPACITY = 1024 * 64;

  // TODO: Make this the responsibility of the caller.
  private static final int MESSAGE_TYPE = 6;

  private AltsFraming() {}

  static int getFrameLengthHeaderSize() {
    return FRAME_LENGTH_HEADER_SIZE;
  }

  static int getFrameMessageTypeHeaderSize() {
    return FRAME_MESSAGE_TYPE_HEADER_SIZE;
  }

  static int getMaxDataLength() {
    return MAX_DATA_LENGTH;
  }

  static int getFramingOverhead() {
    return FRAME_LENGTH_HEADER_SIZE + FRAME_MESSAGE_TYPE_HEADER_SIZE;
  }

  /**
   * Creates a frame of length dataSize + FRAME_HEADER_SIZE using the input bytes, if dataSize <=
   * input.remaining(). Otherwise, a frame of length input.remaining() + FRAME_HEADER_SIZE is
   * created.
   */
  static ByteBuffer toFrame(ByteBuffer input, int dataSize) throws GeneralSecurityException {
    Preconditions.checkNotNull(input);
    if (dataSize > input.remaining()) {
      dataSize = input.remaining();
    }
    Producer producer = new Producer();
    ByteBuffer inputAlias = input.duplicate();
    ((Buffer) inputAlias).limit(input.position() + dataSize);
    producer.readBytes(inputAlias);
    producer.flush();
    ((Buffer) input).position(inputAlias.position());
    ByteBuffer output = producer.getRawFrame();
    return output;
  }

  /**
   * A helper class to write a frame.
   *
   * 

This class guarantees that one of the following is true: * *

    *
  • readBytes will read from the input *
  • writeBytes will write to the output *
* *

Sample usage: * *

{@code
   * Producer producer = new Producer();
   * ByteBuffer inputBuffer = readBytesFromMyStream();
   * ByteBuffer outputBuffer = writeBytesToMyStream();
   * while (inputBuffer.hasRemaining() || outputBuffer.hasRemaining()) {
   *   producer.readBytes(inputBuffer);
   *   producer.writeBytes(outputBuffer);
   * }
   * }
* *

Alternatively, this class guarantees that one of the following is true: * *

    *
  • readBytes will read from the input *
  • {@code isComplete()} returns true and {@code getByteBuffer()} returns the contents of a * processed frame. *
* *

Sample usage: * *

{@code
   * Producer producer = new Producer();
   * while (!producer.isComplete()) {
   *   ByteBuffer inputBuffer = readBytesFromMyStream();
   *   producer.readBytes(inputBuffer);
   * }
   * producer.flush();
   * ByteBuffer outputBuffer = producer.getRawFrame();
   * }
*/ static final class Producer { private ByteBuffer buffer; private boolean isComplete; Producer(int maxFrameSize) { buffer = ByteBuffer.allocate(maxFrameSize); reset(); Preconditions.checkArgument(maxFrameSize > getFramePrefixLength() + getFrameSuffixLength()); } Producer() { this(INITIAL_BUFFER_CAPACITY); } /** The length of the frame prefix data, including the message length/type fields. */ int getFramePrefixLength() { int result = FRAME_LENGTH_HEADER_SIZE + FRAME_MESSAGE_TYPE_HEADER_SIZE; return result; } int getFrameSuffixLength() { return 0; } /** * Reads bytes from input, parsing them into a frame. Returns false if and only if more data is * needed. To obtain a full frame this method must be called repeatedly until it returns true. */ boolean readBytes(ByteBuffer input) throws GeneralSecurityException { Preconditions.checkNotNull(input); if (isComplete) { return true; } copy(buffer, input); if (!buffer.hasRemaining()) { flush(); } return isComplete; } /** * Completes the current frame, signaling that no further data is available to be passed to * readBytes and that the client requires writeBytes to start returning data. isComplete() is * guaranteed to return true after this call. */ void flush() throws GeneralSecurityException { if (isComplete) { return; } // Get the length of the complete frame. int frameLength = buffer.position() + getFrameSuffixLength(); // Set the limit and move to the start. ((Buffer) buffer).flip(); // Advance the limit to allow a crypto suffix. ((Buffer) buffer).limit(buffer.limit() + getFrameSuffixLength()); // Write the data length and the message type. int dataLength = frameLength - FRAME_LENGTH_HEADER_SIZE; buffer.order(ByteOrder.LITTLE_ENDIAN); buffer.putInt(dataLength); buffer.putInt(MESSAGE_TYPE); // Move the position back to 0, the frame is ready. ((Buffer) buffer).position(0); isComplete = true; } /** Resets the state, preparing to construct a new frame. Must be called between frames. */ private void reset() { ((Buffer) buffer).clear(); // Save some space for framing, we'll fill that in later. ((Buffer) buffer).position(getFramePrefixLength()); ((Buffer) buffer).limit(buffer.limit() - getFrameSuffixLength()); isComplete = false; } /** * Returns a ByteBuffer containing a complete raw frame, if it's available. Should only be * called when isComplete() returns true, otherwise null is returned. The returned object * aliases the internal buffer, that is, it shares memory with the internal buffer. No further * operations are permitted on this object until the caller has processed the data it needs from * the returned byte buffer. */ ByteBuffer getRawFrame() { if (!isComplete) { return null; } ByteBuffer result = buffer.duplicate(); reset(); return result; } } /** * A helper class to read a frame. * *

This class guarantees that one of the following is true: * *

    *
  • readBytes will read from the input *
  • writeBytes will write to the output *
* *

Sample usage: * *

{@code
   * Parser parser = new Parser();
   * ByteBuffer inputBuffer = readBytesFromMyStream();
   * ByteBuffer outputBuffer = writeBytesToMyStream();
   * while (inputBuffer.hasRemaining() || outputBuffer.hasRemaining()) {
   *   parser.readBytes(inputBuffer);
   *   parser.writeBytes(outputBuffer); }
   * }
* *

Alternatively, this class guarantees that one of the following is true: * *

    *
  • readBytes will read from the input *
  • {@code isComplete()} returns true and {@code getByteBuffer()} returns the contents of a * processed frame. *
* *

Sample usage: * *

{@code
   * Parser parser = new Parser();
   * while (!parser.isComplete()) {
   *   ByteBuffer inputBuffer = readBytesFromMyStream();
   *   parser.readBytes(inputBuffer);
   * }
   * ByteBuffer outputBuffer = parser.getRawFrame();
   * }
*/ public static final class Parser { private ByteBuffer buffer = ByteBuffer.allocate(INITIAL_BUFFER_CAPACITY); private boolean isComplete = false; public Parser() { Preconditions.checkArgument( INITIAL_BUFFER_CAPACITY > getFramePrefixLength() + getFrameSuffixLength()); } /** * Reads bytes from input, parsing them into a frame. Returns false if and only if more data is * needed. To obtain a full frame this method must be called repeatedly until it returns true. */ public boolean readBytes(ByteBuffer input) throws GeneralSecurityException { Preconditions.checkNotNull(input); if (isComplete) { return true; } // Read enough bytes to determine the length while (buffer.position() < FRAME_LENGTH_HEADER_SIZE && input.hasRemaining()) { buffer.put(input.get()); } // If we have enough bytes to determine the length, read the length and ensure that our // internal buffer is large enough. if (buffer.position() == FRAME_LENGTH_HEADER_SIZE && input.hasRemaining()) { ByteBuffer bufferAlias = buffer.duplicate(); ((Buffer) bufferAlias).flip(); bufferAlias.order(ByteOrder.LITTLE_ENDIAN); int dataLength = bufferAlias.getInt(); if (dataLength < FRAME_MESSAGE_TYPE_HEADER_SIZE || dataLength > MAX_DATA_LENGTH) { throw new IllegalArgumentException("Invalid frame length " + dataLength); } // Maybe resize the buffer int frameLength = dataLength + FRAME_LENGTH_HEADER_SIZE; if (buffer.capacity() < frameLength) { buffer = ByteBuffer.allocate(frameLength); buffer.order(ByteOrder.LITTLE_ENDIAN); buffer.putInt(dataLength); } ((Buffer) buffer).limit(frameLength); } // TODO: Similarly extract and check message type. // Read the remaining data into the internal buffer. copy(buffer, input); if (!buffer.hasRemaining()) { ((Buffer) buffer).flip(); isComplete = true; } return isComplete; } /** The length of the frame prefix data, including the message length/type fields. */ int getFramePrefixLength() { int result = FRAME_LENGTH_HEADER_SIZE + FRAME_MESSAGE_TYPE_HEADER_SIZE; return result; } int getFrameSuffixLength() { return 0; } /** Returns true if we've parsed a complete frame. */ public boolean isComplete() { return isComplete; } /** Resets the state, preparing to parse a new frame. Must be called between frames. */ private void reset() { ((Buffer) buffer).clear(); isComplete = false; } /** * Returns a ByteBuffer containing a complete raw frame, if it's available. Should only be * called when isComplete() returns true, otherwise null is returned. The returned object * aliases the internal buffer, that is, it shares memory with the internal buffer. No further * operations are permitted on this object until the caller has processed the data it needs from * the returned byte buffer. */ public ByteBuffer getRawFrame() { if (!isComplete) { return null; } ByteBuffer result = buffer.duplicate(); reset(); return result; } } /** * Copy as much as possible to dst from src. Unlike {@link ByteBuffer#put(ByteBuffer)}, this stops * early if there is no room left in dst. */ private static void copy(ByteBuffer dst, ByteBuffer src) { if (dst.hasRemaining() && src.hasRemaining()) { // Avoid an allocation if possible. if (dst.remaining() >= src.remaining()) { dst.put(src); } else { int count = Math.min(dst.remaining(), src.remaining()); ByteBuffer slice = src.slice(); ((Buffer) slice).limit(count); dst.put(slice); ((Buffer) src).position(src.position() + count); } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy