use serde::{Deserialize, Serialize};
use crate::Address;
pub const MAX_COURSE_CODE_BYTES: usize = 32;
pub const MAX_DEPARTMENT_BYTES: usize = 64;
pub const MAX_TERM_BYTES: usize = 32;
pub const MAX_SECTION_BYTES: usize = 32;
pub const MAX_TITLE_BYTES: usize = 256;
pub const MAX_EDU_OP_DATA_BYTES: usize = 64 * 1024;
pub const MAX_MEMO_BYTES: usize = 1024;
const DOMAIN_CATALOG_ID: &[u8] = b"SRC817-CATALOG:v1:";
const DOMAIN_OFFERING_ID: &[u8] = b"SRC818-OFFERING:v1:";
const DOMAIN_STUDENT_COMMITMENT: &[u8] = b"SRC818-STUDENT:v1:";
const DOMAIN_SUBMISSION_COMMITMENT: &[u8] = b"SRC818-SUBMISSION:v1:";
const DOMAIN_GRADE_COMMITMENT: &[u8] = b"SRC818-GRADE:v1:";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum EducationStandard {
CourseCatalog = 0,
CourseOffering = 1,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EducationTxData {
pub standard: EducationStandard,
pub operation: u16,
pub data: Vec<u8>,
pub recipient: Address,
}
pub mod catalog_op {
pub const CREATE_CATALOG_ENTRY: u16 = 0;
pub const UPDATE_CATALOG_ENTRY: u16 = 1;
pub const PUBLISH_CATALOG_CONTENT: u16 = 2;
pub const DEPRECATE_CATALOG_ENTRY: u16 = 3;
pub const SUPERSEDE_CATALOG_ENTRY: u16 = 4;
pub const ARCHIVE_CATALOG_ENTRY: u16 = 5;
}
pub mod offering_op {
pub const CREATE_OFFERING: u16 = 0;
pub const UPDATE_OFFERING: u16 = 1;
pub const PUBLISH_CONTENT: u16 = 2;
pub const ADD_ASSESSMENT: u16 = 3;
pub const UPDATE_ASSESSMENT: u16 = 4;
pub const OPEN_ENROLLMENT: u16 = 5;
pub const CLOSE_ENROLLMENT: u16 = 6;
pub const LINK_ENROLLMENT: u16 = 7;
pub const SUBMIT_ASSIGNMENT: u16 = 8;
pub const SUBMIT_EXAM: u16 = 9;
pub const GRADE_SUBMISSION: u16 = 10;
pub const FINALIZE_GRADE: u16 = 11;
pub const FINALIZE_COURSE: u16 = 12;
pub const ARCHIVE_OFFERING: u16 = 13;
pub const SUSPEND_OR_CANCEL_OFFERING: u16 = 14;
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SnipRef {
pub content_root: [u8; 32],
pub snip_file_id: Option<[u8; 32]>,
pub size_bytes: u64,
pub schema_version: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum AccessAudience {
Public,
EnrolledStudents,
InstructorsOnly,
StaffOnly,
IndividualStudent([u8; 32]),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ContentAccessPolicy {
pub opens_at: Option<u64>,
pub closes_at: Option<u64>,
pub grace_until: Option<u64>,
pub audience: AccessAudience,
pub revoke_on_course_archive: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ManagedSnipRef {
pub snip_ref: SnipRef,
pub access_policy: ContentAccessPolicy,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum CatalogStatus {
Draft = 0,
Active = 1,
Deprecated = 2,
Archived = 3,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum OfferingStatus {
Draft = 0,
Active = 1,
EnrollmentClosed = 2,
Completed = 3,
Archived = 4,
Suspended = 5,
Cancelled = 6,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum AssessmentKind {
Assignment = 0,
Exam = 1,
Quiz = 2,
Project = 3,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum CourseRole {
InstitutionAdmin = 0,
Instructor = 1,
TeachingAssistant = 2,
Grader = 3,
Student = 4,
Auditor = 5,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum CourseLevel {
Undergraduate = 0,
Graduate = 1,
Doctoral = 2,
Professional = 3,
Continuing = 4,
Other = 5,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum ContentKind {
Syllabus = 0,
LectureMaterial = 1,
Reading = 2,
Resource = 3,
Other = 4,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum SuspendCancelAction {
Suspend = 0,
Resume = 1,
Cancel = 2,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InvalidEducationCode(pub u8);
impl core::fmt::Display for InvalidEducationCode {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "invalid education wire code: {}", self.0)
}
}
impl TryFrom<u8> for CourseLevel {
type Error = InvalidEducationCode;
fn try_from(v: u8) -> Result<Self, Self::Error> {
Ok(match v {
0 => CourseLevel::Undergraduate,
1 => CourseLevel::Graduate,
2 => CourseLevel::Doctoral,
3 => CourseLevel::Professional,
4 => CourseLevel::Continuing,
5 => CourseLevel::Other,
other => return Err(InvalidEducationCode(other)),
})
}
}
impl TryFrom<u8> for ContentKind {
type Error = InvalidEducationCode;
fn try_from(v: u8) -> Result<Self, Self::Error> {
Ok(match v {
0 => ContentKind::Syllabus,
1 => ContentKind::LectureMaterial,
2 => ContentKind::Reading,
3 => ContentKind::Resource,
4 => ContentKind::Other,
other => return Err(InvalidEducationCode(other)),
})
}
}
impl TryFrom<u8> for AssessmentKind {
type Error = InvalidEducationCode;
fn try_from(v: u8) -> Result<Self, Self::Error> {
Ok(match v {
0 => AssessmentKind::Assignment,
1 => AssessmentKind::Exam,
2 => AssessmentKind::Quiz,
3 => AssessmentKind::Project,
other => return Err(InvalidEducationCode(other)),
})
}
}
impl TryFrom<u8> for CourseRole {
type Error = InvalidEducationCode;
fn try_from(v: u8) -> Result<Self, Self::Error> {
Ok(match v {
0 => CourseRole::InstitutionAdmin,
1 => CourseRole::Instructor,
2 => CourseRole::TeachingAssistant,
3 => CourseRole::Grader,
4 => CourseRole::Student,
5 => CourseRole::Auditor,
other => return Err(InvalidEducationCode(other)),
})
}
}
impl TryFrom<u8> for SuspendCancelAction {
type Error = InvalidEducationCode;
fn try_from(v: u8) -> Result<Self, Self::Error> {
Ok(match v {
0 => SuspendCancelAction::Suspend,
1 => SuspendCancelAction::Resume,
2 => SuspendCancelAction::Cancel,
other => return Err(InvalidEducationCode(other)),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CreateCatalogEntryData {
pub catalog_id: [u8; 32],
pub institution_id: [u8; 32],
pub department: String,
pub course_code: String,
pub course_title: Option<String>,
pub title_commitment: Option<[u8; 32]>,
pub course_level: u8,
pub credit_hours: Option<u16>,
pub credit_commitment: Option<[u8; 32]>,
pub prerequisites_count: u32,
pub prerequisites_root: Option<[u8; 32]>,
pub version: u32,
pub supersedes: Option<[u8; 32]>,
pub nonce: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CreateOfferingData {
pub offering_id: [u8; 32],
pub catalog_id: [u8; 32],
pub term: String,
pub section: String,
pub instruction_start_at: u64,
pub instruction_end_at: u64,
pub final_grade_submission_deadline: u64,
pub nonce: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SubmitAssignmentReceiptData {
pub offering_id: [u8; 32],
pub assessment_id: [u8; 32],
pub student_commitment: [u8; 32],
pub submission_commitment: [u8; 32],
pub work: ManagedSnipRef,
pub attempt: u16,
pub enrollment_ref: [u8; 32],
pub student_auth_commitment: Option<[u8; 32]>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct UpdateCatalogEntryData {
pub catalog_id: [u8; 32],
pub course_title: Option<String>,
pub title_commitment: Option<[u8; 32]>,
pub course_level: Option<u8>,
pub credit_hours: Option<u16>,
pub credit_commitment: Option<[u8; 32]>,
pub nonce: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PublishCatalogContentData {
pub catalog_id: [u8; 32],
pub description_ref: Option<ManagedSnipRef>,
pub learning_outcomes_ref: Option<ManagedSnipRef>,
pub default_syllabus_ref: Option<ManagedSnipRef>,
pub default_assessment_policy_ref: Option<ManagedSnipRef>,
pub nonce: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeprecateCatalogEntryData {
pub catalog_id: [u8; 32],
pub nonce: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SupersedeCatalogEntryData {
pub old_catalog_id: [u8; 32],
pub new_catalog_id: [u8; 32],
pub nonce: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ArchiveCatalogEntryData {
pub catalog_id: [u8; 32],
pub nonce: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct UpdateOfferingData {
pub offering_id: [u8; 32],
pub term: Option<String>,
pub section: Option<String>,
pub instruction_start_at: Option<u64>,
pub instruction_end_at: Option<u64>,
pub final_grade_submission_deadline: Option<u64>,
pub nonce: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PublishContentData {
pub offering_id: [u8; 32],
pub content_id: [u8; 32],
pub kind: u8,
pub item: ManagedSnipRef,
pub content_commitment: [u8; 32],
pub nonce: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AddAssessmentData {
pub offering_id: [u8; 32],
pub assessment_id: [u8; 32],
pub kind: u8,
pub instructions: ManagedSnipRef,
pub spec_commitment: [u8; 32],
pub opens_at: u64,
pub due_at: u64,
pub max_attempts: u16,
pub weight_bps: u16,
pub answer_key_commitment: Option<[u8; 32]>,
pub answer_key_access: Option<ContentAccessPolicy>,
pub nonce: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct UpdateAssessmentData {
pub offering_id: [u8; 32],
pub assessment_id: [u8; 32],
pub opens_at: Option<u64>,
pub due_at: Option<u64>,
pub max_attempts: Option<u16>,
pub weight_bps: Option<u16>,
pub instructions: Option<ManagedSnipRef>,
pub nonce: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OpenEnrollmentData {
pub offering_id: [u8; 32],
pub nonce: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CloseEnrollmentData {
pub offering_id: [u8; 32],
pub nonce: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LinkEnrollmentData {
pub offering_id: [u8; 32],
pub student_commitment: [u8; 32],
pub enrollment_ref: [u8; 32],
pub nonce: u64,
}
pub type SubmitExamReceiptData = SubmitAssignmentReceiptData;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GradeSubmissionData {
pub offering_id: [u8; 32],
pub assessment_id: [u8; 32],
pub student_commitment: [u8; 32],
pub grade_commitment: [u8; 32],
pub feedback: Option<ManagedSnipRef>,
pub grader_role: u8,
pub nonce: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FinalizeGradeData {
pub offering_id: [u8; 32],
pub assessment_id: [u8; 32],
pub student_commitment: [u8; 32],
pub nonce: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FinalizeCourseData {
pub offering_id: [u8; 32],
pub nonce: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ArchiveOfferingData {
pub offering_id: [u8; 32],
pub nonce: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SuspendOrCancelOfferingData {
pub offering_id: [u8; 32],
pub action: u8,
pub nonce: u64,
}
fn blake3_domain_bincode<T: Serialize>(domain: &[u8], value: &T) -> [u8; 32] {
let inner = bincode::serialize(value)
.expect("commitment input is infallibly bincode-serializable");
let mut buf: Vec<u8> = Vec::with_capacity(domain.len() + inner.len());
buf.extend_from_slice(domain);
buf.extend_from_slice(&inner);
*blake3::hash(&buf).as_bytes()
}
pub fn catalog_id(
institution_id: &[u8; 32],
department: &str,
course_code: &str,
version: u32,
nonce: u64,
) -> [u8; 32] {
blake3_domain_bincode(
DOMAIN_CATALOG_ID,
&(*institution_id, department, course_code, version, nonce),
)
}
pub fn offering_id(
catalog_id: &[u8; 32],
term: &str,
section: &str,
creator: &Address,
nonce: u64,
) -> [u8; 32] {
blake3_domain_bincode(
DOMAIN_OFFERING_ID,
&(*catalog_id, term, section, *creator.as_bytes(), nonce),
)
}
pub fn student_commitment(
subject: &[u8; 32],
offering_id: &[u8; 32],
salt: &[u8; 32],
) -> [u8; 32] {
blake3_domain_bincode(
DOMAIN_STUDENT_COMMITMENT,
&(*subject, *offering_id, *salt),
)
}
pub fn submission_commitment(
offering_id: &[u8; 32],
assessment_id: &[u8; 32],
student_commitment: &[u8; 32],
attempt: u16,
work_hash: &[u8; 32],
salt: &[u8; 32],
) -> [u8; 32] {
blake3_domain_bincode(
DOMAIN_SUBMISSION_COMMITMENT,
&(
*offering_id,
*assessment_id,
*student_commitment,
attempt,
*work_hash,
*salt,
),
)
}
pub fn grade_commitment(
offering_id: &[u8; 32],
assessment_id: &[u8; 32],
student_commitment: &[u8; 32],
grade_value: &[u8],
salt: &[u8; 32],
) -> [u8; 32] {
blake3_domain_bincode(
DOMAIN_GRADE_COMMITMENT,
&(
*offering_id,
*assessment_id,
*student_commitment,
grade_value,
*salt,
),
)
}