///|
pub fn validate_request(request : Message) -> Unit raise IppError {
if operation_from_code(request.code) is None {
raise Unsupported("request operation")
}
if request.groups.is_empty() || request.groups[0].tag != 1 {
raise Invalid("operation attributes must be the first group")
}
let operation = request.groups[0]
validate_common(operation)
if operation.attributes.length() < 3 ||
operation.attributes[2].name != "printer-uri" {
raise Invalid("printer-uri must be the third operation attribute")
}
match required_single_value(operation, "printer-uri") {
Uri(uri) => {
let _ = PrinterUri::parse(uri)
}
_ => raise Invalid("printer-uri requires one URI value")
}
if request.request_id < 1 {
raise Invalid("nonpositive request-id")
}
if request.groups.filter(fn(group) { group.tag == 1 }).length() != 1 {
raise Invalid("request has repeated operation groups")
}
if request.groups.any(fn(group) { group.tag != 1 && group.tag != 2 }) {
raise Invalid("request has unsupported attribute group")
}
if request.groups.filter(fn(group) { group.tag == 2 }).length() > 1 {
raise Invalid("request has repeated job groups")
}
match operation_from_code(request.code) {
Some(PrintJob) => {
if request.document.is_empty() {
raise Invalid("Print-Job requires a document")
}
require_document_format(operation)
}
Some(SendDocument) => {
require_job_id(operation)
require_document_format(operation)
match required_single_value(operation, "last-document") {
Boolean(_) => ()
_ => raise Invalid("Send-Document requires one last-document boolean")
}
if request.document.is_empty() {
raise Invalid("Send-Document requires a document")
}
}
Some(CancelJob) | Some(GetJobAttributes) => {
require_job_id(operation)
require_no_document(request)
}
Some(ValidateJob) => {
require_document_format(operation)
require_no_document(request)
}
_ => require_no_document(request)
}
}
///|
fn require_job_id(group : Group) -> Unit raise IppError {
if group.attributes.length() < 4 || group.attributes[3].name != "job-id" {
raise Invalid("job-id must be the fourth operation attribute")
}
match required_single_value(group, "job-id") {
Integer(value) if value > 0 => ()
_ => raise Invalid("operation requires one positive integer job-id")
}
}
///|
fn require_document_format(group : Group) -> Unit raise IppError {
match required_single_value(group, "document-format") {
MimeType(format) if valid_mime_type(format) => ()
_ => raise Invalid("invalid document-format tag or value")
}
}
///|
fn required_single_value(
group : Group,
name : String,
) -> IppValue raise IppError {
match group.find(name) {
Some(attribute) if attribute.values.length() == 1 => attribute.values[0]
_ => raise Invalid("operation requires exactly one value for \{name}")
}
}
///|
fn require_no_document(message : Message) -> Unit raise IppError {
if !message.document.is_empty() {
raise Invalid("unexpected document data")
}
}
///|
fn validate_common(group : Group) -> Unit raise IppError {
if group.attributes.length() < 2 {
raise Protocol("charset and language attributes missing")
}
let charset = group.attributes[0]
let language = group.attributes[1]
if charset.name != "attributes-charset" ||
charset.values != [Charset("utf-8")] {
raise Protocol("first attribute must be UTF-8 charset")
}
if language.name != "attributes-natural-language" ||
language.values.length() != 1 {
raise Protocol("second attribute must be natural language")
}
match language.values[0] {
Language(value) if !value.is_empty() && value.length() <= 63 => ()
_ => raise Protocol("invalid natural-language value")
}
}
///|
pub fn validate_response(
request : Message,
response : Message,
) -> Unit raise IppError {
if response.request_id != request.request_id {
raise Protocol("response request-id does not match request")
}
if response.groups.is_empty() || response.groups[0].tag != 1 {
raise Protocol("response must begin with operation attributes")
}
validate_common(response.groups[0])
if response.groups.filter(fn(group) { group.tag == 1 }).length() != 1 {
raise Protocol("response has repeated operation groups")
}
if !response.document.is_empty() {
raise Protocol("unexpected document in response")
}
if response.code > 0x05ff ||
(response.code > 0x00ff && response.code < 0x0400) {
raise Protocol("invalid status-code class")
}
}
///|
pub fn unsupported_attributes(response : Message) -> Array[Attribute] {
let result : Array[Attribute] = []
for group in response.groups {
if group.tag == 5 {
for attribute in group.attributes {
result.push(attribute)
}
}
}
result
}
///|
pub fn response_status_message(response : Message) -> String {
let label = status_name(response.code)
match response.find_group(1) {
Some(group) =>
match group.get_string("status-message") {
Some(message) => "\{label}: \{message}"
None => label
}
None => label
}
}
///|
pub fn require_success(response : Message) -> Unit raise IppError {
if !response.is_success() {
raise Protocol(response_status_message(response))
}
}