-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Ethan Lee <[email protected]>
- Loading branch information
Showing
35 changed files
with
1,788 additions
and
67 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
* @mistermoe @ethan-tbd @kirahsapong @wesbillman | ||
* @mistermoe @ethan-tbd @kirahsapong @wesbillman @diehuxx |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
Oops, something went wrong.