Skip to main content

Phân tích TrainingRunService

File trung tâm: TrainingRunService.java

Ref: kypo-service-training/src/main/java/cz/muni/ics/kypo/training/service/TrainingRunService.java
Lưu ý: Vì cài đặt Kypo chưa xong, dựa theo đó, file phân tích + trace code dưới đây dựa theo nguồn của gitlabs
Link: https://gitlab.ics.muni.cz/muni-kypo-crp

Sẽ làm:

  • Trace - theo dõi thực thi code
  • Phân tích code
  • Giải thích hệ thống tính điểm

Tập trung vào class: TrainingRunService

  • TrainingLevel → nộp flag
  • AssessmentLevel → làm quiz
  • Tính điểm tổng
  • Kết thúc training run

Phân loại level trong hệ thống

Hệ thống tồn tại 2 loại level, chia từng cách chấm khác nhau:

Loại levelMục đíchCó tính điểm không
TrainingLevelChấm flag
AssessmentLevelChấm quiz

TrainingLevel (Logic chấm flag trong CTF)

User submit 1 chuỗi → hệ thống kiểm tra đúng/sai → nếu đúng thì cộng điểm

Function Entry Point - hàm đầu vào để chấm Flag

public boolean isCorrectAnswer(Long runId, String answer) {
TrainingRun trainingRun = findByIdWithLevel(runId);
AbstractLevel level = trainingRun.getCurrentLevel();

if (level.getClass() != TrainingLevel.class) {
throw new BadRequestException("Current level is not training level and does not have answer.");
} else if (trainingRun.isLevelAnswered()) {
throw new EntityConflictException(
new EntityErrorDetail(
TrainingRun.class,
"id",
Long.class,
runId,
"The answer of the current level of training run has been already corrected."
)
);
}

return evaluateTrainingLevelAnswer(trainingRun, answer);
}

B1: Load trạng thái run

TrainingRun trainingRun = findByIdWithLevel(runId);

→ Lấy entity TrainingRun từ DB
→ Đã join luôn currentLevel

B2: Lấy level hiện tại

AbstractLevel level = trainingRun.getCurrentLevel();

B3: Check loại level

if (level.getClass() != TrainingLevel.class)

→ Chỉ cho phép nộp answer nếu currentLevel là TrainingLevel
→ Nếu không → throw exception

B4: Không cho nộp lại

trainingRun.isLevelAnswered()

→ Nếu đã trả lời rồi thì không cho submit lại

B5: Gọi hàm chấm thật

return evaluateTrainingLevelAnswer(trainingRun, answer);

Function chấm điểm flag

private boolean evaluateTrainingLevelAnswer(TrainingRun trainingRun, String answer) {

TrainingLevel trainingLevel = (TrainingLevel) trainingRun.getCurrentLevel();
String correctAnswer = getTrainingLevelCorrectAnswer(trainingLevel, trainingRun);

if (correctAnswer.equals(answer)) {

trainingRun.setLevelAnswered(true);

trainingRun.increaseTotalTrainingScore(
trainingRun.getMaxLevelScore() - trainingRun.getCurrentPenalty()
);

auditEventsService.auditCorrectAnswerSubmittedAction(trainingRun, answer);
auditEventsService.auditLevelCompletedAction(trainingRun);
auditSubmission(trainingRun, SubmissionType.CORRECT, answer);

return true;

} else if (trainingRun.getIncorrectAnswerCount() != trainingLevel.getIncorrectAnswerLimit()) {

trainingRun.setIncorrectAnswerCount(
trainingRun.getIncorrectAnswerCount() + 1
);
}

auditSubmission(trainingRun, SubmissionType.INCORRECT, answer);
auditEventsService.auditWrongAnswerSubmittedAction(trainingRun, answer);

return false;
}

B1: Lấy correct answer

String correctAnswer = getTrainingLevelCorrectAnswer(...)

Function này trả về chuỗi flag đúng.

B2: So sánh

if (correctAnswer.equals(answer))

→ So sánh string tuyệt đối
→ Không có trim
→ Không có ignoreCase

So sánh EXACT MATCH.

B3: Nếu đúng

trainingRun.setLevelAnswered(true);

→ Mark level đã hoàn thành

Công thức tính điểm

trainingRun.increaseTotalTrainingScore(
trainingRun.getMaxLevelScore() - trainingRun.getCurrentPenalty()
);
Điểm = MaxLevelScore - CurrentPenalty

B4: Nếu sai

trainingRun.setIncorrectAnswerCount(...)

→ Chỉ tăng số lần sai
→ KHÔNG trừ điểm
→ KHÔNG thay đổi totalScore


Penalty

trainingRun.increaseCurrentPenalty(hint.getHintPenalty());

Mỗi lần dùng hint: currentPenalty += hintPenalty


Solution

if (trainingLevel.isSolutionPenalized()) {
trainingRun.setCurrentPenalty(
trainingRun.getMaxLevelScore()
);
}

Nếu solution bị penalize: Penalty = MaxLevelScore
→ Điểm = 0

Thành phầnẢnh hưởng
Trả lời đúng+ (MaxScore - Penalty)
Trả lời saiKhông trừ
Dùng hint+ penalty
Xem solutionCó thể = 0 điểm

Assessment level (QUIZ)

Entry fuction

public void evaluateResponsesToAssessment(
Long trainingRunId,
Map<Long, QuestionAnswerDTO> answersToQuestions
) {
TrainingRun trainingRun = findByIdWithLevel(trainingRunId);

if (!(trainingRun.getCurrentLevel() instanceof AssessmentLevel)) {
throw new BadRequestException(
"Current level is not assessment level and cannot be evaluated."
);
}

if (trainingRun.isLevelAnswered())
throw new EntityConflictException(...);

List<QuestionAnswer> userAnswersToQuestions;

if (((AssessmentLevel) trainingRun.getCurrentLevel())
.getAssessmentType() == AssessmentType.TEST) {

userAnswersToQuestions =
this.gatherAndEvaluateAnswers(trainingRun, answersToQuestions);

} else {
userAnswersToQuestions =
this.gatherAnswers(trainingRun, answersToQuestions);
}

trainingRun.setLevelAnswered(true);

questionAnswerRepository.saveAll(userAnswersToQuestions);

auditEventsService.auditAssessmentAnswersAction(
trainingRun,
userAnswersToQuestions.toString()
);

auditEventsService.auditLevelCompletedAction(trainingRun);
}

Function chấm chính

private List<QuestionAnswer> gatherAndEvaluateAnswers(
TrainingRun trainingRun,
Map<Long, QuestionAnswerDTO> answersToQuestions
) {

int score = 0;
List<QuestionAnswer> userAnswersToQuestions = new ArrayList<>();

for (Question question :
((AssessmentLevel) trainingRun.getCurrentLevel()).getQuestions()) {

QuestionAnswerDTO questionAnswerDTO =
answersToQuestions.get(question.getId());

if (questionAnswerDTO == null) {
throw new BadRequestException(
"The question '" + question.getText() + "' must be answered."
);
}

userAnswersToQuestions.add(
this.createQuestionAnswer(
question,
trainingRun,
questionAnswerDTO
)
);

switch (question.getQuestionType()) {
case MCQ:
score += evaluateMCQ(question, questionAnswerDTO);
break;

case FFQ:
score += evaluateFFQ(question, questionAnswerDTO);
break;

case EMI:
score += evaluateEMI(question, questionAnswerDTO);
break;
}
}

trainingRun.setCurrentPenalty(
trainingRun.getMaxLevelScore() - score
);

trainingRun.increaseTotalAssessmentScore(score);

return userAnswersToQuestions;
}

B1 — Duyệt từng câu hỏi

for (Question question : ...)

→ Không tính tổng sẵn
→ Chấm từng câu

B2 — Gọi hàm chấm theo loại câu hỏi


MCQ (Multiple Choice Question)

private int evaluateMCQ(Question question, QuestionAnswerDTO userAnswer) {

List<String> correctAnswers =
question.getChoices().stream()
.filter(QuestionChoice::isCorrect)
.map(QuestionChoice::getText)
.collect(Collectors.toList());

return userAnswer.getAnswers().size() == correctAnswers.size()
&& userAnswer.getAnswers().containsAll(correctAnswers)
? question.getPoints()
: (-1) * question.getPenalty();
}

Logic

  • Phải chọn đủ
  • Không được chọn dư
  • Không được thiếu

Đúng → +points
Sai → -penalty


FFQ (Free Form Question)

private int evaluateFFQ(Question question, QuestionAnswerDTO userAnswer) {

List<String> correctAnswers =
question.getChoices().stream()
.map(QuestionChoice::getText)
.collect(Collectors.toList());

return correctAnswers.containsAll(userAnswer.getAnswers())
? question.getPoints()
: (-1) * question.getPenalty();
}

Logic

  • Nếu userAnswer nằm trong tập đáp án hợp lệ → +points
  • Sai → -penalty

EMI (Extended Matching Item)

private int evaluateEMI(Question question, QuestionAnswerDTO userAnswer) {

for (ExtendedMatchingStatement statement :
question.getExtendedMatchingStatements()) {

int expectedOptionOrder =
statement.getExtendedMatchingOption().getOrder();

int answeredOptionOrder =
userAnswer.getExtendedMatchingPairs()
.get(statement.getOrder());

if (expectedOptionOrder != answeredOptionOrder) {
return (-1) * question.getPenalty();
}
}

return question.getPoints();
}

Logic

  • Sai 1 cặp → trừ full penalty
  • Đúng tất cả → +points

Công thức tổng

Sau khi chấm xong tất cả câu:

trainingRun.setCurrentPenalty(
trainingRun.getMaxLevelScore() - score
);

Tức là:

Penalty = MaxLevelScore - score

Và:

trainingRun.increaseTotalAssessmentScore(score);
Thành phầnẢnh hưởng
Mỗi câu đúng+points
Mỗi câu sai-penalty
Tổng scoreCộng dồn
HintKhông ảnh hưởng

Tổng kết sau khi trace code

1. TrainingLevel

  • So sánh chuỗi
  • Không trừ khi sai
  • Hint tạo penalty
  • Điểm = MaxScore - Penalty

2. AssessmentLevel

  • Chấm từng câu
  • Sai bị trừ
  • Tổng = cộng dồn
  • Penalty = MaxScore - score
sticker