14. 부동소수점 좌표 직렬화와 NaN/Inf·결정론 방어 (C#)

난이도 중 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 부동소수점 좌표 직렬화와 NaN/Inf·결정론 방어 (서버-서버 동기화)
// ----------------------------------------------------------------------------
// 시나리오 (프로토콜/직렬화):
//   - 존 서버 A 가 엔티티 스냅샷(위치 x,y,z 등 float)을 직렬화해 존 서버 B(또는
//     관전/리플레이 서버)로 전송하고, B 는 역직렬화해 자기 시뮬레이션에 반영한다.
//   - 송신 측은 클라이언트가 보고한 값에서 비롯된 좌표를 그대로 중계할 수도 있다.
//   - float 비트 패턴에는 NaN/+Inf/-Inf, -0.0, 비정규(subnormal) 같은 특수값이 있다.
//   - 서로 다른 런타임/플랫폼(엔디안 포함)을 쓰는 서버들이 같은 바이트를 주고받는다.
//
// 요구사항:
//   - 수신한 float 좌표는 신뢰할 수 없다. NaN/Inf/범위초과 좌표가 시뮬레이션에
//     들어가 물리/판정/AoI 계산을 오염시키면 안 된다(NaN 은 비교가 모두 false 라
//     경계검사·정렬·충돌이 조용히 망가진다).
//   - 직렬화 포맷은 송수신 서버 플랫폼이 달라도 동일하게 해석돼야 한다(엔디안 등).
//   - 좌표는 맵 경계 등 유효 범위 안의 유한값이어야 한다.
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 악용·오작동하는지 설명하고,
//   수정안과 더 나은 설계를 제시하라.
//   (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

using System;
using System.Collections.Generic;

public struct Vec3 { public float X, Y, Z; }

public struct EntitySnapshot
{
    public long Id;
    public Vec3 Pos;
    public Vec3 Vel;
}

public class SnapshotCodec
{
    // 직렬화: 스냅샷을 바이트 리스트에 append
    public void Write(List<byte> outBuf, in EntitySnapshot s)
    {
        outBuf.AddRange(BitConverter.GetBytes(s.Id));
        // (A) float 들을 BitConverter 로 그대로 직렬화
        outBuf.AddRange(BitConverter.GetBytes(s.Pos.X));
        outBuf.AddRange(BitConverter.GetBytes(s.Pos.Y));
        outBuf.AddRange(BitConverter.GetBytes(s.Pos.Z));
        outBuf.AddRange(BitConverter.GetBytes(s.Vel.X));
        outBuf.AddRange(BitConverter.GetBytes(s.Vel.Y));
        outBuf.AddRange(BitConverter.GetBytes(s.Vel.Z));
    }

    // 역직렬화: 버퍼에서 스냅샷 1개 읽기. 성공 시 true.
    public bool Read(byte[] buf, int offset, out EntitySnapshot s)
    {
        s = default;
        if (buf.Length - offset < 8 + 4 * 6)
            return false;

        int o = offset;
        s.Id = BitConverter.ToInt64(buf, o); o += 8;
        // (B) 바이트를 그대로 float 로 해석
        s.Pos.X = BitConverter.ToSingle(buf, o); o += 4;
        s.Pos.Y = BitConverter.ToSingle(buf, o); o += 4;
        s.Pos.Z = BitConverter.ToSingle(buf, o); o += 4;
        s.Vel.X = BitConverter.ToSingle(buf, o); o += 4;
        s.Vel.Y = BitConverter.ToSingle(buf, o); o += 4;
        s.Vel.Z = BitConverter.ToSingle(buf, o); o += 4;

        // (C) 읽은 좌표를 그대로 사용 가능 상태로 반환
        return true;
    }

    // 수신한 스냅샷을 시뮬레이션에 적용
    public void Apply(ref EntitySnapshot dst, in EntitySnapshot recv)
    {
        dst.Pos = recv.Pos;
        dst.Vel = recv.Vel;
        // 이후 물리/AoI/충돌 계산이 dst.Pos 를 사용
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.