12. 플레이어 이동 좌표 검증 (속도핵/벽통과 방지)
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 플레이어 이동 좌표 검증 (속도핵/벽통과 방지, 서버 권위)
// ----------------------------------------------------------------------------
// 시나리오:
// - 플레이어가 움직이면 클라이언트가 C_Move 패킷을 보낸다.
// payload = [float x][float y][float z][long clientTimeMs][float clientSpeed]
// - 서버는 이동이 "물리적으로 가능한지" 검증한 뒤 위치를 갱신해야 한다.
// (1) 직전 위치→새 위치 거리가 (허용속도 × 경과시간) 이내인가 (속도핵 방지)
// (2) 목적지/경로가 통행 가능한가 (벽/장애물/낭떠러지 통과 방지)
// (3) 순간이동(텔레포트)은 서버가 허가한 경우(포탈/스킬)에만 허용
// - 클라이언트는 신뢰할 수 없다(좌표·시간·속도 모두 변조 가능).
// - 같은 플레이어의 이동 패킷이 여러 IO 스레드에서 거의 동시에 처리될 수 있다.
//
// 요구사항:
// - 위치는 서버가 권위 있게 갱신한다. 불가능한 이동은 거부하고 위치를 보정한다.
// - 경과시간/속도는 서버 시계와 서버가 아는 캐릭터 속도로만 계산한다.
// - 통행 불가 지점/벽 너머로의 이동을 막는다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 어떻게 악용되는지 설명하고,
// 수정안과 더 나은 설계를 제시하라. (먼저 직접 리뷰 후 answer.md 와 대조)
// ============================================================================
using System;
using System.Collections.Generic;
public struct Vec3
{
public float X, Y, Z;
public Vec3(float x, float y, float z) { X = x; Y = y; Z = z; }
}
public class Player
{
public long Id;
public Vec3 Pos;
public long LastMoveMs; // 마지막 이동 처리 시각
public float MoveSpeed; // 서버가 아는 캐릭터 이동속도(units/sec)
public bool TeleportGranted; // 서버가 발급한 1회성 순간이동 허가
}
public class CollisionMap
{
// 해당 좌표가 통행 가능한지(서버 권위 맵 데이터). 구현은 생략.
public bool IsWalkable(Vec3 p) { return true; }
}
public class MoveService
{
private readonly Dictionary<long, Player> _players;
private readonly CollisionMap _map;
public MoveService(Dictionary<long, Player> players, CollisionMap map)
{
_players = players;
_map = map;
}
private static long NowMs() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
private static float Distance(Vec3 a, Vec3 b)
{
float dx = a.X - b.X, dy = a.Y - b.Y, dz = a.Z - b.Z;
return (float)Math.Sqrt(dx * dx + dy * dy + dz * dz);
}
// C_Move 처리. 위치 갱신에 성공하면 true.
public bool OnMove(long playerId, float x, float y, float z,
long clientTimeMs, float clientSpeed)
{
Player p = _players[playerId];
Vec3 dst = new Vec3(x, y, z);
// (A) 속도 검증: 클라가 보낸 경과시간/속도로 허용 거리 계산
long dtMs = clientTimeMs - p.LastMoveMs;
float maxDist = clientSpeed * (dtMs / 1000f);
float dist = Distance(p.Pos, dst);
if (dist > maxDist)
return false;
// (B) 목적지 통행 가능 여부 — 그대로 신뢰하고 검사하지 않음
// (C) 위치/시각 갱신: 클라가 보낸 시각을 그대로 저장
p.Pos = dst;
p.LastMoveMs = clientTimeMs;
return true;
}
// 포탈/블링크 스킬이 호출. 다음 1회 이동을 순간이동으로 허용한다.
public void GrantTeleport(long playerId)
{
_players[playerId].TeleportGranted = true;
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 플레이어 이동 좌표 검증 (속도핵/벽통과 방지, 서버 권위)
// ----------------------------------------------------------------------------
// 시나리오:
// - 플레이어가 움직이면 클라이언트가 C_Move 패킷을 보낸다.
// payload = [float x][float y][float z][int64 clientTimeMs][float clientSpeed]
// - 서버는 이동이 "물리적으로 가능한지" 검증한 뒤 위치를 갱신해야 한다.
// (1) 직전 위치→새 위치 거리가 (허용속도 × 경과시간) 이내인가 (속도핵 방지)
// (2) 목적지/경로가 통행 가능한가 (벽/장애물/낭떠러지 통과 방지)
// (3) 순간이동(텔레포트)은 서버가 허가한 경우(포탈/스킬)에만 허용
// - 클라이언트는 신뢰할 수 없다(좌표·시간·속도 모두 변조 가능).
// - 같은 플레이어의 이동 패킷이 여러 IO 스레드에서 거의 동시에 처리될 수 있다.
//
// 요구사항:
// - 위치는 서버가 권위 있게 갱신한다. 불가능한 이동은 거부하고 위치를 보정한다.
// - 경과시간/속도는 서버 시계와 서버가 아는 캐릭터 속도로만 계산한다.
// - 통행 불가 지점/벽 너머로의 이동을 막는다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 어떻게 악용되는지 설명하고,
// 수정안과 더 나은 설계를 제시하라. (먼저 직접 리뷰 후 answer.md 와 대조)
// ============================================================================
#include <cstdint>
#include <cmath>
#include <unordered_map>
struct Vec3 { float x, y, z; };
struct Player {
int64_t id;
Vec3 pos;
int64_t lastMoveMs; // 마지막 이동 처리 시각
float moveSpeed; // 서버가 아는 캐릭터 이동속도(units/sec)
bool teleportGranted = false;
};
struct CollisionMap {
// 해당 좌표가 통행 가능한지(서버 권위 맵 데이터). 구현 생략.
bool IsWalkable(const Vec3&) const { return true; }
};
class MoveService {
public:
MoveService(std::unordered_map<int64_t, Player>& players, CollisionMap& map)
: players_(players), map_(map) {}
// C_Move 처리. 위치 갱신에 성공하면 true.
bool OnMove(int64_t playerId, float x, float y, float z,
int64_t clientTimeMs, float clientSpeed)
{
Player& p = players_[playerId];
Vec3 dst{ x, y, z };
// (A) 속도 검증: 클라가 보낸 경과시간/속도로 허용 거리 계산
int64_t dtMs = clientTimeMs - p.lastMoveMs;
float maxDist = clientSpeed * (dtMs / 1000.0f);
float dist = Distance(p.pos, dst);
if (dist > maxDist)
return false;
// (B) 목적지 통행 가능 여부 — 그대로 신뢰하고 검사하지 않음
// (C) 위치/시각 갱신: 클라가 보낸 시각을 그대로 저장
p.pos = dst;
p.lastMoveMs = clientTimeMs;
return true;
}
void GrantTeleport(int64_t playerId) {
players_[playerId].teleportGranted = true;
}
private:
static float Distance(const Vec3& a, const Vec3& b) {
float dx = a.x - b.x, dy = a.y - b.y, dz = a.z - b.z;
return std::sqrt(dx * dx + dy * dy + dz * dz);
}
std::unordered_map<int64_t, Player>& players_;
CollisionMap& map_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.