// Copyright (c) 2010 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 <algorithm>
#include <limits>

#include "net/websockets/websocket_frame_handler.h"

#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"

namespace net {

WebSocketFrameHandler::WebSocketFrameHandler()
    : current_buffer_size_(0),
      original_current_buffer_size_(0) {
}

WebSocketFrameHandler::~WebSocketFrameHandler() {
}

void WebSocketFrameHandler::AppendData(const char* data, int length) {
  scoped_refptr<IOBufferWithSize> buffer = new IOBufferWithSize(length);
  memcpy(buffer->data(), data, length);
  pending_buffers_.push_back(buffer);
}

int WebSocketFrameHandler::UpdateCurrentBuffer(bool buffered) {
  if (current_buffer_)
    return 0;
  DCHECK(!current_buffer_size_);
  DCHECK(!original_current_buffer_size_);

  if (pending_buffers_.empty())
    return 0;
  scoped_refptr<IOBufferWithSize> buffer = pending_buffers_.front();

  int buffer_size = 0;
  if (buffered) {
    std::vector<FrameInfo> frame_info;
    buffer_size =
        ParseWebSocketFrame(buffer->data(), buffer->size(), &frame_info);
    if (buffer_size <= 0)
      return buffer_size;

    original_current_buffer_size_ = buffer_size;

    // TODO(ukai): filter(e.g. compress or decompress) frame messages.
  } else {
    original_current_buffer_size_ = buffer->size();
    buffer_size = buffer->size();
  }

  current_buffer_ = buffer;
  current_buffer_size_ = buffer_size;
  return buffer_size;
}

void WebSocketFrameHandler::ReleaseCurrentBuffer() {
  DCHECK(!pending_buffers_.empty());
  scoped_refptr<IOBufferWithSize> front_buffer = pending_buffers_.front();
  pending_buffers_.pop_front();
  int remaining_size = front_buffer->size() - original_current_buffer_size_;
  if (remaining_size > 0) {
    scoped_refptr<IOBufferWithSize> next_buffer = NULL;
    int buffer_size = remaining_size;
    if (!pending_buffers_.empty()) {
      next_buffer = pending_buffers_.front();
      buffer_size += next_buffer->size();
      pending_buffers_.pop_front();
    }
    // TODO(ukai): don't copy data.
    scoped_refptr<IOBufferWithSize> buffer = new IOBufferWithSize(buffer_size);
    memcpy(buffer->data(), front_buffer->data() + original_current_buffer_size_,
           remaining_size);
    if (next_buffer)
      memcpy(buffer->data() + remaining_size,
             next_buffer->data(), next_buffer->size());
    pending_buffers_.push_front(buffer);
  }
  current_buffer_ = NULL;
  current_buffer_size_ = 0;
  original_current_buffer_size_ = 0;
}

/* static */
int WebSocketFrameHandler::ParseWebSocketFrame(
    const char* buffer, int size, std::vector<FrameInfo>* frame_info) {
  const char* end = buffer + size;
  const char* p = buffer;
  int buffer_size = 0;
  while (p < end) {
    FrameInfo frame;
    frame.frame_start = p;
    frame.message_length = -1;
    unsigned char frame_byte = static_cast<unsigned char>(*p++);
    if ((frame_byte & 0x80) == 0x80) {
      int length = 0;
      while (p < end) {
        // Note: might overflow later if numeric_limits<int>::max() is not
        // n*128-1.
        if (length > std::numeric_limits<int>::max() / 128) {
          // frame length overflow.
          return ERR_INSUFFICIENT_RESOURCES;
        }
        unsigned char c = static_cast<unsigned char>(*p);
        length = length * 128 + (c & 0x7f);
        ++p;
        if ((c & 0x80) != 0x80)
          break;
      }
      if (end - p >= length) {
        frame.message_start = p;
        frame.message_length = length;
        p += length;
      } else {
        break;
      }
    } else {
      frame.message_start = p;
      while (p < end && *p != '\xff')
        ++p;
      if (p < end && *p == '\xff') {
        frame.message_length = p - frame.message_start;
        ++p;
      } else {
        break;
      }
    }
    if (frame.message_length >= 0 && p <= end) {
      frame.frame_length = p - frame.frame_start;
      buffer_size += frame.frame_length;
      frame_info->push_back(frame);
    }
  }
  return buffer_size;
}

}  // namespace net