32. 수신 프레임 조립 버퍼의 최대 크기 상한 부재
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오 (서버-클라): TCP 는 스트림이라 한 번의 Read 로 완전한 프레임이
// 도착한다는 보장이 없다. 이 서버는 세션마다 수신 버퍼를 두고, Read 로 들어온
// 바이트를 계속 이어붙이다가(누적) 길이 프리픽스로 표시된 만큼 바이트가
// 모이면 하나의 프레임으로 꺼내 처리한다.
//
// 정상 클라이언트는 항상 작고 완결된 프레임을 보내지만, 네트워크 상황이나
// 악의적/오작동 클라이언트는 프레임의 나머지 부분을 아주 느리게 보내거나
// 아예 보내지 않을 수 있다. 그 사이에도 서버는 이미 도착한 바이트를 세션의
// 누적 버퍼에 계속 붙잡아 둔다.
//
// 요구사항:
// - 정상적인 크기의 프레임은 지금처럼 정상 조립되어야 한다.
// - 한 세션이 프레임을 끝까지 보내지 않거나 비정상적으로 큰 길이를
// 주장하더라도, 그 세션 하나 때문에 서버 전체 메모리가 고갈되어서는
// 안 된다.
//
// 과제: (A)(B) 지점을 검토해 위 요구사항이 깨지는 경로를 찾고 수정하라.
using System;
using System.Collections.Generic;
public sealed class SessionRecvBuffer
{
private readonly List<byte> _accum = new();
// 소켓에서 새로 읽은 바이트 조각을 누적 버퍼에 이어붙인다.
public void OnBytesReceived(byte[] chunk, int length)
{
// (A) 이미 누적된 바이트 크기와 무관하게 항상 이어붙인다.
for (int i = 0; i < length; i++)
_accum.Add(chunk[i]);
}
// 누적 버퍼에서 완성된 프레임을 꺼낼 수 있으면 꺼낸다.
// 프레임 포맷: [4바이트 길이 프리픽스][길이만큼의 페이로드]
public bool TryExtractFrame(out byte[] payload)
{
payload = null;
if (_accum.Count < 4)
return false;
int declaredLen = BitConverter.ToInt32(new[] { _accum[0], _accum[1], _accum[2], _accum[3] }, 0);
// (B) declaredLen 이 비정상적으로 큰 값이어도 그대로 받아들이고,
// 그만큼 모일 때까지 계속 기다린다.
if (_accum.Count < 4 + declaredLen)
return false; // 아직 다 도착하지 않음 — 더 기다린다.
payload = new byte[declaredLen];
for (int i = 0; i < declaredLen; i++)
payload[i] = _accum[4 + i];
_accum.RemoveRange(0, 4 + declaredLen);
return true;
}
} 결함 코드 · C++
// ============================================================================
// 시나리오 (서버-클라)
// ----------------------------------------------------------------------------
// TCP 는 스트림이라 한 번의 recv 로 완전한 프레임이 도착한다는 보장이 없다.
// 이 서버는 세션마다 수신 버퍼를 두고, recv 로 들어온 바이트를 계속
// 이어붙이다가(누적) 길이 프리픽스로 표시된 만큼 바이트가 모이면 하나의
// 프레임으로 꺼내 처리한다.
//
// 정상 클라이언트는 항상 작고 완결된 프레임을 보내지만, 네트워크 상황이나
// 악의적/오작동 클라이언트는 프레임의 나머지 부분을 아주 느리게 보내거나
// 아예 보내지 않을 수 있다. 그 사이에도 서버는 이미 도착한 바이트를 세션의
// 누적 버퍼에 계속 붙잡아 둔다.
//
// 요구사항:
// - 정상적인 크기의 프레임은 지금처럼 정상 조립되어야 한다.
// - 한 세션이 프레임을 끝까지 보내지 않거나 비정상적으로 큰 길이를
// 주장하더라도, 그 세션 하나 때문에 서버 전체 메모리가 고갈되어서는
// 안 된다.
//
// 과제: (A)(B) 지점을 검토해 위 요구사항이 깨지는 경로를 찾고 수정하라.
// ============================================================================
#include <cstdint>
#include <cstring>
#include <vector>
class SessionRecvBuffer
{
public:
// 소켓에서 새로 읽은 바이트 조각을 누적 버퍼에 이어붙인다.
void OnBytesReceived(const uint8_t* chunk, size_t length)
{
// (A) 이미 누적된 바이트 크기와 무관하게 항상 이어붙인다.
accum_.insert(accum_.end(), chunk, chunk + length);
}
// 누적 버퍼에서 완성된 프레임을 꺼낼 수 있으면 꺼낸다.
// 프레임 포맷: [4바이트 길이 프리픽스(리틀엔디안)][길이만큼의 페이로드]
bool TryExtractFrame(std::vector<uint8_t>& payload)
{
if (accum_.size() < 4)
return false;
uint32_t declaredLen;
std::memcpy(&declaredLen, accum_.data(), sizeof(declaredLen));
// (B) declaredLen 이 비정상적으로 큰 값이어도 그대로 받아들이고,
// 그만큼 모일 때까지 계속 기다린다.
if (accum_.size() < 4 + static_cast<size_t>(declaredLen))
return false; // 아직 다 도착하지 않음 — 더 기다린다.
payload.assign(accum_.begin() + 4, accum_.begin() + 4 + declaredLen);
accum_.erase(accum_.begin(), accum_.begin() + 4 + declaredLen);
return true;
}
private:
std::vector<uint8_t> accum_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.