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 <> = 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