blob: dfbe600fd79c086f7c8e2073b4534eff7975f1f6 [file]
// Copyright 2026 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "iree/async/address.h"
#include <string.h>
#include "iree/async/address_impl.h"
//===----------------------------------------------------------------------===//
// Formatting helpers (write into guaranteed-large-enough buffer)
//===----------------------------------------------------------------------===//
// Writes a single decimal unsigned integer. Returns chars written.
static iree_host_size_t iree_address_write_uint(char* buffer, uint32_t value) {
if (value == 0) {
buffer[0] = '0';
return 1;
}
char digits[10];
int count = 0;
while (value > 0) {
digits[count++] = '0' + (char)(value % 10);
value /= 10;
}
for (int i = count - 1; i >= 0; --i) {
buffer[count - 1 - i] = digits[i];
}
return (iree_host_size_t)count;
}
// Writes a 1-4 digit lowercase hex value (no leading zeros). Returns chars
// written.
static iree_host_size_t iree_address_write_hex16(char* buffer, uint16_t value) {
static const char hex_chars[] = "0123456789abcdef";
if (value == 0) {
buffer[0] = '0';
return 1;
}
char digits[4];
int count = 0;
while (value > 0) {
digits[count++] = hex_chars[value & 0xf];
value >>= 4;
}
for (int i = count - 1; i >= 0; --i) {
buffer[count - 1 - i] = digits[i];
}
return (iree_host_size_t)count;
}
//===----------------------------------------------------------------------===//
// IPv4 parsing
//===----------------------------------------------------------------------===//
iree_status_t iree_async_address_parse_ipv4(iree_string_view_t host,
uint8_t out_octets[4]) {
const char* data = host.data;
iree_host_size_t length = host.size;
iree_host_size_t position = 0;
for (int octet_index = 0; octet_index < 4; ++octet_index) {
// Each octet must start with a digit.
if (position >= length || data[position] < '0' || data[position] > '9') {
return iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
"invalid IPv4 address: expected digit at "
"position %" PRIhsz,
position);
}
// Reject leading zeros (ambiguous octal interpretation).
if (data[position] == '0' && position + 1 < length &&
data[position + 1] >= '0' && data[position + 1] <= '9') {
return iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
"invalid IPv4 address: leading zero in octet %d",
octet_index);
}
// Accumulate decimal digits.
uint32_t value = 0;
while (position < length && data[position] >= '0' &&
data[position] <= '9') {
value = value * 10 + (uint32_t)(data[position] - '0');
if (value > 255) {
return iree_make_status(
IREE_STATUS_INVALID_ARGUMENT,
"invalid IPv4 address: octet %d value exceeds 255", octet_index);
}
++position;
}
out_octets[octet_index] = (uint8_t)value;
// After octets 0-2, expect a dot separator.
if (octet_index < 3) {
if (position >= length || data[position] != '.') {
return iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
"invalid IPv4 address: expected '.' after "
"octet %d",
octet_index);
}
++position;
}
}
// Reject trailing garbage.
if (position != length) {
return iree_make_status(
IREE_STATUS_INVALID_ARGUMENT,
"invalid IPv4 address: unexpected characters after address");
}
return iree_ok_status();
}
//===----------------------------------------------------------------------===//
// IPv6 parsing
//===----------------------------------------------------------------------===//
static inline int iree_hex_digit_value(char character) {
if (character >= '0' && character <= '9') return character - '0';
if (character >= 'a' && character <= 'f') return character - 'a' + 10;
if (character >= 'A' && character <= 'F') return character - 'A' + 10;
return -1;
}
iree_status_t iree_async_address_parse_ipv6(iree_string_view_t host,
uint8_t out_bytes[16]) {
const char* data = host.data;
iree_host_size_t length = host.size;
iree_host_size_t position = 0;
memset(out_bytes, 0, 16);
uint16_t groups[8];
int group_count = 0;
int compression_index = -1;
while (position < length && group_count < 8) {
// Check for :: (zero compression).
if (position + 1 < length && data[position] == ':' &&
data[position + 1] == ':') {
if (compression_index >= 0) {
return iree_make_status(
IREE_STATUS_INVALID_ARGUMENT,
"invalid IPv6 address: multiple :: not allowed");
}
compression_index = group_count;
position += 2;
if (position >= length) break;
continue;
}
// Colon separator between groups (not before first, not right after ::).
if (group_count > 0 && compression_index != group_count) {
if (position >= length || data[position] != ':') {
return iree_make_status(
IREE_STATUS_INVALID_ARGUMENT,
"invalid IPv6 address: expected ':' between groups");
}
++position;
}
// Scan ahead for '.' to detect IPv4-mapped suffix.
{
iree_host_size_t scan = position;
while (scan < length && data[scan] != ':' && data[scan] != '.') {
++scan;
}
if (scan < length && data[scan] == '.') {
if (group_count > 6) {
return iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
"invalid IPv6 address: too many groups "
"before IPv4-mapped suffix");
}
iree_string_view_t ipv4_part = {data + position, length - position};
uint8_t ipv4_octets[4];
IREE_RETURN_IF_ERROR(
iree_async_address_parse_ipv4(ipv4_part, ipv4_octets));
groups[group_count++] =
(uint16_t)((uint16_t)ipv4_octets[0] << 8 | ipv4_octets[1]);
groups[group_count++] =
(uint16_t)((uint16_t)ipv4_octets[2] << 8 | ipv4_octets[3]);
position = length;
break;
}
}
// Parse hex group (1-4 hex digits).
uint32_t value = 0;
int digit_count = 0;
while (position < length && digit_count < 4) {
int digit = iree_hex_digit_value(data[position]);
if (digit < 0) break;
value = (value << 4) | (uint32_t)digit;
++digit_count;
++position;
}
if (digit_count == 0) {
return iree_make_status(
IREE_STATUS_INVALID_ARGUMENT,
"invalid IPv6 address: expected hex digit at position %" PRIhsz,
position);
}
groups[group_count++] = (uint16_t)value;
}
// Reject trailing garbage.
if (position != length) {
return iree_make_status(
IREE_STATUS_INVALID_ARGUMENT,
"invalid IPv6 address: unexpected characters after address");
}
// Expand groups into 16 bytes.
if (compression_index >= 0) {
int groups_before = compression_index;
int groups_after = group_count - compression_index;
if (groups_before + groups_after >= 8) {
return iree_make_status(
IREE_STATUS_INVALID_ARGUMENT,
"invalid IPv6 address: too many groups with :: compression");
}
for (int i = 0; i < groups_before; ++i) {
out_bytes[i * 2] = (uint8_t)(groups[i] >> 8);
out_bytes[i * 2 + 1] = (uint8_t)(groups[i] & 0xff);
}
int end_offset = 8 - groups_after;
for (int i = 0; i < groups_after; ++i) {
int source_index = compression_index + i;
out_bytes[(end_offset + i) * 2] = (uint8_t)(groups[source_index] >> 8);
out_bytes[(end_offset + i) * 2 + 1] =
(uint8_t)(groups[source_index] & 0xff);
}
} else {
if (group_count != 8) {
return iree_make_status(
IREE_STATUS_INVALID_ARGUMENT,
"invalid IPv6 address: expected 8 groups, got %d (use :: for "
"zero compression)",
group_count);
}
for (int i = 0; i < 8; ++i) {
out_bytes[i * 2] = (uint8_t)(groups[i] >> 8);
out_bytes[i * 2 + 1] = (uint8_t)(groups[i] & 0xff);
}
}
return iree_ok_status();
}
//===----------------------------------------------------------------------===//
// IPv4 formatting
//===----------------------------------------------------------------------===//
iree_host_size_t iree_async_address_format_ipv4(const uint8_t octets[4],
uint16_t port, char* buffer) {
iree_host_size_t position = 0;
position += iree_address_write_uint(buffer + position, octets[0]);
buffer[position++] = '.';
position += iree_address_write_uint(buffer + position, octets[1]);
buffer[position++] = '.';
position += iree_address_write_uint(buffer + position, octets[2]);
buffer[position++] = '.';
position += iree_address_write_uint(buffer + position, octets[3]);
buffer[position++] = ':';
position += iree_address_write_uint(buffer + position, port);
return position;
}
//===----------------------------------------------------------------------===//
// IPv6 formatting (RFC 5952)
//===----------------------------------------------------------------------===//
iree_host_size_t iree_async_address_format_ipv6(const uint8_t bytes[16],
uint16_t port,
uint32_t scope_id,
char* buffer) {
// Convert to 8 groups.
uint16_t groups[8];
for (int i = 0; i < 8; ++i) {
groups[i] = (uint16_t)((uint16_t)bytes[i * 2] << 8 | bytes[i * 2 + 1]);
}
// RFC 5952: find longest run of consecutive zero groups (>1, leftmost wins).
int best_start = -1;
int best_length = 1;
int current_start = -1;
int current_length = 0;
for (int i = 0; i < 8; ++i) {
if (groups[i] == 0) {
if (current_start < 0) {
current_start = i;
current_length = 1;
} else {
++current_length;
}
} else {
if (current_length > best_length) {
best_start = current_start;
best_length = current_length;
}
current_start = -1;
current_length = 0;
}
}
if (current_length > best_length) {
best_start = current_start;
best_length = current_length;
}
// Check for IPv4-mapped (::ffff:0:0/96 prefix).
bool is_ipv4_mapped =
(groups[0] == 0 && groups[1] == 0 && groups[2] == 0 && groups[3] == 0 &&
groups[4] == 0 && groups[5] == 0xffff);
iree_host_size_t position = 0;
buffer[position++] = '[';
if (is_ipv4_mapped) {
memcpy(buffer + position, "::ffff:", 7);
position += 7;
position += iree_address_write_uint(buffer + position, bytes[12]);
buffer[position++] = '.';
position += iree_address_write_uint(buffer + position, bytes[13]);
buffer[position++] = '.';
position += iree_address_write_uint(buffer + position, bytes[14]);
buffer[position++] = '.';
position += iree_address_write_uint(buffer + position, bytes[15]);
} else {
for (int i = 0; i < 8; ++i) {
if (best_start >= 0 && i == best_start) {
memcpy(buffer + position, "::", 2);
position += 2;
i += best_length - 1;
continue;
}
if (i > 0 && !(best_start >= 0 && i == best_start + best_length)) {
buffer[position++] = ':';
}
position += iree_address_write_hex16(buffer + position, groups[i]);
}
}
if (scope_id != 0) {
buffer[position++] = '%';
position += iree_address_write_uint(buffer + position, scope_id);
}
buffer[position++] = ']';
buffer[position++] = ':';
position += iree_address_write_uint(buffer + position, port);
return position;
}
//===----------------------------------------------------------------------===//
// Unix path formatting
//===----------------------------------------------------------------------===//
iree_host_size_t iree_async_address_format_unix(iree_string_view_t path,
bool is_abstract,
char* buffer) {
iree_host_size_t position = 0;
memcpy(buffer + position, "unix:", 5);
position += 5;
if (is_abstract) {
buffer[position++] = '@';
}
memcpy(buffer + position, path.data, path.size);
position += path.size;
return position;
}
//===----------------------------------------------------------------------===//
// String-to-address parsing
//===----------------------------------------------------------------------===//
// Parses a decimal port string into a uint16_t.
// Rejects empty strings, non-digit characters, leading zeros (except "0"
// itself), and values exceeding 65535. |full_address| is included in error
// messages for context.
static iree_status_t iree_async_address_parse_port(
iree_string_view_t port_str, iree_string_view_t full_address,
uint16_t* out_port) {
if (port_str.size == 0) {
return iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
"empty port in address '%.*s'",
(int)full_address.size, full_address.data);
}
// Reject leading zeros (ambiguous; "0" alone is fine).
if (port_str.size > 1 && port_str.data[0] == '0') {
return iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
"leading zeros in port in address '%.*s'",
(int)full_address.size, full_address.data);
}
uint32_t value = 0;
for (iree_host_size_t i = 0; i < port_str.size; ++i) {
char character = port_str.data[i];
if (character < '0' || character > '9') {
return iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
"non-numeric port in address '%.*s'",
(int)full_address.size, full_address.data);
}
value = value * 10 + (uint32_t)(character - '0');
if (value > 65535) {
return iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
"port out of range in address '%.*s'",
(int)full_address.size, full_address.data);
}
}
*out_port = (uint16_t)value;
return iree_ok_status();
}
IREE_API_EXPORT iree_status_t iree_async_address_from_string(
iree_string_view_t address_str, iree_async_address_t* out_address) {
if (address_str.size == 0) {
return iree_make_status(IREE_STATUS_INVALID_ARGUMENT, "empty address");
}
// Unix: "unix:<path>"
if (iree_string_view_starts_with(address_str,
iree_make_cstring_view("unix:"))) {
iree_string_view_t path =
iree_string_view_substr(address_str, 5, IREE_HOST_SIZE_MAX);
return iree_async_address_from_unix(path, out_address);
}
// IPv6: "[<host>]:<port>"
if (address_str.data[0] == '[') {
// Find closing bracket.
iree_host_size_t close_bracket = IREE_HOST_SIZE_MAX;
for (iree_host_size_t i = 1; i < address_str.size; ++i) {
if (address_str.data[i] == ']') {
close_bracket = i;
break;
}
}
if (close_bracket == IREE_HOST_SIZE_MAX) {
return iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
"missing closing ']' in address '%.*s'",
(int)address_str.size, address_str.data);
}
// Extract host (content between brackets, may include %zone).
iree_string_view_t host =
iree_string_view_substr(address_str, 1, close_bracket - 1);
// Expect "]:port" after the closing bracket.
iree_host_size_t after_bracket = close_bracket + 1;
if (after_bracket >= address_str.size ||
address_str.data[after_bracket] != ':') {
return iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
"expected ':port' after ']' in address '%.*s'",
(int)address_str.size, address_str.data);
}
iree_string_view_t port_str = iree_string_view_substr(
address_str, after_bracket + 1, IREE_HOST_SIZE_MAX);
uint16_t port = 0;
IREE_RETURN_IF_ERROR(
iree_async_address_parse_port(port_str, address_str, &port));
return iree_async_address_from_ipv6(host, port, out_address);
}
// IPv4: "<host>:<port>" (split at last colon).
iree_host_size_t last_colon = IREE_HOST_SIZE_MAX;
for (iree_host_size_t i = address_str.size; i > 0; --i) {
if (address_str.data[i - 1] == ':') {
last_colon = i - 1;
break;
}
}
if (last_colon == IREE_HOST_SIZE_MAX) {
return iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
"missing ':port' in address '%.*s'",
(int)address_str.size, address_str.data);
}
iree_string_view_t host = iree_string_view_substr(address_str, 0, last_colon);
iree_string_view_t port_str =
iree_string_view_substr(address_str, last_colon + 1, IREE_HOST_SIZE_MAX);
uint16_t port = 0;
IREE_RETURN_IF_ERROR(
iree_async_address_parse_port(port_str, address_str, &port));
return iree_async_address_from_ipv4(host, port, out_address);
}