///|
/// Protocol identifiers used by the first Multiaddr model.
pub(all) enum Protocol {
Ip4
Ip6
Dns
Dns4
Dns6
Dnsaddr
Tcp
Udp
P2p
Http
Https
Ws
Wss
Unknown(UInt64)
} derive(Eq, @debug.Debug)
///|
pub(all) enum ProtocolValueKind {
Empty
Ipv4
Ipv6
Domain
Port
PeerId
Text
Raw
} derive(Eq, @debug.Debug)
///|
pub fn Protocol::code(self : Protocol) -> UInt64 {
match protocol_info(self) {
Some(info) => info.code
None =>
match self {
Unknown(code) => code
_ => abort("known protocol missing from registry")
}
}
}
///|
pub fn Protocol::from_code(code : UInt64) -> Protocol {
match protocol_info_by_code(code) {
Some(info) => info.protocol
None => Unknown(code)
}
}
///|
pub fn Protocol::name(self : Protocol) -> String? {
match protocol_info(self) {
Some(info) => Some(info.name)
None => None
}
}
///|
pub fn Protocol::from_name(name : StringView) -> Protocol? {
let wanted = name.to_owned()
for info in protocol_table() {
if info.name == wanted {
return Some(info.protocol)
}
}
None
}
///|
pub fn Protocol::expected_value_kind(self : Protocol) -> ProtocolValueKind {
match protocol_info(self) {
Some(info) => info.value_kind
None => Raw
}
}
///|
pub fn Protocol::accepts_value(self : Protocol, value : ProtocolValue) -> Bool {
match self {
Unknown(_) => value.kind() == Text || value.kind() == Raw
_ => self.expected_value_kind() == value.kind()
}
}
///|
pub fn Protocol::diagnostic_name(self : Protocol) -> String {
match self.name() {
Some(name) => name
None => "protocol-" + self.code().to_string()
}
}
///|
pub struct Ipv4Address {
octets : Bytes
} derive(Eq, @debug.Debug)
///|
pub fn Ipv4Address::new(
a : Int,
b : Int,
c : Int,
d : Int,
) -> Result[Ipv4Address, MoonLoomError] {
if a < 0 || a > 255 {
return Err(InvalidField("ipv4 octet", a))
}
if b < 0 || b > 255 {
return Err(InvalidField("ipv4 octet", b))
}
if c < 0 || c > 255 {
return Err(InvalidField("ipv4 octet", c))
}
if d < 0 || d > 255 {
return Err(InvalidField("ipv4 octet", d))
}
Ok({ octets: [a.to_byte(), b.to_byte(), c.to_byte(), d.to_byte()], })
}
///|
pub fn Ipv4Address::from_octets(
value : BytesView,
) -> Result[Ipv4Address, MoonLoomError] {
if value.length() != 4 {
Err(InvalidField("ipv4 length", value.length()))
} else {
Ok({ octets: value.to_owned(), })
}
}
///|
pub fn Ipv4Address::octets(self : Ipv4Address) -> BytesView {
self.octets[:]
}
///|
pub struct Ipv6Address {
octets : Bytes
} derive(Eq, @debug.Debug)
///|
pub fn Ipv6Address::from_octets(
value : BytesView,
) -> Result[Ipv6Address, MoonLoomError] {
if value.length() != 16 {
Err(InvalidField("ipv6 length", value.length()))
} else {
Ok({ octets: value.to_owned(), })
}
}
///|
pub fn Ipv6Address::octets(self : Ipv6Address) -> BytesView {
self.octets[:]
}
///|
/// Typed values accepted by the Multiaddr domain model.
pub(all) enum ProtocolValue {
Empty
Ipv4(Ipv4Address)
Ipv6(Ipv6Address)
Domain(String)
Port(Int)
PeerId(String)
Text(String)
Raw(Bytes)
} derive(Eq, @debug.Debug)
///|
/// A typed protocol/value pair.
pub struct PathSegment {
protocol : Protocol
value : ProtocolValue
} derive(Eq, @debug.Debug)
///|
pub fn ProtocolValue::kind(self : ProtocolValue) -> ProtocolValueKind {
match self {
Empty => Empty
Ipv4(_) => Ipv4
Ipv6(_) => Ipv6
Domain(_) => Domain
Port(_) => Port
PeerId(_) => PeerId
Text(_) => Text
Raw(_) => Raw
}
}
///|
pub fn PathSegment::new(
protocol : Protocol,
value : ProtocolValue,
) -> Result[PathSegment, MoonLoomError] {
if !protocol.accepts_value(value) {
return Err(InvalidProtocolValue(protocol.diagnostic_name()))
}
match value {
Port(port) =>
if port < 0 || port > 65535 {
Err(InvalidPort(port))
} else {
Ok({ protocol, value, })
}
Domain(domain) | PeerId(domain) =>
if domain.is_empty() || domain.length() > 255 || domain.contains("\u{0}") {
Err(InvalidProtocolValue(protocol.diagnostic_name()))
} else {
Ok({ protocol, value, })
}
_ => Ok({ protocol, value, })
}
}
///|
pub fn PathSegment::protocol(self : PathSegment) -> Protocol {
self.protocol
}
///|
pub fn PathSegment::value(self : PathSegment) -> ProtocolValue {
self.value
}
///|
/// Type-safe Multiaddr domain model. Text and binary codecs are layered on top.
pub struct Multiaddr {
segments : Array[PathSegment]
} derive(Eq, @debug.Debug)
///|
pub fn Multiaddr::empty() -> Multiaddr {
{ segments: [], }
}
///|
pub fn Multiaddr::from_segments(
segments : Array[PathSegment],
limits : Limits,
) -> Result[Multiaddr, MoonLoomError] {
match limits.check_multiaddr(segments.length()) {
Ok(_) => {
let copy : Array[PathSegment] = Array(capacity=segments.length())
for segment in segments {
copy.push(segment)
}
Ok({ segments: copy, })
}
Err(err) => Err(err)
}
}
///|
pub fn Multiaddr::length(self : Multiaddr) -> Int {
self.segments.length()
}
///|
pub fn Multiaddr::is_empty(self : Multiaddr) -> Bool {
self.segments.is_empty()
}
///|
pub fn Multiaddr::get(self : Multiaddr, index : Int) -> PathSegment? {
if index < 0 || index >= self.segments.length() {
None
} else {
Some(self.segments[index])
}
}
///|
pub fn Multiaddr::segments(self : Multiaddr) -> ArrayView[PathSegment] {
self.segments[:]
}
///|
pub fn Multiaddr::push(
self : Multiaddr,
segment : PathSegment,
limits : Limits,
) -> Result[Multiaddr, MoonLoomError] {
match limits.check_multiaddr(self.length() + 1) {
Ok(_) => {
let copy : Array[PathSegment] = Array(capacity=self.length() + 1)
for item in self.segments {
copy.push(item)
}
copy.push(segment)
Ok({ segments: copy, })
}
Err(err) => Err(err)
}
}
///|
fn parse_decimal_bounded(input : StringView, maximum : Int) -> Int? {
if input.length() == 0 {
return None
}
let mut value = 0
for code_unit in input.code_units() {
let code = code_unit.to_int()
if code < 48 || code > 57 {
return None
}
value = value * 10 + (code - 48)
if value > maximum {
return None
}
}
Some(value)
}
///|
fn parse_ipv4_text(input : StringView) -> Result[Ipv4Address, MoonLoomError] {
let parts : Array[Int] = []
for part in input.split(".") {
match parse_decimal_bounded(part, 255) {
Some(value) => parts.push(value)
None => return Err(InvalidProtocolValue("ip4"))
}
}
if parts.length() != 4 {
return Err(InvalidProtocolValue("ip4"))
}
Ipv4Address::new(parts[0], parts[1], parts[2], parts[3])
}
///|
fn parse_hex_group(input : StringView) -> Int? {
if input.length() == 0 || input.length() > 4 {
return None
}
let mut value = 0
for code_unit in input.code_units() {
let code = code_unit.to_int()
let digit = if code >= 48 && code <= 57 {
code - 48
} else if code >= 97 && code <= 102 {
code - 97 + 10
} else if code >= 65 && code <= 70 {
code - 65 + 10
} else {
return None
}
value = (value << 4) | digit
}
Some(value)
}
///|
fn parse_ipv6_side(
input : StringView,
groups : Array[Int],
) -> Result[Unit, MoonLoomError] {
if input.length() == 0 {
return Ok(())
}
for part in input.split(":") {
match parse_hex_group(part) {
Some(value) => groups.push(value)
None => return Err(InvalidProtocolValue("ip6"))
}
}
Ok(())
}
///|
fn parse_ipv6_text(input : StringView) -> Result[Ipv6Address, MoonLoomError] {
let sides : Array[StringView] = []
for side in input.split("::") {
sides.push(side)
}
let groups : Array[Int] = []
if sides.length() == 1 {
match parse_ipv6_side(sides[0], groups) {
Ok(_) => ()
Err(err) => return Err(err)
}
if groups.length() != 8 {
return Err(InvalidProtocolValue("ip6"))
}
} else if sides.length() == 2 {
match parse_ipv6_side(sides[0], groups) {
Ok(_) => ()
Err(err) => return Err(err)
}
let right : Array[Int] = []
match parse_ipv6_side(sides[1], right) {
Ok(_) => ()
Err(err) => return Err(err)
}
if groups.length() + right.length() >= 8 {
return Err(InvalidProtocolValue("ip6"))
}
let missing = 8 - groups.length() - right.length()
for _ in 0..> 8).to_byte())
out.write_byte((group & 0xFF).to_byte())
}
Ipv6Address::from_octets(out.to_bytes())
}
///|
fn validate_ascii_value(
input : StringView,
context : String,
) -> Result[String, MoonLoomError] {
if input.length() == 0 || input.length() > 255 {
return Err(InvalidProtocolValue(context))
}
for code_unit in input.code_units() {
let code = code_unit.to_int()
let valid = (code >= 48 && code <= 57) ||
(code >= 65 && code <= 90) ||
(code >= 97 && code <= 122) ||
code == 45 ||
code == 46 ||
code == 95
if !valid {
return Err(InvalidProtocolValue(context))
}
}
Ok(input.to_owned())
}
///|
fn parse_protocol_value(
protocol : Protocol,
input : StringView,
) -> Result[ProtocolValue, MoonLoomError] {
match protocol {
Ip4 =>
match parse_ipv4_text(input) {
Ok(value) => Ok(ProtocolValue::Ipv4(value))
Err(err) => Err(err)
}
Ip6 =>
match parse_ipv6_text(input) {
Ok(value) => Ok(ProtocolValue::Ipv6(value))
Err(err) => Err(err)
}
Dns | Dns4 | Dns6 | Dnsaddr =>
match validate_ascii_value(input, protocol.diagnostic_name()) {
Ok(value) => Ok(ProtocolValue::Domain(value))
Err(err) => Err(err)
}
Tcp | Udp =>
match parse_decimal_bounded(input, 65535) {
Some(value) => Ok(ProtocolValue::Port(value))
None => Err(InvalidProtocolValue(protocol.diagnostic_name()))
}
P2p =>
match validate_ascii_value(input, "p2p") {
Ok(value) => Ok(ProtocolValue::PeerId(value))
Err(err) => Err(err)
}
Unknown(_) => Ok(ProtocolValue::Text(input.to_owned()))
Http | Https | Ws | Wss =>
Err(InvalidProtocolValue(protocol.diagnostic_name()))
}
}
///|
pub fn Multiaddr::parse(
text : StringView,
limits : Limits,
) -> Result[Multiaddr, MoonLoomError] {
match limits.check_multiaddr(text.length()) {
Ok(_) => ()
Err(err) => return Err(err)
}
if text.length() == 0 {
return Ok(Multiaddr::empty())
}
let parts : Array[StringView] = []
for part in text.split("/") {
parts.push(part)
}
if parts.length() == 0 || parts[0].length() != 0 {
return Err(InvalidField("multiaddr prefix", 0))
}
if parts.length() == 1 {
return Ok(Multiaddr::empty())
}
let segments : Array[PathSegment] = []
let mut index = 1
while index < parts.length() {
if parts[index].length() == 0 {
return Err(InvalidField("multiaddr protocol", index))
}
let protocol = match Protocol::from_name(parts[index]) {
Some(value) => value
None => return Err(UnknownProtocol(parts[index].to_owned()))
}
index += 1
let value = if protocol.expected_value_kind() == Empty {
ProtocolValue::Empty
} else {
if index >= parts.length() || parts[index].length() == 0 {
return Err(Truncated("multiaddr value", index))
}
let parsed = match parse_protocol_value(protocol, parts[index]) {
Ok(value) => value
Err(err) => return Err(err)
}
index += 1
parsed
}
let segment = match PathSegment::new(protocol, value) {
Ok(value) => value
Err(err) => return Err(err)
}
segments.push(segment)
}
Multiaddr::from_segments(segments, limits)
}
///|
///|
pub fn Multiaddr::to_bytes(
self : Multiaddr,
limits : Limits,
) -> Result[Bytes, MoonLoomError] {
let writer = WireWriter::new()
for segment in self.segments {
let protocol = segment.protocol()
match protocol {
Unknown(code) =>
return Err(UnknownProtocol("protocol-" + code.to_string()))
_ => ()
}
writer.write_varint(protocol.code())
match segment.value() {
Empty => ()
Ipv4(value) => writer.write_bytes(value.octets())
Ipv6(value) => writer.write_bytes(value.octets())
Domain(value) => writer.write_length_prefixed(@utf8.encode(value[:]))
Port(value) => {
if value < 0 || value > 65535 {
return Err(InvalidPort(value))
}
writer.write_u16_be(value)
}
PeerId(value) =>
match decode_base58(value[:]) {
Ok(bytes) => writer.write_length_prefixed(bytes)
Err(_) => return Err(InvalidProtocolValue("p2p"))
}
Text(_) | Raw(_) =>
return Err(InvalidProtocolValue(protocol.diagnostic_name()))
}
}
let bytes = writer.to_bytes()
match limits.check_multiaddr(bytes.length()) {
Ok(_) => Ok(bytes)
Err(err) => Err(err)
}
}
///|
pub fn Multiaddr::parse_bytes(
input : BytesView,
limits : Limits,
) -> Result[Multiaddr, MoonLoomError] {
let reader = match WireReader::new(input, limits) {
Ok(value) => value
Err(err) => return Err(err)
}
match limits.check_multiaddr(input.length()) {
Ok(_) => ()
Err(err) => return Err(err)
}
let segments : Array[PathSegment] = []
while !reader.is_done() {
let code = match reader.read_varint("multiaddr protocol") {
Ok(value) => value
Err(err) => return Err(err)
}
let protocol = Protocol::from_code(code)
match protocol {
Unknown(_) => return Err(UnknownProtocol("protocol-" + code.to_string()))
_ => ()
}
let value = match protocol.expected_value_kind() {
Empty => ProtocolValue::Empty
Ipv4 => {
let bytes = match reader.read_fixed("ip4", 4) {
Ok(value) => value
Err(err) => return Err(err)
}
match Ipv4Address::from_octets(bytes) {
Ok(value) => ProtocolValue::Ipv4(value)
Err(err) => return Err(err)
}
}
Ipv6 => {
let bytes = match reader.read_fixed("ip6", 16) {
Ok(value) => value
Err(err) => return Err(err)
}
match Ipv6Address::from_octets(bytes) {
Ok(value) => ProtocolValue::Ipv6(value)
Err(err) => return Err(err)
}
}
Domain => {
let bytes = match reader.read_length_prefixed("dns") {
Ok(value) => value
Err(err) => return Err(err)
}
let decoded = @utf8.decode(bytes) catch {
_ => return Err(InvalidProtocolValue(protocol.diagnostic_name()))
}
ProtocolValue::Domain(decoded)
}
Port => {
let port = match reader.read_u16_be(protocol.diagnostic_name()) {
Ok(value) => value
Err(err) => return Err(err)
}
ProtocolValue::Port(port)
}
PeerId => {
let bytes = match reader.read_length_prefixed("p2p") {
Ok(value) => value
Err(err) => return Err(err)
}
ProtocolValue::PeerId(encode_base58(bytes))
}
Text | Raw => return Err(InvalidProtocolValue(protocol.diagnostic_name()))
}
let segment = match PathSegment::new(protocol, value) {
Ok(value) => value
Err(err) => return Err(err)
}
segments.push(segment)
}
Multiaddr::from_segments(segments, limits)
}
///|
const HEX_DIGITS : Bytes = b"0123456789abcdef"
///|
pub fn Ipv4Address::to_text(self : Ipv4Address) -> String {
let out = StringBuilder(size_hint=15)
out.write_string(self.octets[0].to_int().to_string())
out.write_char('.')
out.write_string(self.octets[1].to_int().to_string())
out.write_char('.')
out.write_string(self.octets[2].to_int().to_string())
out.write_char('.')
out.write_string(self.octets[3].to_int().to_string())
out.to_string()
}
///|
fn Ipv6Address::group(self : Ipv6Address, index : Int) -> Int {
let offset = index * 2
(self.octets[offset].to_int() << 8) | self.octets[offset + 1].to_int()
}
///|
fn write_ipv6_group(out : StringBuilder, value : Int) -> Unit {
let mut started = false
for shift in [12, 8, 4, 0] {
let digit = (value >> shift) & 0x0F
if digit != 0 || started || shift == 0 {
started = true
out.write_char(HEX_DIGITS[digit].to_char())
}
}
}
///|
pub fn Ipv6Address::to_text(self : Ipv6Address) -> String {
let groups : Array[Int] = Array(capacity=8)
for index in 0..<8 {
groups.push(self.group(index))
}
let mut best_start = -1
let mut best_length = 0
let mut index = 0
while index < groups.length() {
if groups[index] != 0 {
index += 1
continue
}
let start = index
while index < groups.length() && groups[index] == 0 {
index += 1
}
let length = index - start
if length > best_length {
best_start = start
best_length = length
}
}
let compress = best_length >= 2
let out = StringBuilder(size_hint=39)
let mut index = 0
while index < groups.length() {
if compress && index == best_start {
out.write_string("::")
index += best_length
} else {
if index > 0 && !(compress && index == best_start + best_length) {
out.write_char(':')
}
write_ipv6_group(out, groups[index])
index += 1
}
}
out.to_string()
}
///|
fn render_segment(segment : PathSegment) -> Result[String, MoonLoomError] {
let protocol = segment.protocol()
match protocol {
Unknown(_) =>
Err(UnknownProtocol("protocol-" + protocol.code().to_string()))
_ => {
let value = match segment.value() {
Empty => ""
Ipv4(address) => "/" + address.to_text()
Ipv6(address) => "/" + address.to_text()
Domain(value) | PeerId(value) => "/" + value
Port(value) => "/" + value.to_string()
Text(_) | Raw(_) =>
return Err(InvalidProtocolValue(protocol.diagnostic_name()))
}
Ok("/" + protocol.diagnostic_name() + value)
}
}
}
///|
pub fn Multiaddr::to_text(self : Multiaddr) -> Result[String, MoonLoomError] {
if self.is_empty() {
return Ok("")
}
let out = StringBuilder()
for segment in self.segments {
let rendered = match render_segment(segment) {
Ok(value) => value
Err(err) => return Err(err)
}
out.write_string(rendered)
}
Ok(out.to_string())
}
///|
pub extend Protocol with Eq::{not_equal, equal}
///|
pub extend Protocol with @debug.Debug::{to_repr}
///|
pub extend ProtocolValueKind with Eq::{not_equal, equal}
///|
pub extend ProtocolValueKind with @debug.Debug::{to_repr}
///|
pub extend ProtocolValue with Eq::{not_equal, equal}
///|
pub extend ProtocolValue with @debug.Debug::{to_repr}
///|
pub extend PathSegment with Eq::{not_equal, equal}
///|
pub extend PathSegment with @debug.Debug::{to_repr}
///|
pub extend Ipv4Address with Eq::{not_equal, equal}
///|
pub extend Ipv4Address with @debug.Debug::{to_repr}
///|
pub extend Ipv6Address with Eq::{not_equal, equal}
///|
pub extend Ipv6Address with @debug.Debug::{to_repr}
///|
pub extend Multiaddr with Eq::{not_equal, equal}
///|
pub extend Multiaddr with @debug.Debug::{to_repr}