///|
pub struct MxRecordData {
preference_value : Int
exchange_value : DomainName
} derive(Eq, Debug)
///|
pub struct SrvRecordData {
priority_value : Int
weight_value : Int
port_value : Int
target_value : DomainName
} derive(Eq, Debug)
///|
pub struct CaaRecordData {
flags_value : Int
tag_value : String
value_value : String
} derive(Eq, Debug)
///|
pub struct SoaRecordData {
primary_name_value : DomainName
responsible_name_value : DomainName
serial_value : UInt64
refresh_value : Ttl
retry_value : Ttl
expire_value : Ttl
minimum_value : Ttl
} derive(Eq, Debug)
///|
pub struct GenericRecordData {
record_type_value : String
field_values : Array[String]
} derive(Eq, Debug)
///|
pub(all) enum RecordData {
AData(Ipv4Address)
AaaaData(Ipv6Address)
NsData(DomainName)
CnameData(DomainName)
PtrData(DomainName)
MxData(MxRecordData)
TxtData(Array[String])
SrvData(SrvRecordData)
CaaData(CaaRecordData)
SoaData(SoaRecordData)
GenericData(GenericRecordData)
} derive(Eq, Debug)
///|
fn record_data_error(
code : ZoneErrorCode,
message : String,
token : ZoneToken,
) -> ZoneError {
ZoneError::new(code, message, token.span())
}
///|
fn require_record_fields(
fields : Array[ZoneToken],
count : Int,
record_type : ZoneToken,
) -> Result[Unit, ZoneError] {
if fields.length() == count {
Ok(())
} else {
Err(
record_data_error(
MissingRecordField,
"record data has an invalid number of fields",
record_type,
),
)
}
}
///|
fn parse_bounded_decimal(
token : ZoneToken,
maximum : UInt64,
) -> Result[UInt64, ZoneError] {
let text = token.text()
if text.length() == 0 {
return Err(
record_data_error(InvalidRecordData, "numeric field is empty", token),
)
}
let mut value = 0UL
for index = 0; index < text.length(); index = index + 1 {
let code = text[index].to_int()
if code < 48 || code > 57 {
return Err(
record_data_error(
InvalidRecordData,
"numeric record field must contain decimal digits",
token,
),
)
}
let digit = (code - 48).to_uint64()
if value > maximum / 10UL || value * 10UL > maximum - digit {
return Err(
record_data_error(
NumericFieldOutOfRange,
"numeric record field exceeds its allowed range",
token,
),
)
}
value = value * 10UL + digit
}
Ok(value)
}
///|
fn parse_target_name(
token : ZoneToken,
origin : DomainName,
) -> Result[DomainName, ZoneError] {
match resolve_zone_name(token.text(), origin) {
Ok(value) => Ok(value)
Err(error) =>
Err(ZoneError::new(InvalidRecordData, error.message(), token.span()))
}
}
///|
fn parse_single_name_data(
fields : Array[ZoneToken],
record_type : ZoneToken,
origin : DomainName,
) -> Result[DomainName, ZoneError] {
match require_record_fields(fields, 1, record_type) {
Ok(_) => parse_target_name(fields[0], origin)
Err(error) => Err(error)
}
}
///|
fn parse_mx_data(
fields : Array[ZoneToken],
record_type : ZoneToken,
origin : DomainName,
) -> Result[RecordData, ZoneError] {
match require_record_fields(fields, 2, record_type) {
Ok(_) => ()
Err(error) => return Err(error)
}
let preference = match parse_bounded_decimal(fields[0], 65535UL) {
Ok(value) => value.to_int()
Err(error) => return Err(error)
}
let exchange = match parse_target_name(fields[1], origin) {
Ok(value) => value
Err(error) => return Err(error)
}
Ok(MxData({ preference_value: preference, exchange_value: exchange }))
}
///|
fn parse_txt_data(
fields : Array[ZoneToken],
record_type : ZoneToken,
) -> Result[RecordData, ZoneError] {
if fields.length() == 0 {
return Err(
record_data_error(
MissingRecordField,
"TXT record requires at least one character string",
record_type,
),
)
}
let values : Array[String] = []
for field in fields {
if field.text().length() > 255 {
return Err(
record_data_error(
InvalidRecordData,
"TXT character string exceeds 255 octets",
field,
),
)
}
values.push(field.text())
}
Ok(TxtData(values))
}
///|
fn parse_srv_data(
fields : Array[ZoneToken],
record_type : ZoneToken,
origin : DomainName,
) -> Result[RecordData, ZoneError] {
match require_record_fields(fields, 4, record_type) {
Ok(_) => ()
Err(error) => return Err(error)
}
let values : Array[Int] = []
for index = 0; index < 3; index = index + 1 {
match parse_bounded_decimal(fields[index], 65535UL) {
Ok(value) => values.push(value.to_int())
Err(error) => return Err(error)
}
}
let target = match parse_target_name(fields[3], origin) {
Ok(value) => value
Err(error) => return Err(error)
}
Ok(
SrvData({
priority_value: values[0],
weight_value: values[1],
port_value: values[2],
target_value: target,
}),
)
}
///|
fn valid_caa_tag(text : String) -> Bool {
if text.length() == 0 || text.length() > 15 {
return false
}
for index = 0; index < text.length(); index = index + 1 {
let code = text[index].to_int()
if !((code >= 65 && code <= 90) ||
(code >= 97 && code <= 122) ||
(code >= 48 && code <= 57) ||
code == 45) {
return false
}
}
true
}
///|
fn parse_caa_data(
fields : Array[ZoneToken],
record_type : ZoneToken,
) -> Result[RecordData, ZoneError] {
match require_record_fields(fields, 3, record_type) {
Ok(_) => ()
Err(error) => return Err(error)
}
let flags = match parse_bounded_decimal(fields[0], 255UL) {
Ok(value) => value.to_int()
Err(error) => return Err(error)
}
if !valid_caa_tag(fields[1].text()) {
return Err(
record_data_error(InvalidRecordData, "CAA tag is invalid", fields[1]),
)
}
Ok(
CaaData({
flags_value: flags,
tag_value: ascii_lower(fields[1].text()),
value_value: fields[2].text(),
}),
)
}
///|
fn parse_soa_ttl(token : ZoneToken) -> Result[Ttl, ZoneError] {
match parse_ttl(token.text()) {
Ok(value) => Ok(value)
Err(error) =>
Err(ZoneError::new(InvalidRecordData, error.message(), token.span()))
}
}
///|
fn parse_soa_data(
fields : Array[ZoneToken],
record_type : ZoneToken,
origin : DomainName,
) -> Result[RecordData, ZoneError] {
match require_record_fields(fields, 7, record_type) {
Ok(_) => ()
Err(error) => return Err(error)
}
let primary = match parse_target_name(fields[0], origin) {
Ok(value) => value
Err(error) => return Err(error)
}
let responsible = match parse_target_name(fields[1], origin) {
Ok(value) => value
Err(error) => return Err(error)
}
let serial = match parse_bounded_decimal(fields[2], 0xFFFFFFFFUL) {
Ok(value) => value
Err(error) => return Err(error)
}
let timers : Array[Ttl] = []
for index = 3; index < 7; index = index + 1 {
match parse_soa_ttl(fields[index]) {
Ok(value) => timers.push(value)
Err(error) => return Err(error)
}
}
Ok(
SoaData({
primary_name_value: primary,
responsible_name_value: responsible,
serial_value: serial,
refresh_value: timers[0],
retry_value: timers[1],
expire_value: timers[2],
minimum_value: timers[3],
}),
)
}
///|
fn valid_generic_type(text : String) -> Bool {
if text.length() == 0 {
return false
}
for index = 0; index < text.length(); index = index + 1 {
let code = text[index].to_int()
if !((code >= 65 && code <= 90) ||
(code >= 97 && code <= 122) ||
(code >= 48 && code <= 57) ||
code == 45) {
return false
}
}
true
}
///|
fn uppercase_ascii(text : String) -> String {
let mut result = ""
for index = 0; index < text.length(); index = index + 1 {
let code = text[index].to_int()
let value = if code >= 97 && code <= 122 { code - 32 } else { code }
result = result + ascii_character(value)
}
result
}
///|
fn generic_record_data(
record_type : ZoneToken,
fields : Array[ZoneToken],
) -> Result[RecordData, ZoneError] {
if !valid_generic_type(record_type.text()) {
return Err(
record_data_error(
InvalidRecordType,
"record type contains invalid characters",
record_type,
),
)
}
if fields.length() == 0 {
return Err(
record_data_error(
MissingRecordField,
"generic record data is empty",
record_type,
),
)
}
let values : Array[String] = []
for field in fields {
values.push(field.text())
}
Ok(
GenericData({
record_type_value: uppercase_ascii(record_type.text()),
field_values: values,
}),
)
}
///|
pub fn parse_record_data(
record_type : ZoneToken,
fields : Array[ZoneToken],
origin : DomainName,
) -> Result[RecordData, ZoneError] {
match ascii_lower(record_type.text()) {
"a" => {
match require_record_fields(fields, 1, record_type) {
Ok(_) => ()
Err(error) => return Err(error)
}
match parse_ipv4(fields[0].text()) {
Ok(value) => Ok(AData(value))
Err(error) =>
Err(
ZoneError::new(InvalidRecordData, error.message(), fields[0].span()),
)
}
}
"aaaa" => {
match require_record_fields(fields, 1, record_type) {
Ok(_) => ()
Err(error) => return Err(error)
}
match parse_ipv6(fields[0].text()) {
Ok(value) => Ok(AaaaData(value))
Err(error) =>
Err(
ZoneError::new(InvalidRecordData, error.message(), fields[0].span()),
)
}
}
"ns" =>
match parse_single_name_data(fields, record_type, origin) {
Ok(value) => Ok(NsData(value))
Err(error) => Err(error)
}
"cname" =>
match parse_single_name_data(fields, record_type, origin) {
Ok(value) => Ok(CnameData(value))
Err(error) => Err(error)
}
"ptr" =>
match parse_single_name_data(fields, record_type, origin) {
Ok(value) => Ok(PtrData(value))
Err(error) => Err(error)
}
"mx" => parse_mx_data(fields, record_type, origin)
"txt" => parse_txt_data(fields, record_type)
"srv" => parse_srv_data(fields, record_type, origin)
"caa" => parse_caa_data(fields, record_type)
"soa" => parse_soa_data(fields, record_type, origin)
_ => generic_record_data(record_type, fields)
}
}
///|
pub fn parse_record_data_statement(
statement : ZoneStatement,
origin : DomainName,
) -> Result[RecordData, ZoneError] {
let tokens = statement.tokens()
if tokens.length() == 0 {
return Err(
ZoneError::new(
MissingRecordField,
"record data statement is empty",
SourceSpan::point(0, 0),
),
)
}
let fields : Array[ZoneToken] = []
for index = 1; index < tokens.length(); index = index + 1 {
fields.push(tokens[index])
}
parse_record_data(tokens[0], fields, origin)
}
///|
pub fn MxRecordData::preference(self : MxRecordData) -> Int {
self.preference_value
}
///|
pub fn MxRecordData::exchange(self : MxRecordData) -> DomainName {
self.exchange_value
}
///|
pub fn SrvRecordData::priority(self : SrvRecordData) -> Int {
self.priority_value
}
///|
pub fn SrvRecordData::weight(self : SrvRecordData) -> Int {
self.weight_value
}
///|
pub fn SrvRecordData::port(self : SrvRecordData) -> Int {
self.port_value
}
///|
pub fn SrvRecordData::target(self : SrvRecordData) -> DomainName {
self.target_value
}
///|
pub fn CaaRecordData::flags(self : CaaRecordData) -> Int {
self.flags_value
}
///|
pub fn CaaRecordData::tag(self : CaaRecordData) -> String {
self.tag_value
}
///|
pub fn CaaRecordData::value(self : CaaRecordData) -> String {
self.value_value
}
///|
pub fn SoaRecordData::primary_name(self : SoaRecordData) -> DomainName {
self.primary_name_value
}
///|
pub fn SoaRecordData::responsible_name(self : SoaRecordData) -> DomainName {
self.responsible_name_value
}
///|
pub fn SoaRecordData::serial(self : SoaRecordData) -> UInt64 {
self.serial_value
}
///|
pub fn SoaRecordData::refresh(self : SoaRecordData) -> Ttl {
self.refresh_value
}
///|
pub fn SoaRecordData::retry(self : SoaRecordData) -> Ttl {
self.retry_value
}
///|
pub fn SoaRecordData::expire(self : SoaRecordData) -> Ttl {
self.expire_value
}
///|
pub fn SoaRecordData::minimum(self : SoaRecordData) -> Ttl {
self.minimum_value
}
///|
pub fn GenericRecordData::record_type(self : GenericRecordData) -> String {
self.record_type_value
}
///|
pub fn GenericRecordData::fields(self : GenericRecordData) -> Array[String] {
self.field_values.copy()
}