Skip to content

Abnormal after receiving Continuation Frame #49

@Ambert1108

Description

@Ambert1108

Hello author, I am using oatpp-ws 1.3.0 version, and it seems that the warehouse has not been updated for a long time.

I recently encountered a problem using oatpp-ws: when the other end sends a text frame, a continuous frame, and a text frame, the ws listening will be disconnected and an error occurs, what happen?

[oatpp::web::protocol::websocket::WebSocket::listen()]:Unhandled error occurred. Message='[oatpp::web::protocol::websocket::WebSocket::handleFrame()]: Invalid communication state. OPCODE_CONTINUATION expected'

Analyze

To solve this problem, I checked the source code, as shown below:

bool WebSocket::checkForContinuation(const Frame::Header& frameHeader) {
  if(m_lastOpcode == Frame::OPCODE_TEXT || m_lastOpcode == Frame::OPCODE_BINARY) {
    return false;
  }
  if(frameHeader.fin) {
    m_lastOpcode = -1;
  } else {
    m_lastOpcode = frameHeader.opcode;
  }
  return true;
}

void WebSocket::handleFrame(const Frame::Header& frameHeader) {
  
  switch (frameHeader.opcode) {
    case Frame::OPCODE_CONTINUATION:
      if(m_lastOpcode < 0) {
        throw std::runtime_error("[oatpp::web::protocol::websocket::WebSocket::handleFrame()]: Invalid communication state.");
      }
      readPayload(frameHeader, nullptr);
      break;
      
    case Frame::OPCODE_TEXT:
      if(checkForContinuation(frameHeader)) {
        readPayload(frameHeader, nullptr);
      } else {
        throw std::runtime_error("[oatpp::web::protocol::websocket::WebSocket::handleFrame()]: Invalid communication state. OPCODE_CONTINUATION expected");
      }
      break;
      
    case Frame::OPCODE_BINARY:
      if(checkForContinuation(frameHeader)) {
        readPayload(frameHeader, nullptr);
      } else {
        throw std::runtime_error("[oatpp::web::protocol::websocket::WebSocket::handleFrame()]: Invalid communication state. OPCODE_CONTINUATION expected");
      }
      break;
          
   //part of code
}
    
void WebSocket::listen() {
  m_listening = true;
  try {
    Frame::Header frameHeader;
    do {
      readFrameHeader(frameHeader);
      handleFrame(frameHeader);
    } while(frameHeader.opcode != Frame::OPCODE_CLOSE && m_listening);
  } catch(const std::runtime_error& error) {
    OATPP_LOGD("[oatpp::web::protocol::websocket::WebSocket::listen()]", "Unhandled error occurred. Message='%s'", error.what());
  } catch(...) {
    OATPP_LOGD("[oatpp::web::protocol::websocket::WebSocket::listen()]", "Unhandled error occurred");
  }
}

As you can see, after we call the listen method of Websocket, it will continuously try to read and process frames, and enter handleFrame when a frame is received.

In handleFrame, the frame type is determined. When the received frame is a text frame or a binary frame, oatpp will determine m_lastOpcode. If m_lastOpcode is a text frame or a binary frame, an exception will be thrown. Otherwise, if this frame is the last frame, m_lastOpcode is reset to -1; if this frame is not the last frame, m_lastOpcode is set to the type of this frame.

  1. The program listens to a text frame (fin=false). Set m_lastOpcode to OPCODE_TEXT, which is the text frame type. Read the payload and save it.

  2. The program listens to a continuation frame (fin=true). Take out the payload, combine it with the previous payload, and pass it to the user.

  3. The program listens to a text frame (fin=true). Judge that m_lastOpcode=OPCODE_TEXT and throw an exception.

My fixed

At this point, we know that this problem occurs because: oatpp does not reset m_lastOpcode to -1 after processing the continuation frame and determining that the continuation frame is the last frame (fin=true).

Therefore, after processing the continuation frame, reset m_lastOpcode to -1, and the problem is solved.

void WebSocket::handleFrame(const Frame::Header& frameHeader) {
  
  switch (frameHeader.opcode) {
    case Frame::OPCODE_CONTINUATION:
      if(m_lastOpcode < 0) {
        throw std::runtime_error("[oatpp::web::protocol::websocket::WebSocket::handleFrame()]: Invalid communication state.");
      }
      readPayload(frameHeader, nullptr);
      m_lastOpcode = -1; //here we are
      break;
      
    case Frame::OPCODE_TEXT:
      if(checkForContinuation(frameHeader)) {
        readPayload(frameHeader, nullptr);
      } else {
        throw std::runtime_error("[oatpp::web::protocol::websocket::WebSocket::handleFrame()]: Invalid communication state. OPCODE_CONTINUATION expected");
      }
      break;
      
    case Frame::OPCODE_BINARY:
      if(checkForContinuation(frameHeader)) {
        readPayload(frameHeader, nullptr);
      } else {
        throw std::runtime_error("[oatpp::web::protocol::websocket::WebSocket::handleFrame()]: Invalid communication state. OPCODE_CONTINUATION expected");
      }
      break;
          
   //part of code
}

However, my approach probably does not fully consider the overall logic of the program design. If the author sees this, please answer two questions:

  1. Does the above problem exist?
  2. If so, is there a plan to fix this problem?

THANKS!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions