diff --git a/lab2/main_test.js b/lab2/main_test.js index 5034468e..2fa6a198 100644 --- a/lab2/main_test.js +++ b/lab2/main_test.js @@ -1,6 +1,122 @@ const test = require('node:test'); const assert = require('assert'); -const { Application, MailSystem } = require('./main'); +const fs = require('fs'); +const util = require('util'); -// TODO: write your tests here -// Remember to use Stub, Mock, and Spy when necessary \ No newline at end of file +// 1️⃣ Stub `fs.readFile` 為 Promise 版本 +fs.readFile = util.promisify((path, encoding, callback) => { + callback(null, 'Alice\nBob\nCharlie\nDavid'); // 回傳假名單 +}); + +const { Application, MailSystem } = require('./main'); // 確保這是你的主程式 + +test('Application getNames should return list of names from stubbed file', async () => { + const app = new Application(); + await new Promise((resolve) => setTimeout(resolve, 100)); // 確保 `getNames` 完成 + assert.deepStrictEqual(app.people, ['Alice', 'Bob', 'Charlie', 'David']); +}); + +test('MailSystem write should generate mail content', () => { + const mailSystem = new MailSystem(); + const name = 'Alice'; + const result = mailSystem.write(name); + assert.strictEqual(result, 'Congrats, Alice!'); +}); + +test('MailSystem send should return both true and false', () => { + const mailSystem = new MailSystem(); + const name = 'Alice'; + const context = 'Congrats, Alice!'; + + let seenTrue = false; + let seenFalse = false; + let attempts = 0; + const maxAttempts = 100; // 限制最大迴圈次數避免無窮迴圈 + + while (!(seenTrue && seenFalse) && attempts < maxAttempts) { + const result = mailSystem.send(name, context); + if (result) { + seenTrue = true; + } else { + seenFalse = true; + } + attempts++; + } + + assert.strictEqual(seenTrue, true, 'MailSystem.send() should return true at least once'); + assert.strictEqual(seenFalse, true, 'MailSystem.send() should return false at least once'); +}); + + +test('Application getRandomPerson should return a valid person', async () => { + const app = new Application(); + await new Promise((resolve) => setTimeout(resolve, 100)); // 確保 getNames 完成 + const person = app.getRandomPerson(); + assert.ok(['Alice', 'Bob', 'Charlie', 'David'].includes(person)); +}); + +test('Application selectNextPerson should return null when all are selected', async () => { + const app = new Application(); + await new Promise((resolve) => setTimeout(resolve, 100)); + app.people = ['Alice', 'Bob']; + app.selected = ['Alice', 'Bob']; + const result = app.selectNextPerson(); + assert.strictEqual(result, null); +}); + +test('Application selectNextPerson should select a new person each time', async () => { + const app = new Application(); + await new Promise((resolve) => setTimeout(resolve, 100)); + const selected1 = app.selectNextPerson(); + const selected2 = app.selectNextPerson(); + assert.notStrictEqual(selected1, null); + assert.notStrictEqual(selected2, null); + assert.notStrictEqual(selected1, selected2); +}); + +test('Application selectNextPerson should avoid duplicate selection', async () => { + const app = new Application(); + await new Promise((resolve) => setTimeout(resolve, 100)); + app.people = ['Alice', 'Bob', 'Charlie', 'David']; + app.selected = ['Alice']; + const selected = new Set(app.selected); + for (let i = 0; i < 4; i++) { + const newPerson = app.selectNextPerson(); + assert.ok(!selected.has(newPerson)); + selected.add(newPerson); + } +}); + +test('Application notifySelected should send emails to selected people', async () => { + const app = new Application(); + await new Promise((resolve) => setTimeout(resolve, 100)); + app.selectNextPerson(); + app.selectNextPerson(); + + // Spy: 監視方法呼叫次數 + let writeCallCount = 0; + let sendCallCount = 0; + + const originalWrite = app.mailSystem.write; + const originalSend = app.mailSystem.send; + + // Mock: 取代方法回傳預期值 + app.mailSystem.write = () => { + writeCallCount++; + return 'Mock Content'; + }; + + app.mailSystem.send = () => { + sendCallCount++; + return true; + }; + + app.notifySelected(); + + assert.strictEqual(writeCallCount, app.selected.length); + assert.strictEqual(sendCallCount, app.selected.length); + + // 還原原始方法 + app.mailSystem.write = originalWrite; + app.mailSystem.send = originalSend; +}); diff --git a/lab8/solve.py b/lab8/solve.py index 9ab3ee2f..e6fd2777 100755 --- a/lab8/solve.py +++ b/lab8/solve.py @@ -1,11 +1,46 @@ #!/usr/bin/env python3 -import angr,sys +import angr +import claripy +import sys def main(): - secret_key = b"" - sys.stdout.buffer.write(secret_key) + # 載入 chal 執行檔 + proj = angr.Project("./chal", auto_load_libs=False) + # 建立 8 個符號位元組(每個是 8-bit),組成 secret_key + key_bytes = [claripy.BVS(f'key_{i}', 8) for i in range(8)] + secret_key = claripy.Concat(*key_bytes) + + # 初始化 state,將 symbolic input 傳入 stdin + state = proj.factory.full_init_state(stdin=secret_key) + + # 加入輸入長度限制(因為 chal.c 會用 strlen 判斷長度必須是 8) + for b in key_bytes: + state.solver.add(b >= 0x20) # 可列印字元 + state.solver.add(b <= 0x7e) + + # 建立 simulation manager + simgr = proj.factory.simgr(state) + + # 設定搜尋目標:當輸出包含 "Correct!",代表成功通過 gate() + def is_successful(state): + return b"Correct!" in state.posix.dumps(1) + + # 設定排除條件:當輸出包含 "Wrong key!",表示是失敗路徑 + def should_abort(state): + return b"Wrong key!" in state.posix.dumps(1) + + # 探索符合條件的路徑 + simgr.explore(find=is_successful, avoid=should_abort) + + if simgr.found: + found = simgr.found[0] + # 將求得的符號解碼為實際的字串 + key = found.solver.eval(secret_key, cast_to=bytes) + sys.stdout.buffer.write(key) + else: + print("No solution found.") if __name__ == '__main__': main()