// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/spdy/spdy_protocol.h"

namespace net {

SpdyFrameWithNameValueBlockIR::SpdyFrameWithNameValueBlockIR(
    SpdyStreamId stream_id) : SpdyFrameWithFinIR(stream_id) {}

SpdyFrameWithNameValueBlockIR::~SpdyFrameWithNameValueBlockIR() {}

SpdyDataIR::SpdyDataIR(SpdyStreamId stream_id, const base::StringPiece& data)
    : SpdyFrameWithFinIR(stream_id),
      pad_low_(false),
      pad_high_(false),
      padding_payload_len_(0) {
  SetDataDeep(data);
}

SpdyDataIR::SpdyDataIR(SpdyStreamId stream_id)
    : SpdyFrameWithFinIR(stream_id),
      pad_low_(false),
      pad_high_(false),
      padding_payload_len_(0) {}

SpdyDataIR::~SpdyDataIR() {}

bool SpdyConstants::IsValidFrameType(SpdyMajorVersion version,
                                     int frame_type_field) {
  switch (version) {
    case SPDY2:
    case SPDY3:
      // SYN_STREAM is the first valid frame.
      if (frame_type_field < SerializeFrameType(version, SYN_STREAM)) {
        return false;
      }

      // WINDOW_UPDATE is the last valid frame.
      if (frame_type_field > SerializeFrameType(version, WINDOW_UPDATE)) {
        return false;
      }

      // The valid range is non-contiguous.
      if (frame_type_field == NOOP) {
        return false;
      }

      return true;
    case SPDY4:
    case SPDY5:
      // DATA is the first valid frame.
      if (frame_type_field < SerializeFrameType(version, DATA)) {
        return false;
      }

      // BLOCKED is the last valid frame.
      if (frame_type_field > SerializeFrameType(version, BLOCKED)) {
        return false;
      }

      return true;
  }

  LOG(DFATAL) << "Unhandled SPDY version " << version;
  return false;
}

SpdyFrameType SpdyConstants::ParseFrameType(SpdyMajorVersion version,
                                            int frame_type_field) {
  switch (version) {
    case SPDY2:
    case SPDY3:
      switch (frame_type_field) {
        case 1:
          return SYN_STREAM;
        case 2:
          return SYN_REPLY;
        case 3:
          return RST_STREAM;
        case 4:
          return SETTINGS;
        case 6:
          return PING;
        case 7:
          return GOAWAY;
        case 8:
          return HEADERS;
        case 9:
          return WINDOW_UPDATE;
      }
      break;
    case SPDY4:
    case SPDY5:
      switch (frame_type_field) {
        case 0:
          return DATA;
        case 1:
          return HEADERS;
        case 2:
          return PRIORITY;
        case 3:
          return RST_STREAM;
        case 4:
          return SETTINGS;
        case 5:
          return PUSH_PROMISE;
        case 6:
          return PING;
        case 7:
          return GOAWAY;
        case 8:
          return WINDOW_UPDATE;
        case 9:
          return CONTINUATION;
        case 10:
          return ALTSVC;
        case 11:
          return BLOCKED;
      }
      break;
  }

  LOG(DFATAL) << "Unhandled frame type " << frame_type_field;
  return DATA;
}

int SpdyConstants::SerializeFrameType(SpdyMajorVersion version,
                                      SpdyFrameType frame_type) {
  switch (version) {
    case SPDY2:
    case SPDY3:
      switch (frame_type) {
        case SYN_STREAM:
          return 1;
        case SYN_REPLY:
          return 2;
        case RST_STREAM:
          return 3;
        case SETTINGS:
          return 4;
        case PING:
          return 6;
        case GOAWAY:
          return 7;
        case HEADERS:
          return 8;
        case WINDOW_UPDATE:
          return 9;
        default:
          LOG(DFATAL) << "Serializing unhandled frame type " << frame_type;
          return -1;
      }
    case SPDY4:
    case SPDY5:
      switch (frame_type) {
        case DATA:
          return 0;
        case HEADERS:
          return 1;
        case PRIORITY:
          return 2;
        case RST_STREAM:
          return 3;
        case SETTINGS:
          return 4;
        case PUSH_PROMISE:
          return 5;
        case PING:
          return 6;
        case GOAWAY:
          return 7;
        case WINDOW_UPDATE:
          return 8;
        case CONTINUATION:
          return 9;
        case ALTSVC:
          return 10;
        case BLOCKED:
          return 11;
        default:
          LOG(DFATAL) << "Serializing unhandled frame type " << frame_type;
          return -1;
      }
  }

  LOG(DFATAL) << "Unhandled SPDY version " << version;
  return -1;
}

bool SpdyConstants::IsValidSettingId(SpdyMajorVersion version,
                                     int setting_id_field) {
  switch (version) {
    case SPDY2:
    case SPDY3:
      // UPLOAD_BANDWIDTH is the first valid setting id.
      if (setting_id_field <
          SerializeSettingId(version, SETTINGS_UPLOAD_BANDWIDTH)) {
        return false;
      }

      // INITIAL_WINDOW_SIZE is the last valid setting id.
      if (setting_id_field >
          SerializeSettingId(version, SETTINGS_INITIAL_WINDOW_SIZE)) {
        return false;
      }

      return true;
    case SPDY4:
    case SPDY5:
      // HEADER_TABLE_SIZE is the first valid setting id.
      if (setting_id_field <
          SerializeSettingId(version, SETTINGS_HEADER_TABLE_SIZE)) {
        return false;
      }

      // COMPRESS_DATA is the last valid setting id.
      if (setting_id_field >
          SerializeSettingId(version, SETTINGS_COMPRESS_DATA)) {
        return false;
      }

      return true;
  }

  LOG(DFATAL) << "Unhandled SPDY version " << version;
  return false;
}

SpdySettingsIds SpdyConstants::ParseSettingId(SpdyMajorVersion version,
                                              int setting_id_field) {
  switch (version) {
    case SPDY2:
    case SPDY3:
      switch (setting_id_field) {
        case 1:
          return SETTINGS_UPLOAD_BANDWIDTH;
        case 2:
          return SETTINGS_DOWNLOAD_BANDWIDTH;
        case 3:
          return SETTINGS_ROUND_TRIP_TIME;
        case 4:
          return SETTINGS_MAX_CONCURRENT_STREAMS;
        case 5:
          return SETTINGS_CURRENT_CWND;
        case 6:
          return SETTINGS_DOWNLOAD_RETRANS_RATE;
        case 7:
          return SETTINGS_INITIAL_WINDOW_SIZE;
      }
      break;
    case SPDY4:
    case SPDY5:
      switch (setting_id_field) {
        case 1:
          return SETTINGS_HEADER_TABLE_SIZE;
        case 2:
          return SETTINGS_ENABLE_PUSH;
        case 3:
          return SETTINGS_MAX_CONCURRENT_STREAMS;
        case 4:
          return SETTINGS_INITIAL_WINDOW_SIZE;
        case 5:
          return SETTINGS_COMPRESS_DATA;
      }
      break;
  }

  LOG(DFATAL) << "Unhandled setting ID " << setting_id_field;
  return SETTINGS_UPLOAD_BANDWIDTH;
}

int SpdyConstants::SerializeSettingId(SpdyMajorVersion version,
                                       SpdySettingsIds id) {
  switch (version) {
    case SPDY2:
    case SPDY3:
      switch (id) {
        case SETTINGS_UPLOAD_BANDWIDTH:
          return 1;
        case SETTINGS_DOWNLOAD_BANDWIDTH:
          return 2;
        case SETTINGS_ROUND_TRIP_TIME:
          return 3;
        case SETTINGS_MAX_CONCURRENT_STREAMS:
          return 4;
        case SETTINGS_CURRENT_CWND:
          return 5;
        case SETTINGS_DOWNLOAD_RETRANS_RATE:
          return 6;
        case SETTINGS_INITIAL_WINDOW_SIZE:
          return 7;
        default:
          LOG(DFATAL) << "Serializing unhandled setting id " << id;
          return -1;
      }
    case SPDY4:
    case SPDY5:
      switch (id) {
        case SETTINGS_HEADER_TABLE_SIZE:
          return 1;
        case SETTINGS_ENABLE_PUSH:
          return 2;
        case SETTINGS_MAX_CONCURRENT_STREAMS:
          return 3;
        case SETTINGS_INITIAL_WINDOW_SIZE:
          return 4;
        case SETTINGS_COMPRESS_DATA:
          return 5;
        default:
          LOG(DFATAL) << "Serializing unhandled setting id " << id;
          return -1;
      }
  }
  LOG(DFATAL) << "Unhandled SPDY version " << version;
  return -1;
}

bool SpdyConstants::IsValidRstStreamStatus(SpdyMajorVersion version,
                                           int rst_stream_status_field) {
  switch (version) {
    case SPDY2:
    case SPDY3:
      // PROTOCOL_ERROR is the valid first status code.
      if (rst_stream_status_field <
          SerializeRstStreamStatus(version, RST_STREAM_PROTOCOL_ERROR)) {
        return false;
      }

      // FRAME_TOO_LARGE is the valid last status code.
      if (rst_stream_status_field >
          SerializeRstStreamStatus(version, RST_STREAM_FRAME_TOO_LARGE)) {
        return false;
      }

      return true;
    case SPDY4:
    case SPDY5:
      // NO_ERROR is the first valid status code.
      if (rst_stream_status_field <
          SerializeRstStreamStatus(version, RST_STREAM_PROTOCOL_ERROR)) {
        return false;
      }

      // TODO(hkhalil): Omit COMPRESSION_ERROR and SETTINGS_TIMEOUT
      /*
      // This works because GOAWAY and RST_STREAM share a namespace.
      if (rst_stream_status_field ==
          SerializeGoAwayStatus(version, GOAWAY_COMPRESSION_ERROR) ||
          rst_stream_status_field ==
          SerializeGoAwayStatus(version, GOAWAY_SETTINGS_TIMEOUT)) {
        return false;
      }
      */

      // ENHANCE_YOUR_CALM is the last valid status code.
      if (rst_stream_status_field >
          SerializeRstStreamStatus(version, RST_STREAM_ENHANCE_YOUR_CALM)) {
        return false;
      }

      return true;
  }
  LOG(DFATAL) << "Unhandled SPDY version " << version;
  return false;
}

SpdyRstStreamStatus SpdyConstants::ParseRstStreamStatus(
    SpdyMajorVersion version,
    int rst_stream_status_field) {
  switch (version) {
    case SPDY2:
    case SPDY3:
      switch (rst_stream_status_field) {
        case 1:
          return RST_STREAM_PROTOCOL_ERROR;
        case 2:
          return RST_STREAM_INVALID_STREAM;
        case 3:
          return RST_STREAM_REFUSED_STREAM;
        case 4:
          return RST_STREAM_UNSUPPORTED_VERSION;
        case 5:
          return RST_STREAM_CANCEL;
        case 6:
          return RST_STREAM_INTERNAL_ERROR;
        case 7:
          return RST_STREAM_FLOW_CONTROL_ERROR;
        case 8:
          return RST_STREAM_STREAM_IN_USE;
        case 9:
          return RST_STREAM_STREAM_ALREADY_CLOSED;
        case 10:
          return RST_STREAM_INVALID_CREDENTIALS;
        case 11:
          return RST_STREAM_FRAME_TOO_LARGE;
      }
      break;
    case SPDY4:
    case SPDY5:
      switch (rst_stream_status_field) {
        case 1:
          return RST_STREAM_PROTOCOL_ERROR;
        case 2:
          return RST_STREAM_INTERNAL_ERROR;
        case 3:
          return RST_STREAM_FLOW_CONTROL_ERROR;
        case 5:
          return RST_STREAM_STREAM_CLOSED;
        case 6:
          return RST_STREAM_FRAME_SIZE_ERROR;
        case 7:
          return RST_STREAM_REFUSED_STREAM;
        case 8:
          return RST_STREAM_CANCEL;
        case 10:
          return RST_STREAM_CONNECT_ERROR;
        case 11:
          return RST_STREAM_ENHANCE_YOUR_CALM;
      }
      break;
  }

  LOG(DFATAL) << "Invalid RST_STREAM status " << rst_stream_status_field;
  return RST_STREAM_PROTOCOL_ERROR;
}

int SpdyConstants::SerializeRstStreamStatus(
    SpdyMajorVersion version,
    SpdyRstStreamStatus rst_stream_status) {
  switch (version) {
    case SPDY2:
    case SPDY3:
      switch (rst_stream_status) {
        case RST_STREAM_PROTOCOL_ERROR:
          return 1;
        case RST_STREAM_INVALID_STREAM:
          return 2;
        case RST_STREAM_REFUSED_STREAM:
          return 3;
        case RST_STREAM_UNSUPPORTED_VERSION:
          return 4;
        case RST_STREAM_CANCEL:
          return 5;
        case RST_STREAM_INTERNAL_ERROR:
          return 6;
        case RST_STREAM_FLOW_CONTROL_ERROR:
          return 7;
        case RST_STREAM_STREAM_IN_USE:
          return 8;
        case RST_STREAM_STREAM_ALREADY_CLOSED:
          return 9;
        case RST_STREAM_INVALID_CREDENTIALS:
          return 10;
        case RST_STREAM_FRAME_TOO_LARGE:
          return 11;
        default:
          LOG(DFATAL) << "Unhandled RST_STREAM status "
                      << rst_stream_status;
          return -1;
      }
    case SPDY4:
    case SPDY5:
      switch (rst_stream_status) {
        case RST_STREAM_PROTOCOL_ERROR:
          return 1;
        case RST_STREAM_INTERNAL_ERROR:
          return 2;
        case RST_STREAM_FLOW_CONTROL_ERROR:
          return 3;
        case RST_STREAM_STREAM_CLOSED:
          return 5;
        case RST_STREAM_FRAME_SIZE_ERROR:
          return 6;
        case RST_STREAM_REFUSED_STREAM:
          return 7;
        case RST_STREAM_CANCEL:
          return 8;
        case RST_STREAM_CONNECT_ERROR:
          return 10;
        case RST_STREAM_ENHANCE_YOUR_CALM:
          return 11;
        default:
          LOG(DFATAL) << "Unhandled RST_STREAM status "
                      << rst_stream_status;
          return -1;
      }
  }
  LOG(DFATAL) << "Unhandled SPDY version " << version;
  return -1;
}

bool SpdyConstants::IsValidGoAwayStatus(SpdyMajorVersion version,
                                        int goaway_status_field) {
  switch (version) {
    case SPDY2:
    case SPDY3:
      // GOAWAY_OK is the first valid status.
      if (goaway_status_field < SerializeGoAwayStatus(version, GOAWAY_OK)) {
        return false;
      }

      // GOAWAY_INTERNAL_ERROR is the last valid status.
      if (goaway_status_field > SerializeGoAwayStatus(version,
                                                      GOAWAY_INTERNAL_ERROR)) {
        return false;
      }

      return true;
    case SPDY4:
    case SPDY5:
      // GOAWAY_NO_ERROR is the first valid status.
      if (goaway_status_field < SerializeGoAwayStatus(version,
                                                      GOAWAY_NO_ERROR)) {
        return false;
      }

      // GOAWAY_INADEQUATE_SECURITY is the last valid status.
      if (goaway_status_field >
          SerializeGoAwayStatus(version, GOAWAY_INADEQUATE_SECURITY)) {
        return false;
      }

      return true;
  }
  LOG(DFATAL) << "Unknown SpdyMajorVersion " << version;
  return false;
}

SpdyGoAwayStatus SpdyConstants::ParseGoAwayStatus(SpdyMajorVersion version,
                                                  int goaway_status_field) {
  switch (version) {
    case SPDY2:
    case SPDY3:
      switch (goaway_status_field) {
        case 0:
          return GOAWAY_OK;
        case 1:
          return GOAWAY_PROTOCOL_ERROR;
        case 2:
          return GOAWAY_INTERNAL_ERROR;
      }
      break;
    case SPDY4:
    case SPDY5:
      switch (goaway_status_field) {
        case 0:
          return GOAWAY_NO_ERROR;
        case 1:
          return GOAWAY_PROTOCOL_ERROR;
        case 2:
          return GOAWAY_INTERNAL_ERROR;
        case 3:
          return GOAWAY_FLOW_CONTROL_ERROR;
        case 4:
          return GOAWAY_SETTINGS_TIMEOUT;
        case 5:
          return GOAWAY_STREAM_CLOSED;
        case 6:
          return GOAWAY_FRAME_SIZE_ERROR;
        case 7:
          return GOAWAY_REFUSED_STREAM;
        case 8:
          return GOAWAY_CANCEL;
        case 9:
          return GOAWAY_COMPRESSION_ERROR;
        case 10:
          return GOAWAY_CONNECT_ERROR;
        case 11:
          return GOAWAY_ENHANCE_YOUR_CALM;
        case 12:
          return GOAWAY_INADEQUATE_SECURITY;
      }
      break;
  }

  LOG(DFATAL) << "Unhandled GOAWAY status " << goaway_status_field;
  return GOAWAY_PROTOCOL_ERROR;
}

SpdyMajorVersion SpdyConstants::ParseMajorVersion(int version_number) {
  switch (version_number) {
    case 2:
      return SPDY2;
    case 3:
      return SPDY3;
    case 4:
      return SPDY4;
    case 5:
      return SPDY5;
    default:
      LOG(DFATAL) << "Unsupported SPDY version number: " << version_number;
      return SPDY3;
  }
}

int SpdyConstants::SerializeMajorVersion(SpdyMajorVersion version) {
  switch (version) {
    case SPDY2:
      return 2;
    case SPDY3:
      return 3;
    case SPDY4:
      return 4;
    case SPDY5:
      return 5;
    default:
      LOG(DFATAL) << "Unsupported SPDY major version: " << version;
      return -1;
  }
}

std::string SpdyConstants::GetVersionString(SpdyMajorVersion version) {
  switch (version) {
    case SPDY2:
      return "spdy/2";
    case SPDY3:
      return "spdy/3";
    case SPDY4:
      return "spdy/4";
    case SPDY5:
      return "spdy/5";
    default:
      LOG(DFATAL) << "Unsupported SPDY major version: " << version;
      return "spdy/3";
  }
}

int SpdyConstants::SerializeGoAwayStatus(SpdyMajorVersion version,
                                         SpdyGoAwayStatus status) {
  switch (version) {
    case SPDY2:
    case SPDY3:
      // TODO(jgraettinger): Merge this back to server-side.
      switch (status) {
        case GOAWAY_NO_ERROR:
          return 0;
        case GOAWAY_PROTOCOL_ERROR:
        case GOAWAY_INTERNAL_ERROR:
        case GOAWAY_FLOW_CONTROL_ERROR:
        case GOAWAY_SETTINGS_TIMEOUT:
        case GOAWAY_STREAM_CLOSED:
        case GOAWAY_FRAME_SIZE_ERROR:
        case GOAWAY_REFUSED_STREAM:
        case GOAWAY_CANCEL:
        case GOAWAY_COMPRESSION_ERROR:
        case GOAWAY_CONNECT_ERROR:
        case GOAWAY_ENHANCE_YOUR_CALM:
        case GOAWAY_INADEQUATE_SECURITY:
          return 1;  // PROTOCOL_ERROR.
        default:
          LOG(DFATAL) << "Serializing unhandled GOAWAY status " << status;
          return -1;
      }
    case SPDY4:
    case SPDY5:
      switch (status) {
        case GOAWAY_NO_ERROR:
          return 0;
        case GOAWAY_PROTOCOL_ERROR:
          return 1;
        case GOAWAY_INTERNAL_ERROR:
          return 2;
        case GOAWAY_FLOW_CONTROL_ERROR:
          return 3;
        case GOAWAY_SETTINGS_TIMEOUT:
          return 4;
        case GOAWAY_STREAM_CLOSED:
          return 5;
        case GOAWAY_FRAME_SIZE_ERROR:
          return 6;
        case GOAWAY_REFUSED_STREAM:
          return 7;
        case GOAWAY_CANCEL:
          return 8;
        case GOAWAY_COMPRESSION_ERROR:
          return 9;
        case GOAWAY_CONNECT_ERROR:
          return 10;
        case GOAWAY_ENHANCE_YOUR_CALM:
          return 11;
        case GOAWAY_INADEQUATE_SECURITY:
          return 12;
        default:
          LOG(DFATAL) << "Serializing unhandled GOAWAY status " << status;
          return -1;
      }
  }
  LOG(DFATAL) << "Unknown SpdyMajorVersion " << version;
  return -1;
}

size_t SpdyConstants::GetDataFrameMinimumSize() {
  return 8;
}

size_t SpdyConstants::GetControlFrameHeaderSize(SpdyMajorVersion version) {
  switch (version) {
    case SPDY2:
    case SPDY3:
    case SPDY4:
    case SPDY5:
      return 8;
  }
  LOG(DFATAL) << "Unhandled SPDY version.";
  return 0;
}

size_t SpdyConstants::GetPrefixLength(SpdyFrameType type,
                                      SpdyMajorVersion version) {
  if (type != DATA) {
     return GetControlFrameHeaderSize(version);
  } else {
     return GetDataFrameMinimumSize();
  }
}

size_t SpdyConstants::GetFrameMaximumSize(SpdyMajorVersion version) {
  if (version < SPDY4) {
    // 24-bit length field plus eight-byte frame header.
    return ((1<<24) - 1) + 8;
  } else {
    // 14-bit length field.
    return (1<<14) - 1;
  }
}

size_t SpdyConstants::GetSizeOfSizeField(SpdyMajorVersion version) {
  return (version < SPDY3) ? sizeof(uint16) : sizeof(uint32);
}

void SpdyDataIR::Visit(SpdyFrameVisitor* visitor) const {
  return visitor->VisitData(*this);
}

void SpdySynStreamIR::Visit(SpdyFrameVisitor* visitor) const {
  return visitor->VisitSynStream(*this);
}

void SpdySynReplyIR::Visit(SpdyFrameVisitor* visitor) const {
  return visitor->VisitSynReply(*this);
}

SpdyRstStreamIR::SpdyRstStreamIR(SpdyStreamId stream_id,
                                 SpdyRstStreamStatus status,
                                 base::StringPiece description)
    : SpdyFrameWithStreamIdIR(stream_id),
      description_(description) {
  set_status(status);
}

SpdyRstStreamIR::~SpdyRstStreamIR() {}

void SpdyRstStreamIR::Visit(SpdyFrameVisitor* visitor) const {
  return visitor->VisitRstStream(*this);
}

SpdySettingsIR::SpdySettingsIR()
    : clear_settings_(false),
      is_ack_(false) {}

SpdySettingsIR::~SpdySettingsIR() {}

void SpdySettingsIR::Visit(SpdyFrameVisitor* visitor) const {
  return visitor->VisitSettings(*this);
}

void SpdyPingIR::Visit(SpdyFrameVisitor* visitor) const {
  return visitor->VisitPing(*this);
}

SpdyGoAwayIR::SpdyGoAwayIR(SpdyStreamId last_good_stream_id,
                           SpdyGoAwayStatus status,
                           const base::StringPiece& description)
    : description_(description) {
      set_last_good_stream_id(last_good_stream_id);
  set_status(status);
}

SpdyGoAwayIR::~SpdyGoAwayIR() {}

const base::StringPiece& SpdyGoAwayIR::description() const {
  return description_;
}

void SpdyGoAwayIR::Visit(SpdyFrameVisitor* visitor) const {
  return visitor->VisitGoAway(*this);
}

void SpdyHeadersIR::Visit(SpdyFrameVisitor* visitor) const {
  return visitor->VisitHeaders(*this);
}

void SpdyWindowUpdateIR::Visit(SpdyFrameVisitor* visitor) const {
  return visitor->VisitWindowUpdate(*this);
}

void SpdyBlockedIR::Visit(SpdyFrameVisitor* visitor) const {
  return visitor->VisitBlocked(*this);
}

void SpdyPushPromiseIR::Visit(SpdyFrameVisitor* visitor) const {
  return visitor->VisitPushPromise(*this);
}

void SpdyContinuationIR::Visit(SpdyFrameVisitor* visitor) const {
  return visitor->VisitContinuation(*this);
}

SpdyAltSvcIR::SpdyAltSvcIR(SpdyStreamId stream_id)
    : SpdyFrameWithStreamIdIR(stream_id),
      max_age_(0),
      port_(0) {}

void SpdyAltSvcIR::Visit(SpdyFrameVisitor* visitor) const {
  return visitor->VisitAltSvc(*this);
}

}  // namespace net