Skip to content

Commit

Permalink
did:dht refactor step 1 (#60)
Browse files Browse the repository at this point in the history
Co-authored-by: Ethan Lee <[email protected]>
  • Loading branch information
mistermoe and ethan-tbd authored Apr 18, 2024
1 parent f21f616 commit eb6746f
Show file tree
Hide file tree
Showing 35 changed files with 1,788 additions and 67 deletions.
2 changes: 1 addition & 1 deletion CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* @mistermoe @ethan-tbd @kirahsapong @wesbillman
* @mistermoe @ethan-tbd @kirahsapong @wesbillman @diehuxx
3 changes: 2 additions & 1 deletion packages/web5/lib/src/dids/did_dht/did_dht.dart
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ class DidDht {
static Future<DidResolutionResult> resolve(
Did did, {
String relayUrl = 'https://diddht.tbddev.org',
HttpClient? client,
}) async {
if (did.method != methodName) {
return DidResolutionResult.withError(DidResolutionError.invalidDid);
Expand All @@ -149,7 +150,7 @@ class DidDht {
final parsedRelayUrl = Uri.parse(relayUrl);
final resolutionUrl = parsedRelayUrl.replace(path: did.id);

final httpClient = HttpClient();
final httpClient = client ??= HttpClient();
final request = await httpClient.getUrl(resolutionUrl);
final response = await request.close();

Expand Down
180 changes: 180 additions & 0 deletions packages/web5/lib/src/dids/did_dht/dns/answer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import 'dart:typed_data';

import 'package:web5/src/dids/did_dht/dns/codec.dart';
import 'package:web5/src/dids/did_dht/dns/rdata_codecs.dart';
import 'package:web5/src/dids/did_dht/dns/consts.dart';
import 'package:web5/src/dids/did_dht/dns/name.dart';
import 'package:web5/src/dids/did_dht/dns/opt_data.dart';
import 'package:web5/src/dids/did_dht/dns/rdata.dart';
import 'package:web5/src/dids/did_dht/dns/record_class.dart';
import 'package:web5/src/dids/did_dht/dns/record_type.dart';

/// Represents an answer section in a DNS packet.
class Answer<T extends RData> {
/// The domain name to which this resource record pertains.
late RecordName name;

/// The type of the resource record. Specifies the meaning of the data in the RData field.
late RecordType type;

/// The class of the data in the RData field.
late RecordClass klass;

/// The specific data for this resource record, according to its type.
late T data;

/// The Time-To-Live of the resource record. This value is the number of seconds
/// that the resource record may be cached before it should be discarded.
late int ttl;

/// A flag indicating whether the cache flush bit is set for this record.
late bool flush;

/// For OPT records, this field specifies the maximum UDP payload size.
late int? udpPayloadSize;

/// For OPT records, this field specifies the extended RCODE.
late int? extendedRcode;

/// For OPT records, this field specifies the EDNS version.
late int? ednsVersion;

/// For OPT records, this field specifies the EDNS flags.
late int? flags;

/// For OPT records, this field indicates whether the DNSSEC OK bit is set.
late bool? flagDo;

/// Options for OPT records, dynamically determined based on the specific type of option.
late dynamic options;

Answer({
required this.name,
required this.type,
required this.klass,
required this.data,
required this.ttl,
this.flush = false,
this.udpPayloadSize,
this.extendedRcode,
this.ednsVersion,
this.flags,
this.flagDo,
this.options,
});

Answer._();

static final codec = _AnswerCodec();

/// Decodes a [Answer] from a byte buffer [buf] starting at the given [offset].
///
/// Throws [FormatException] if the buffer data cannot be decoded into a valid DNS answer.
factory Answer.decode(Uint8List buf, int offset) =>
codec.decode(buf, offset: offset).value as Answer<T>;

Uint8List encode({Uint8List? buf, int offset = 0}) =>
codec.encode(this, input: buf, offset: offset).value;

int encodingLength() {
return name.encodingLength() + 8 + data.encodingLength();
}
}

class _AnswerCodec implements Codec<Answer> {
@override
EncodeResult encode(
Answer answer, {
Uint8List? input,
int offset = 0,
}) {
final buf = input ?? Uint8List(answer.encodingLength());
final oldOffset = offset;

final n = RecordName.codec.encode(answer.name, input: buf, offset: offset);
offset += n.offset;

ByteData.view(buf.buffer).setUint16(offset, answer.type.value, Endian.big);

if (answer.type == RecordType.OPT) {
if (answer.name.value != '.') {
throw Exception('OPT name must be root.');
}
ByteData.view(buf.buffer)
.setUint16(offset, answer.udpPayloadSize!, Endian.big);

buf[offset + 4] = answer.extendedRcode!;
buf[offset + 5] = answer.ednsVersion!;

ByteData.view(buf.buffer)
.setUint16(offset + 6, answer.flags ?? 0, Endian.big);

offset += 8;
// TODO: need OptDataCodec here
offset += answer.options.encode(buf, offset) as int;
} else {
final klassValue = answer.flush ? FLUSH_MASK : answer.klass.value;
ByteData.view(buf.buffer).setUint16(offset + 2, klassValue, Endian.big);

ByteData.view(buf.buffer).setUint32(offset + 4, answer.ttl, Endian.big);

offset += 8;

final result = RDataCodecs.encode(
answer.type,
answer.data,
input: buf,
offset: offset,
);
offset += result.offset;
}

return EncodeResult(buf, offset - oldOffset);
}

@override
DecodeResult<Answer> decode(Uint8List buf, {int offset = 0}) {
final originalOffset = offset;

final nameResult = RecordName.codec.decode(buf, offset: offset);
offset += nameResult.offset;

final byteData = ByteData.sublistView(buf);

final rawType = byteData.getUint16(offset, Endian.big);
final type = RecordType.fromValue(rawType);
offset += 2;

final answer = Answer._();
answer.name = nameResult.value;
answer.type = type;

if (type == RecordType.OPT) {
answer.udpPayloadSize = byteData.getUint16(offset + 2, Endian.big);
answer.extendedRcode = byteData.getUint8(offset + 4);
answer.ednsVersion = byteData.getUint8(offset + 5);

answer.flags = byteData.getUint16(offset + 6, Endian.big);
answer.flagDo = (answer.flags! >> 15) & 0x1 == 1;

answer.options = OptionData.decode(buf, offset + 8);

offset += (8 + answer.options.numBytes).toInt();
} else {
final rawDnsClass = byteData.getUint16(offset, Endian.big);
answer.klass = RecordClass.fromValue(rawDnsClass & NOT_FLUSH_MASK);

answer.flush = (rawDnsClass & FLUSH_MASK) != 0;
offset += 2;

answer.ttl = byteData.getUint32(offset, Endian.big);
offset += 4;

final result = RDataCodecs.decode(type, buf, offset: offset);
answer.data = result.value;
offset += result.offset;
}

return DecodeResult(answer, offset - originalOffset);
}
}
20 changes: 20 additions & 0 deletions packages/web5/lib/src/dids/did_dht/dns/codec.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'dart:typed_data';

abstract interface class Codec<T> {
EncodeResult encode(T value, {Uint8List? input, int offset});
DecodeResult<T> decode(Uint8List buf, {int offset});
}

class EncodeResult {
final Uint8List value;
final int offset;

EncodeResult(this.value, this.offset);
}

class DecodeResult<T> {
final T value;
final int offset;

DecodeResult(this.value, this.offset);
}
8 changes: 8 additions & 0 deletions packages/web5/lib/src/dids/did_dht/dns/consts.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// ignore_for_file: constant_identifier_names

const int QUERY_FLAG = 0;
const int RESPONSE_FLAG = 1 << 15;
const int FLUSH_MASK = 1 << 15;
const int NOT_FLUSH_MASK = ~FLUSH_MASK;
const int QU_MASK = 1 << 15;
const int NOT_QU_MASK = ~QU_MASK;
150 changes: 150 additions & 0 deletions packages/web5/lib/src/dids/did_dht/dns/header.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import 'dart:typed_data';

import 'package:web5/src/dids/did_dht/dns/codec.dart';
import 'package:web5/src/dids/did_dht/dns/opcode.dart';
import 'package:web5/src/dids/did_dht/dns/rcode.dart';

class Header {
/// Identifier assigned by the program that generates the query.
int id;

/// Whether this message is a query (0), or a response (1)
bool qr;

/// Specifies kind of query in this message.
OpCode opcode;

/// Specifies that the responding name server is an authority for the domain name in question section.
bool? aa;

/// Specifies whether the message was truncated.
bool tc;

/// Directs the name server to pursue the query recursively
bool rd;

/// Set or cleared in a response, and denotes whether recursive query support is available in the name server
bool? ra;

/// Reserved for future use, always set to 0.
bool z;

/// TODO: Find documentation for this field
bool? ad;

/// TODO: Find documentation for this field
bool? cd;

/// Response code
RCode? rcode;

/// Number of entries in the question section.
int qdcount;

/// Number of resource records in the answer section.
int ancount;

/// Number of name server resource records in the authority records section.
int nscount;

/// Number of resource records in the additional records section.
int arcount;

get numQuestions => qdcount;
get numAnswers => ancount;
get numAuthorities => nscount;
get numAdditionals => arcount;

final numBytes = 12;

Header({
required this.id,
required this.qr,
required this.opcode,
this.aa,
required this.tc,
required this.rd,
this.ra,
this.z = false,
this.ad,
this.cd,
this.rcode,
required this.qdcount,
required this.ancount,
required this.nscount,
required this.arcount,
});

static final codec = _HeaderCodec();

factory Header.decode(Uint8List buf, {int offset = 0}) =>
codec.decode(buf, offset: offset).value;

Uint8List encode({Uint8List? buf, int offset = 0}) =>
codec.encode(this).value;

int encodingLength() => numBytes;
}

class _HeaderCodec implements Codec<Header> {
@override
EncodeResult encode(Header header, {Uint8List? input, int offset = 0}) {
final buf = input ?? Uint8List(12);
final byteData = ByteData.sublistView(buf);

byteData.setUint16(offset, header.id, Endian.big);
offset += 2;

final flags = (header.qr ? 1 : 0) << 15 |
(header.opcode.value & 0xF) << 11 |
(header.aa ?? false ? 1 : 0) << 10 |
(header.tc ? 1 : 0) << 9 |
(header.rd ? 1 : 0) << 8 |
(header.ra ?? false ? 1 : 0) << 7 |
(header.z ? 1 : 0) << 6 |
(header.ad ?? false ? 1 : 0) << 5 |
(header.cd ?? false ? 1 : 0) << 4 |
(header.rcode?.value ?? 0) & 0xF;

byteData.setUint16(offset, flags, Endian.big);
offset += 2;

byteData.setUint16(offset, header.qdcount, Endian.big);
offset += 2;
byteData.setUint16(offset, header.ancount, Endian.big);
offset += 2;
byteData.setUint16(offset, header.nscount, Endian.big);
offset += 2;
byteData.setUint16(offset, header.arcount, Endian.big);

return EncodeResult(buf, 12);
}

@override
DecodeResult<Header> decode(Uint8List buf, {int offset = 0}) {
if (buf.length < 12) throw Exception('Header must be 12 bytes');

final byteData = ByteData.sublistView(buf);
final flags = byteData.getUint16(offset + 2, Endian.big);

final header = Header(
id: byteData.getUint16(offset, Endian.big),
qr: (flags >> 15) & 0x1 == 1,
opcode: OpCode.fromValue((flags >> 11) & 0xf),
aa: (flags >> 10) & 0x1 == 1,
tc: (flags >> 9) & 0x1 == 1,
rd: (flags >> 8) & 0x1 == 1,
ra: (flags >> 7) & 0x1 == 1,
z: (flags >> 6) & 0x1 == 1,
ad: (flags >> 5) & 0x1 == 1,
cd: (flags >> 4) & 0x1 == 1,
rcode: RCode.fromValue(flags & 0xf),
qdcount: byteData.getUint16(offset + 4, Endian.big),
ancount: byteData.getUint16(offset + 6, Endian.big),
nscount: byteData.getUint16(offset + 8, Endian.big),
arcount: byteData.getUint16(offset + 10, Endian.big),
);

return DecodeResult(header, 12);
}
}
Loading

0 comments on commit eb6746f

Please sign in to comment.