/*
 * Copyright (C) 2014 Square, Inc.
 *
 * 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 okhttp3.internal.ws;

import okio.Buffer;
import okio.ByteString;

public final class WebSocketProtocol {
  /** Magic value which must be appended to the key in a response header. */
  static final String ACCEPT_MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

  /*
  Each frame starts with two bytes of data.

   0 1 2 3 4 5 6 7    0 1 2 3 4 5 6 7
  +-+-+-+-+-------+  +-+-------------+
  |F|R|R|R| OP    |  |M| LENGTH      |
  |I|S|S|S| CODE  |  |A|             |
  |N|V|V|V|       |  |S|             |
  | |1|2|3|       |  |K|             |
  +-+-+-+-+-------+  +-+-------------+
  */

  /** Byte 0 flag for whether this is the final fragment in a message. */
  static final int B0_FLAG_FIN = 0b10000000;
  /** Byte 0 reserved flag 1. Must be 0 unless negotiated otherwise. */
  static final int B0_FLAG_RSV1 = 0b01000000;
  /** Byte 0 reserved flag 2. Must be 0 unless negotiated otherwise. */
  static final int B0_FLAG_RSV2 = 0b00100000;
  /** Byte 0 reserved flag 3. Must be 0 unless negotiated otherwise. */
  static final int B0_FLAG_RSV3 = 0b00010000;
  /** Byte 0 mask for the frame opcode. */
  static final int B0_MASK_OPCODE = 0b00001111;
  /** Flag in the opcode which indicates a control frame. */
  static final int OPCODE_FLAG_CONTROL = 0b00001000;

  /**
   * Byte 1 flag for whether the payload data is masked. <p> If this flag is set, the next four
   * bytes represent the mask key. These bytes appear after any additional bytes specified by {@link
   * #B1_MASK_LENGTH}.
   */
  static final int B1_FLAG_MASK = 0b10000000;
  /**
   * Byte 1 mask for the payload length. <p> If this value is {@link #PAYLOAD_SHORT}, the next two
   * bytes represent the length. If this value is {@link #PAYLOAD_LONG}, the next eight bytes
   * represent the length.
   */
  static final int B1_MASK_LENGTH = 0b01111111;

  static final int OPCODE_CONTINUATION = 0x0;
  static final int OPCODE_TEXT = 0x1;
  static final int OPCODE_BINARY = 0x2;

  static final int OPCODE_CONTROL_CLOSE = 0x8;
  static final int OPCODE_CONTROL_PING = 0x9;
  static final int OPCODE_CONTROL_PONG = 0xa;

  /**
   * Maximum length of frame payload. Larger payloads, if supported by the frame type, can use the
   * special values {@link #PAYLOAD_SHORT} or {@link #PAYLOAD_LONG}.
   */
  static final long PAYLOAD_BYTE_MAX = 125L;
  /** Maximum length of close message in bytes. */
  static final long CLOSE_MESSAGE_MAX = PAYLOAD_BYTE_MAX - 2;
  /**
   * Value for {@link #B1_MASK_LENGTH} which indicates the next two bytes are the unsigned length.
   */
  static final int PAYLOAD_SHORT = 126;
  /** Maximum length of a frame payload to be denoted as {@link #PAYLOAD_SHORT}. */
  static final long PAYLOAD_SHORT_MAX = 0xffffL;
  /**
   * Value for {@link #B1_MASK_LENGTH} which indicates the next eight bytes are the unsigned
   * length.
   */
  static final int PAYLOAD_LONG = 127;

  /** Used when an unchecked exception was thrown in a listener. */
  static final int CLOSE_CLIENT_GOING_AWAY = 1001;
  /** Used when an empty close frame was received (i.e., without a status code). */
  static final int CLOSE_NO_STATUS_CODE = 1005;

  static void toggleMask(Buffer.UnsafeCursor cursor, byte[] key) {
    int keyIndex = 0;
    int keyLength = key.length;
    do {
      byte[] buffer = cursor.data;
      for (int i = cursor.start, end = cursor.end; i < end; i++, keyIndex++) {
        keyIndex %= keyLength; // Reassign to prevent overflow breaking counter.
        buffer[i] = (byte) (buffer[i] ^ key[keyIndex]);
      }
    } while (cursor.next() != -1);
  }

  static String closeCodeExceptionMessage(int code) {
    if (code < 1000 || code >= 5000) {
      return "Code must be in range [1000,5000): " + code;
    } else if ((code >= 1004 && code <= 1006) || (code >= 1012 && code <= 2999)) {
      return "Code " + code + " is reserved and may not be used.";
    } else {
      return null;
    }
  }

  static void validateCloseCode(int code) {
    String message = closeCodeExceptionMessage(code);
    if (message != null) throw new IllegalArgumentException(message);
  }

  public static String acceptHeader(String key) {
    return ByteString.encodeUtf8(key + WebSocketProtocol.ACCEPT_MAGIC).sha1().base64();
  }

  private WebSocketProtocol() {
    throw new AssertionError("No instances.");
  }
}
