8. 대용량 메시지 프래그먼트/재조립
난이도 상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 대용량 메시지 프래그먼트/재조립 (UDP 신뢰 계층)
// ----------------------------------------------------------------------------
// 시나리오:
// - 게임은 자체 신뢰성 UDP 계층 위에서 동작한다. MTU(예: 1200B)를 넘는
// 큰 메시지(맵 스트리밍, 인벤토리 풀스냅샷)는 여러 프래그먼트로 쪼개 보낸다.
// - 프래그먼트 헤더(와이어, little-endian):
// [uint msgId][ushort fragIndex][ushort fragCount][payload...]
// 같은 msgId 의 프래그먼트들을 fragIndex 순서로 재조립해 완성 메시지를 만든다.
// - 신뢰 계층이 순서/중복은 어느 정도 처리하지만, 재전송/지연으로
// 프래그먼트가 중복·지연·유실되어 도착할 수 있다(타이밍은 비결정적).
// - 클라이언트는 신뢰할 수 없다(변조된 프래그먼트 헤더 가능).
//
// 요구사항:
// - 변조/거대 메시지/유실 상황에서도 서버 메모리가 무한히 늘거나 깨지면 안 된다.
// - 동시에 여러 메시지가 재조립 중일 수 있다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 악용·오작동하는지 설명하고,
// 수정안과 더 나은 재조립 설계를 제시하라.
// ============================================================================
using System;
using System.Collections.Generic;
// 재조립 진행 중인 메시지 상태
public class ReassemblyState
{
public ushort fragCount = 0;
public ushort received = 0;
public byte[] buffer; // 완성 메시지를 여기에 채운다
public bool[] got; // 각 프래그먼트 도착 여부
}
public class Reassembler
{
private const int HeaderSize = 8; // uint + ushort + ushort
private Dictionary<uint, ReassemblyState> _pending = new Dictionary<uint, ReassemblyState>();
// 한 프래그먼트 수신
public void OnFragment(byte[] buf, int len)
{
uint msgId = BitConverter.ToUInt32(buf, 0);
ushort fragIndex = BitConverter.ToUInt16(buf, 4);
ushort fragCount = BitConverter.ToUInt16(buf, 6);
int payloadOffset = HeaderSize;
int payloadLen = len - HeaderSize; // (A)
// (B) 이 msgId 의 재조립 상태를 가져오거나 새로 만든다
if (!_pending.TryGetValue(msgId, out ReassemblyState st))
{
st = new ReassemblyState();
_pending[msgId] = st;
}
if (st.fragCount == 0)
{
// 첫 프래그먼트: 전체 크기를 fragCount 로 예약
st.fragCount = fragCount;
st.got = new bool[fragCount];
// (C) 한 프래그먼트당 payloadLen 으로 전체 버퍼 크기 추정
st.buffer = new byte[fragCount * payloadLen];
}
// (D) fragIndex 위치에 payload 복사
int offset = fragIndex * payloadLen;
Array.Copy(buf, payloadOffset, st.buffer, offset, payloadLen);
if (!st.got[fragIndex])
{
st.got[fragIndex] = true;
st.received++;
}
// (E) 다 모였으면 완성 메시지 디스패치
if (st.received == st.fragCount)
{
OnMessageComplete(msgId, st.buffer, st.buffer.Length);
_pending.Remove(msgId);
}
}
private void OnMessageComplete(uint msgId, byte[] data, int len) { /* ... */ }
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 대용량 메시지 프래그먼트/재조립 (UDP 신뢰 계층)
// ----------------------------------------------------------------------------
// 시나리오:
// - 게임은 자체 신뢰성 UDP 계층 위에서 동작한다. MTU(예: 1200B)를 넘는
// 큰 메시지(맵 스트리밍, 인벤토리 풀스냅샷)는 여러 프래그먼트로 쪼개 보낸다.
// - 프래그먼트 헤더(와이어):
// [uint32 msgId][uint16 fragIndex][uint16 fragCount][payload...]
// 같은 msgId 의 프래그먼트들을 fragIndex 순서로 재조립해 완성 메시지를 만든다.
// - 신뢰 계층이 순서/중복은 어느 정도 처리하지만, 재전송/지연으로
// 프래그먼트가 중복·지연·유실되어 도착할 수 있다(타이밍은 비결정적).
// - 클라이언트는 신뢰할 수 없다(변조된 프래그먼트 헤더 가능).
//
// 요구사항:
// - 변조/거대 메시지/유실 상황에서도 서버 메모리가 무한히 늘거나 깨지면 안 된다.
// - 동시에 여러 메시지가 재조립 중일 수 있다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 악용·오작동하는지 설명하고,
// 수정안과 더 나은 재조립 설계를 제시하라.
// ============================================================================
#include <cstdint>
#include <cstring>
#include <vector>
#include <unordered_map>
#pragma pack(push, 1)
struct FragHeader {
uint32_t msgId;
uint16_t fragIndex;
uint16_t fragCount;
};
#pragma pack(pop)
// 재조립 진행 중인 메시지 상태
struct ReassemblyState {
uint16_t fragCount = 0;
uint16_t received = 0;
std::vector<uint8_t> buffer; // 완성 메시지를 여기에 채운다
std::vector<bool> got; // 각 프래그먼트 도착 여부
};
class Reassembler {
public:
// 한 프래그먼트 수신
void OnFragment(const uint8_t* buf, size_t len)
{
const FragHeader* h = reinterpret_cast<const FragHeader*>(buf);
const uint8_t* payload = buf + sizeof(FragHeader);
size_t payloadLen = len - sizeof(FragHeader); // (A)
// (B) 이 msgId 의 재조립 상태를 가져오거나 새로 만든다
ReassemblyState& st = _pending[h->msgId];
if (st.fragCount == 0) {
// 첫 프래그먼트: 전체 크기를 fragCount 로 예약
st.fragCount = h->fragCount;
st.got.resize(h->fragCount, false);
// (C) 한 프래그먼트당 payloadLen 으로 전체 버퍼 크기 추정
st.buffer.resize(h->fragCount * payloadLen);
}
// (D) fragIndex 위치에 payload 복사
size_t offset = h->fragIndex * payloadLen;
std::memcpy(st.buffer.data() + offset, payload, payloadLen);
if (!st.got[h->fragIndex]) {
st.got[h->fragIndex] = true;
st.received++;
}
// (E) 다 모였으면 완성 메시지 디스패치
if (st.received == st.fragCount) {
OnMessageComplete(h->msgId, st.buffer.data(), st.buffer.size());
_pending.erase(h->msgId);
}
}
private:
std::unordered_map<uint32_t, ReassemblyState> _pending;
void OnMessageComplete(uint32_t msgId, const uint8_t* data, size_t len) { /* ... */ }
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.