9. 압축/암호화 협상과 폴백 (전송 변환 파이프라인)

난이도 상 해설 보기 →

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

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 압축/암호화 협상과 폴백 (전송 변환 파이프라인)
// ----------------------------------------------------------------------------
// 시나리오:
//   - 핸드셰이크에서 클라/서버가 "지원하는 압축/암호화 알고리즘"을 교환하고,
//     공통으로 지원하는 것 중 하나를 골라 이후 모든 패킷에 적용한다.
//   - 패킷 헤더에는 적용된 변환을 나타내는 1바이트 flags 가 있다:
//       bit0 = 압축됨, bit1 = 암호화됨
//   - 협상이 실패하거나 한쪽이 알고리즘을 모르면 "무압축/무암호" 로 폴백한다.
//   - 운영 중 신버전(새 알고리즘 zstd, AES-GCM 추가)과 구버전(기존 deflate, AES-CBC)이
//     공존한다.
//
// 요구사항:
//   - 협상된 변환은 세션 전체에 일관 적용되어야 하고, 송신이 적용한 변환과
//     수신이 푸는 변환이 항상 일치해야 한다.
//   - 협상 실패가 "평문 전송"으로 조용히 떨어지면 안 된다(보안 다운그레이드).
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 발생하는지(특히 버전 공존/폴백 시점),
//   수정안과 더 나은 협상 설계를 제시하라.
// ============================================================================

using System;
using System.Collections.Generic;

[Flags]
public enum XformFlags : byte
{
    None      = 0,
    Compressed = 1 << 0,
    Encrypted  = 1 << 1,
}

public enum CompAlgo : byte { None, Deflate, Zstd }
public enum CryptAlgo : byte { None, AesCbc, AesGcm }

public class SessionXform
{
    public CompAlgo Comp = CompAlgo.None;
    public CryptAlgo Crypt = CryptAlgo.None;
}

public class XformNegotiator
{
    // 서버가 지원하는 알고리즘 (신버전)
    private static readonly CompAlgo[] ServerComp  = { CompAlgo.Zstd, CompAlgo.Deflate };
    private static readonly CryptAlgo[] ServerCrypt = { CryptAlgo.AesGcm, CryptAlgo.AesCbc };

    // 핸드셰이크: 클라가 지원하는 목록을 받아 협상
    public void Negotiate(SessionXform x, CompAlgo[] clientComp, CryptAlgo[] clientCrypt)
    {
        // (A) 서버 우선순위 순으로 클라가 지원하면 채택
        foreach (var c in ServerComp)
            if (Array.IndexOf(clientComp, c) >= 0) { x.Comp = c; break; }

        foreach (var c in ServerCrypt)
            if (Array.IndexOf(clientCrypt, c) >= 0) { x.Crypt = c; break; }

        // (B) 공통이 없으면 None 으로 남는다(폴백). 별도 처리 없음.
    }
}

public class PacketCodec
{
    // 송신: 변환 적용 후 flags 세팅
    public byte[] Encode(SessionXform x, byte[] payload)
    {
        XformFlags flags = XformFlags.None;
        byte[] body = payload;

        // (C) 압축 → 암호화 순서로 적용
        if (x.Comp != CompAlgo.None) { body = Compress(x.Comp, body); flags |= XformFlags.Compressed; }
        if (x.Crypt != CryptAlgo.None) { body = Encrypt(x.Crypt, body); flags |= XformFlags.Encrypted; }

        var outBuf = new byte[1 + body.Length];
        outBuf[0] = (byte)flags;
        Array.Copy(body, 0, outBuf, 1, body.Length);
        return outBuf;
    }

    // 수신: flags 를 보고 역변환
    public byte[] Decode(SessionXform x, byte[] packet)
    {
        XformFlags flags = (XformFlags)packet[0];
        byte[] body = new byte[packet.Length - 1];
        Array.Copy(packet, 1, body, 0, body.Length);

        // (D) flags 비트가 켜져 있으면 푼다
        if ((flags & XformFlags.Encrypted) != 0)  body = Decrypt(x.Crypt, body);
        if ((flags & XformFlags.Compressed) != 0) body = Decompress(x.Comp, body);
        return body;
    }

    private byte[] Compress(CompAlgo a, byte[] d)   => d;   // 구현 생략
    private byte[] Decompress(CompAlgo a, byte[] d) => d;
    private byte[] Encrypt(CryptAlgo a, byte[] d)   => d;
    private byte[] Decrypt(CryptAlgo a, byte[] d)   => d;
}
내 리뷰 · C#
내 답안 · 자동 저장

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