Files
odinsea-elixir/test/crypto_test.exs
2026-02-25 12:26:26 -07:00

186 lines
5.7 KiB
Elixir

defmodule Odinsea.CryptoTest do
@moduledoc """
Test module for MapleStory crypto implementation.
Run with: mix test test/crypto_test.exs
"""
use ExUnit.Case
alias Odinsea.Net.Cipher.{ClientCrypto, IGCipher, ShandaCipher, AESCipher}
alias Odinsea.Util.BitTools
describe "IGCipher (IV transformation)" do
test "inno_hash transforms IV correctly" do
iv = <<0x34, 0x9A, 0x0F, 0x0C>>
result = IGCipher.inno_hash(iv)
# Result should be 4 bytes
assert byte_size(result) == 4
# Result should be different from input (usually)
assert result != iv
end
test "inno_hash produces deterministic results" do
iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
result1 = IGCipher.inno_hash(iv)
result2 = IGCipher.inno_hash(iv)
assert result1 == result2
end
end
describe "ShandaCipher" do
test "encrypt and decrypt are inverse operations" do
original = <<1, 0, 7, 112, 0, 4, 0>> # CP_PermissionRequest payload
encrypted = ShandaCipher.encrypt(original)
decrypted = ShandaCipher.decrypt(encrypted)
assert decrypted == original
end
test "encrypt produces different output" do
original = <<1, 0, 7, 112, 0, 4, 0>>
encrypted = ShandaCipher.encrypt(original)
assert encrypted != original
end
end
describe "AESCipher" do
test "crypt is self-inverse (XOR property)" do
data = <<1, 0, 7, 112, 0, 4, 0>>
iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
encrypted = AESCipher.crypt(data, iv)
decrypted = AESCipher.crypt(encrypted, iv)
assert decrypted == data
end
end
describe "ClientCrypto" do
test "new creates crypto with random IVs" do
crypto = ClientCrypto.new(112)
assert byte_size(crypto.send_iv) == 4
assert byte_size(crypto.recv_iv) == 4
assert crypto.version == 112
end
test "new_from_ivs creates crypto with specified IVs" do
send_iv = <<0x34, 0x9A, 0x0F, 0x0C>>
recv_iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
crypto = ClientCrypto.new_from_ivs(112, send_iv, recv_iv)
assert crypto.send_iv == send_iv
assert crypto.recv_iv == recv_iv
end
test "new_from_client_ivs swaps IVs correctly" do
# Client's perspective
client_send_iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
client_recv_iv = <<0x34, 0x9A, 0x0F, 0x0C>>
# Server's perspective (should be swapped)
crypto = ClientCrypto.new_from_client_ivs(112, client_send_iv, client_recv_iv)
# Server's send = client's recv
assert crypto.send_iv == client_recv_iv
# Server's recv = client's send
assert crypto.recv_iv == client_send_iv
end
test "decrypt updates recv_iv" do
crypto = ClientCrypto.new(112)
original_recv_iv = crypto.recv_iv
encrypted_data = <<0x7C, 0xA8, 0x7B, 0xA8, 0xBF, 0x0A, 0xCD, 0xDE>>
{new_crypto, _decrypted} = ClientCrypto.decrypt(crypto, encrypted_data)
# IV should be updated after decryption
assert new_crypto.recv_iv != original_recv_iv
end
test "encrypt updates send_iv" do
crypto = ClientCrypto.new(112)
original_send_iv = crypto.send_iv
data = <<1, 0, 7, 112, 0, 4, 0>>
{new_crypto, _encrypted, _header} = ClientCrypto.encrypt(crypto, data)
# IV should be updated after encryption
assert new_crypto.send_iv != original_send_iv
end
test "header encoding produces 4 bytes" do
crypto = ClientCrypto.new(112)
data = <<1, 0, 7, 112, 0, 4, 0>>
{_new_crypto, _encrypted, header} = ClientCrypto.encrypt(crypto, data)
assert byte_size(header) == 4
end
test "header validation works correctly" do
crypto = ClientCrypto.new(112)
data = <<1, 0, 7, 112, 0, 4, 0>>
{new_crypto, encrypted, header} = ClientCrypto.encrypt(crypto, data)
# Extract raw_seq from header
<<raw_seq::little-16, _raw_len::little-16>> = header
# Validation should pass
assert ClientCrypto.decode_header_valid?(new_crypto, raw_seq)
end
test "full encrypt/decrypt roundtrip" do
# Create crypto instances with same IVs
send_iv = <<0x34, 0x9A, 0x0F, 0x0C>>
recv_iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
# Server's crypto (for sending)
server_crypto = ClientCrypto.new_from_ivs(112, send_iv, recv_iv)
# Client's crypto would have swapped IVs
# For testing, we'll use the same crypto to verify roundtrip
original_data = <<1, 0, 7, 112, 0, 4, 0>>
# Encrypt
{server_crypto_after, encrypted, header} = ClientCrypto.encrypt(server_crypto, original_data)
# Decrypt (using recv_iv which should match server's send_iv after swap)
# For this test, we'll create a matching client crypto
client_crypto = ClientCrypto.new_from_ivs(112, recv_iv, send_iv)
{client_crypto_after, decrypted} = ClientCrypto.decrypt(client_crypto, encrypted)
assert decrypted == original_data
end
end
describe "BitTools" do
test "roll_left rotates bits correctly" do
# 0b11001101 = 205
# rotate left by 3: 0b01101101 = 109 (wrapping around)
result = BitTools.roll_left(205, 3)
assert result == 109
end
test "roll_right rotates bits correctly" do
# Test that roll_right is inverse of roll_left
original = 205
rotated = BitTools.roll_left(original, 3)
back = BitTools.roll_right(rotated, 3)
assert back == original
end
test "multiply_bytes repeats correctly" do
input = <<1, 2, 3, 4>>
result = BitTools.multiply_bytes(input, 4, 2)
assert result == <<1, 2, 3, 4, 1, 2, 3, 4>>
end
end
end