mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-02-22 15:59:50 -05:00
tabledb array work
This commit is contained in:
parent
83c8715742
commit
5d89de9bfe
@ -24,7 +24,7 @@ class ActiveAccountInfo {
|
||||
return KeyPair(key: identityKey, secret: identitySecret.value);
|
||||
}
|
||||
|
||||
Future<DHTRecordCrypto> makeConversationCrypto(
|
||||
Future<VeilidCrypto> makeConversationCrypto(
|
||||
TypedKey remoteIdentityPublicKey) async {
|
||||
final identitySecret = userLogin.identitySecret;
|
||||
final cs = await Veilid.instance.getCryptoSystem(identitySecret.kind);
|
||||
@ -33,8 +33,8 @@ class ActiveAccountInfo {
|
||||
identitySecret.value,
|
||||
utf8.encode('VeilidChat Conversation'));
|
||||
|
||||
final messagesCrypto = await DHTRecordCryptoPrivate.fromSecret(
|
||||
identitySecret.kind, sharedSecret);
|
||||
final messagesCrypto =
|
||||
await VeilidCryptoPrivate.fromSecret(identitySecret.kind, sharedSecret);
|
||||
return messagesCrypto;
|
||||
}
|
||||
|
||||
|
@ -120,9 +120,8 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
Future<void> _initSentMessagesCubit() async {
|
||||
final writer = _activeAccountInfo.conversationWriter;
|
||||
|
||||
_sentMessagesCubit = DHTShortArrayCubit(
|
||||
open: () async => DHTShortArray.openWrite(
|
||||
_localMessagesRecordKey, writer,
|
||||
_sentMessagesCubit = DHTLogCubit(
|
||||
open: () async => DHTLog.openWrite(_localMessagesRecordKey, writer,
|
||||
debugName: 'SingleContactMessagesCubit::_initSentMessagesCubit::'
|
||||
'SentMessages',
|
||||
parent: _localConversationRecordKey,
|
||||
@ -135,8 +134,8 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
|
||||
// Open remote messages key
|
||||
Future<void> _initRcvdMessagesCubit() async {
|
||||
_rcvdMessagesCubit = DHTShortArrayCubit(
|
||||
open: () async => DHTShortArray.openRead(_remoteMessagesRecordKey,
|
||||
_rcvdMessagesCubit = DHTLogCubit(
|
||||
open: () async => DHTLog.openRead(_remoteMessagesRecordKey,
|
||||
debugName: 'SingleContactMessagesCubit::_initRcvdMessagesCubit::'
|
||||
'RcvdMessages',
|
||||
parent: _remoteConversationRecordKey,
|
||||
@ -152,8 +151,8 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
final accountRecordKey =
|
||||
_activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
_reconciledMessagesCubit = DHTShortArrayCubit(
|
||||
open: () async => DHTShortArray.openOwned(_reconciledChatRecord,
|
||||
_reconciledMessagesCubit = DHTLogCubit(
|
||||
open: () async => DHTLog.openOwned(_reconciledChatRecord,
|
||||
debugName:
|
||||
'SingleContactMessagesCubit::_initReconciledMessagesCubit::'
|
||||
'ReconciledMessages',
|
||||
@ -166,10 +165,24 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Set the tail position of the log for pagination.
|
||||
// If tail is 0, the end of the log is used.
|
||||
// If tail is negative, the position is subtracted from the current log
|
||||
// length.
|
||||
// If tail is positive, the position is absolute from the head of the log
|
||||
// If follow is enabled, the tail offset will update when the log changes
|
||||
Future<void> setWindow(
|
||||
{int? tail, int? count, bool? follow, bool forceRefresh = false}) async {
|
||||
await _initWait();
|
||||
await _reconciledMessagesCubit!.setWindow(
|
||||
tail: tail, count: count, follow: follow, forceRefresh: forceRefresh);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Called when the sent messages cubit gets a change
|
||||
// This will re-render when messages are sent from another machine
|
||||
void _updateSentMessagesState(
|
||||
DHTShortArrayBusyState<proto.Message> avmessages) {
|
||||
void _updateSentMessagesState(DHTLogBusyState<proto.Message> avmessages) {
|
||||
final sentMessages = avmessages.state.asData?.value;
|
||||
if (sentMessages == null) {
|
||||
return;
|
||||
@ -182,27 +195,52 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
}
|
||||
|
||||
// Called when the received messages cubit gets a change
|
||||
void _updateRcvdMessagesState(
|
||||
DHTShortArrayBusyState<proto.Message> avmessages) {
|
||||
void _updateRcvdMessagesState(DHTLogBusyState<proto.Message> avmessages) {
|
||||
final rcvdMessages = avmessages.state.asData?.value;
|
||||
if (rcvdMessages == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add remote messages updates to queue to process asynchronously
|
||||
// Ignore offline state because remote messages are always fully delivered
|
||||
// This may happen once per client but should be idempotent
|
||||
_unreconciledMessagesQueue.addAllSync(rcvdMessages.map((x) => x.value));
|
||||
singleFuture(_rcvdMessagesCubit!, () async {
|
||||
// Get the timestamp of our most recent reconciled message
|
||||
final lastReconciledMessageTs =
|
||||
await _reconciledMessagesCubit!.operate((r) async {
|
||||
final len = r.length;
|
||||
if (len == 0) {
|
||||
return null;
|
||||
} else {
|
||||
final lastMessage =
|
||||
await r.getItemProtobuf(proto.Message.fromBuffer, len - 1);
|
||||
if (lastMessage == null) {
|
||||
throw StateError('should have gotten last message');
|
||||
}
|
||||
return lastMessage.timestamp;
|
||||
}
|
||||
});
|
||||
|
||||
// Update the view
|
||||
_renderState();
|
||||
// Find oldest message we have not yet reconciled
|
||||
|
||||
// // Go through all the ones from the cubit state first since we've already
|
||||
// // gotten them from the DHT
|
||||
// for (var rn = rcvdMessages.elements.length; rn >= 0; rn--) {
|
||||
// //
|
||||
// }
|
||||
|
||||
// // Add remote messages updates to queue to process asynchronously
|
||||
// // Ignore offline state because remote messages are always fully delivered
|
||||
// // This may happen once per client but should be idempotent
|
||||
// _unreconciledMessagesQueue.addAllSync(rcvdMessages.map((x) => x.value));
|
||||
|
||||
// Update the view
|
||||
_renderState();
|
||||
});
|
||||
}
|
||||
|
||||
// Called when the reconciled messages list gets a change
|
||||
// This can happen when multiple clients for the same identity are
|
||||
// reading and reconciling the same remote chat
|
||||
void _updateReconciledMessagesState(
|
||||
DHTShortArrayBusyState<proto.Message> avmessages) {
|
||||
DHTLogBusyState<proto.Message> avmessages) {
|
||||
// Update the view
|
||||
_renderState();
|
||||
}
|
||||
@ -210,85 +248,85 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
// Async process to reconcile messages sent or received in the background
|
||||
Future<void> _processUnreconciledMessages(
|
||||
IList<proto.Message> messages) async {
|
||||
await _reconciledMessagesCubit!
|
||||
.operateWrite((reconciledMessagesWriter) async {
|
||||
await _reconcileMessagesInner(
|
||||
reconciledMessagesWriter: reconciledMessagesWriter,
|
||||
messages: messages);
|
||||
});
|
||||
// await _reconciledMessagesCubit!
|
||||
// .operateAppendEventual((reconciledMessagesWriter) async {
|
||||
// await _reconcileMessagesInner(
|
||||
// reconciledMessagesWriter: reconciledMessagesWriter,
|
||||
// messages: messages);
|
||||
// });
|
||||
}
|
||||
|
||||
// Async process to send messages in the background
|
||||
Future<void> _processSendingMessages(IList<proto.Message> messages) async {
|
||||
for (final message in messages) {
|
||||
await _sentMessagesCubit!.operateWriteEventual(
|
||||
(writer) => writer.tryAddItem(message.writeToBuffer()));
|
||||
}
|
||||
await _sentMessagesCubit!.operateAppendEventual((writer) =>
|
||||
writer.tryAddItems(messages.map((m) => m.writeToBuffer()).toList()));
|
||||
}
|
||||
|
||||
Future<void> _reconcileMessagesInner(
|
||||
{required DHTRandomReadWrite reconciledMessagesWriter,
|
||||
{required DHTLogWriteOperations reconciledMessagesWriter,
|
||||
required IList<proto.Message> messages}) async {
|
||||
// Ensure remoteMessages is sorted by timestamp
|
||||
final newMessages = messages
|
||||
.sort((a, b) => a.timestamp.compareTo(b.timestamp))
|
||||
.removeDuplicates();
|
||||
// // Ensure remoteMessages is sorted by timestamp
|
||||
// final newMessages = messages
|
||||
// .sort((a, b) => a.timestamp.compareTo(b.timestamp))
|
||||
// .removeDuplicates();
|
||||
|
||||
// Existing messages will always be sorted by timestamp so merging is easy
|
||||
final existingMessages = await reconciledMessagesWriter
|
||||
.getItemRangeProtobuf(proto.Message.fromBuffer, 0);
|
||||
if (existingMessages == null) {
|
||||
throw Exception(
|
||||
'Could not load existing reconciled messages at this time');
|
||||
}
|
||||
// // Existing messages will always be sorted by timestamp so merging is easy
|
||||
// final existingMessages = await reconciledMessagesWriter
|
||||
// .getItemRangeProtobuf(proto.Message.fromBuffer, 0);
|
||||
// if (existingMessages == null) {
|
||||
// throw Exception(
|
||||
// 'Could not load existing reconciled messages at this time');
|
||||
// }
|
||||
|
||||
var ePos = 0;
|
||||
var nPos = 0;
|
||||
while (ePos < existingMessages.length && nPos < newMessages.length) {
|
||||
final existingMessage = existingMessages[ePos];
|
||||
final newMessage = newMessages[nPos];
|
||||
// var ePos = 0;
|
||||
// var nPos = 0;
|
||||
// while (ePos < existingMessages.length && nPos < newMessages.length) {
|
||||
// final existingMessage = existingMessages[ePos];
|
||||
// final newMessage = newMessages[nPos];
|
||||
|
||||
// If timestamp to insert is less than
|
||||
// the current position, insert it here
|
||||
final newTs = Timestamp.fromInt64(newMessage.timestamp);
|
||||
final existingTs = Timestamp.fromInt64(existingMessage.timestamp);
|
||||
final cmp = newTs.compareTo(existingTs);
|
||||
if (cmp < 0) {
|
||||
// New message belongs here
|
||||
// // If timestamp to insert is less than
|
||||
// // the current position, insert it here
|
||||
// final newTs = Timestamp.fromInt64(newMessage.timestamp);
|
||||
// final existingTs = Timestamp.fromInt64(existingMessage.timestamp);
|
||||
// final cmp = newTs.compareTo(existingTs);
|
||||
// if (cmp < 0) {
|
||||
// // New message belongs here
|
||||
|
||||
// Insert into dht backing array
|
||||
await reconciledMessagesWriter.tryInsertItem(
|
||||
ePos, newMessage.writeToBuffer());
|
||||
// Insert into local copy as well for this operation
|
||||
existingMessages.insert(ePos, newMessage);
|
||||
// // Insert into dht backing array
|
||||
// await reconciledMessagesWriter.tryInsertItem(
|
||||
// ePos, newMessage.writeToBuffer());
|
||||
// // Insert into local copy as well for this operation
|
||||
// existingMessages.insert(ePos, newMessage);
|
||||
|
||||
// Next message
|
||||
nPos++;
|
||||
ePos++;
|
||||
} else if (cmp == 0) {
|
||||
// Duplicate, skip
|
||||
nPos++;
|
||||
ePos++;
|
||||
} else if (cmp > 0) {
|
||||
// New message belongs later
|
||||
ePos++;
|
||||
}
|
||||
}
|
||||
// If there are any new messages left, append them all
|
||||
while (nPos < newMessages.length) {
|
||||
final newMessage = newMessages[nPos];
|
||||
// // Next message
|
||||
// nPos++;
|
||||
// ePos++;
|
||||
// } else if (cmp == 0) {
|
||||
// // Duplicate, skip
|
||||
// nPos++;
|
||||
// ePos++;
|
||||
// } else if (cmp > 0) {
|
||||
// // New message belongs later
|
||||
// ePos++;
|
||||
// }
|
||||
// }
|
||||
// // If there are any new messages left, append them all
|
||||
// while (nPos < newMessages.length) {
|
||||
// final newMessage = newMessages[nPos];
|
||||
|
||||
// Append to dht backing array
|
||||
await reconciledMessagesWriter.tryAddItem(newMessage.writeToBuffer());
|
||||
// Insert into local copy as well for this operation
|
||||
existingMessages.add(newMessage);
|
||||
// // Append to dht backing array
|
||||
// await reconciledMessagesWriter.tryAddItem(newMessage.writeToBuffer());
|
||||
// // Insert into local copy as well for this operation
|
||||
// existingMessages.add(newMessage);
|
||||
|
||||
nPos++;
|
||||
}
|
||||
// nPos++;
|
||||
// }
|
||||
}
|
||||
|
||||
// Produce a state for this cubit from the input cubits and queues
|
||||
void _renderState() {
|
||||
// xxx move into a singlefuture
|
||||
|
||||
// Get all reconciled messages
|
||||
final reconciledMessages =
|
||||
_reconciledMessagesCubit?.state.state.asData?.value;
|
||||
@ -307,14 +345,14 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
|
||||
// Generate state for each message
|
||||
final sentMessagesMap =
|
||||
IMap<Int64, DHTShortArrayElementState<proto.Message>>.fromValues(
|
||||
IMap<Int64, DHTLogElementState<proto.Message>>.fromValues(
|
||||
keyMapper: (x) => x.value.timestamp,
|
||||
values: sentMessages,
|
||||
values: sentMessages.elements,
|
||||
);
|
||||
final reconciledMessagesMap =
|
||||
IMap<Int64, DHTShortArrayElementState<proto.Message>>.fromValues(
|
||||
IMap<Int64, DHTLogElementState<proto.Message>>.fromValues(
|
||||
keyMapper: (x) => x.value.timestamp,
|
||||
values: reconciledMessages,
|
||||
values: reconciledMessages.elements,
|
||||
);
|
||||
final sendingMessagesMap = IMap<Int64, proto.Message>.fromValues(
|
||||
keyMapper: (x) => x.timestamp,
|
||||
@ -372,9 +410,8 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
.sort((x, y) => x.key.compareTo(y.key));
|
||||
final renderedState = messageKeys
|
||||
.map((x) => MessageState(
|
||||
author: x.value.message.author.toVeilid(),
|
||||
content: x.value.message,
|
||||
timestamp: Timestamp.fromInt64(x.key),
|
||||
text: x.value.message.text,
|
||||
sendState: x.value.sendState))
|
||||
.toIList();
|
||||
|
||||
@ -400,17 +437,16 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
final TypedKey _remoteMessagesRecordKey;
|
||||
final OwnedDHTRecordPointer _reconciledChatRecord;
|
||||
|
||||
late final DHTRecordCrypto _messagesCrypto;
|
||||
late final VeilidCrypto _messagesCrypto;
|
||||
|
||||
DHTShortArrayCubit<proto.Message>? _sentMessagesCubit;
|
||||
DHTShortArrayCubit<proto.Message>? _rcvdMessagesCubit;
|
||||
DHTShortArrayCubit<proto.Message>? _reconciledMessagesCubit;
|
||||
DHTLogCubit<proto.Message>? _sentMessagesCubit;
|
||||
DHTLogCubit<proto.Message>? _rcvdMessagesCubit;
|
||||
DHTLogCubit<proto.Message>? _reconciledMessagesCubit;
|
||||
|
||||
late final PersistentQueue<proto.Message> _unreconciledMessagesQueue;
|
||||
late final PersistentQueue<proto.Message> _sendingMessagesQueue;
|
||||
|
||||
StreamSubscription<DHTShortArrayBusyState<proto.Message>>? _sentSubscription;
|
||||
StreamSubscription<DHTShortArrayBusyState<proto.Message>>? _rcvdSubscription;
|
||||
StreamSubscription<DHTShortArrayBusyState<proto.Message>>?
|
||||
_reconciledSubscription;
|
||||
StreamSubscription<DHTLogBusyState<proto.Message>>? _sentSubscription;
|
||||
StreamSubscription<DHTLogBusyState<proto.Message>>? _rcvdSubscription;
|
||||
StreamSubscription<DHTLogBusyState<proto.Message>>? _reconciledSubscription;
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../proto/proto.dart' as proto;
|
||||
|
||||
part 'message_state.freezed.dart';
|
||||
part 'message_state.g.dart';
|
||||
|
||||
@ -23,9 +25,12 @@ enum MessageSendState {
|
||||
@freezed
|
||||
class MessageState with _$MessageState {
|
||||
const factory MessageState({
|
||||
required TypedKey author,
|
||||
// Content of the message
|
||||
@JsonKey(fromJson: proto.messageFromJson, toJson: proto.messageToJson)
|
||||
required proto.Message content,
|
||||
// Received or delivered timestamp
|
||||
required Timestamp timestamp,
|
||||
required String text,
|
||||
// The state of the mssage
|
||||
required MessageSendState? sendState,
|
||||
}) = _MessageState;
|
||||
|
||||
|
@ -20,9 +20,12 @@ MessageState _$MessageStateFromJson(Map<String, dynamic> json) {
|
||||
|
||||
/// @nodoc
|
||||
mixin _$MessageState {
|
||||
Typed<FixedEncodedString43> get author => throw _privateConstructorUsedError;
|
||||
Timestamp get timestamp => throw _privateConstructorUsedError;
|
||||
String get text => throw _privateConstructorUsedError;
|
||||
// Content of the message
|
||||
@JsonKey(fromJson: proto.messageFromJson, toJson: proto.messageToJson)
|
||||
proto.Message get content =>
|
||||
throw _privateConstructorUsedError; // Received or delivered timestamp
|
||||
Timestamp get timestamp =>
|
||||
throw _privateConstructorUsedError; // The state of the mssage
|
||||
MessageSendState? get sendState => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@ -38,9 +41,9 @@ abstract class $MessageStateCopyWith<$Res> {
|
||||
_$MessageStateCopyWithImpl<$Res, MessageState>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{Typed<FixedEncodedString43> author,
|
||||
{@JsonKey(fromJson: proto.messageFromJson, toJson: proto.messageToJson)
|
||||
proto.Message content,
|
||||
Timestamp timestamp,
|
||||
String text,
|
||||
MessageSendState? sendState});
|
||||
}
|
||||
|
||||
@ -57,24 +60,19 @@ class _$MessageStateCopyWithImpl<$Res, $Val extends MessageState>
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? author = null,
|
||||
Object? content = null,
|
||||
Object? timestamp = null,
|
||||
Object? text = null,
|
||||
Object? sendState = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
author: null == author
|
||||
? _value.author
|
||||
: author // ignore: cast_nullable_to_non_nullable
|
||||
as Typed<FixedEncodedString43>,
|
||||
content: null == content
|
||||
? _value.content
|
||||
: content // ignore: cast_nullable_to_non_nullable
|
||||
as proto.Message,
|
||||
timestamp: null == timestamp
|
||||
? _value.timestamp
|
||||
: timestamp // ignore: cast_nullable_to_non_nullable
|
||||
as Timestamp,
|
||||
text: null == text
|
||||
? _value.text
|
||||
: text // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
sendState: freezed == sendState
|
||||
? _value.sendState
|
||||
: sendState // ignore: cast_nullable_to_non_nullable
|
||||
@ -92,9 +90,9 @@ abstract class _$$MessageStateImplCopyWith<$Res>
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{Typed<FixedEncodedString43> author,
|
||||
{@JsonKey(fromJson: proto.messageFromJson, toJson: proto.messageToJson)
|
||||
proto.Message content,
|
||||
Timestamp timestamp,
|
||||
String text,
|
||||
MessageSendState? sendState});
|
||||
}
|
||||
|
||||
@ -109,24 +107,19 @@ class __$$MessageStateImplCopyWithImpl<$Res>
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? author = null,
|
||||
Object? content = null,
|
||||
Object? timestamp = null,
|
||||
Object? text = null,
|
||||
Object? sendState = freezed,
|
||||
}) {
|
||||
return _then(_$MessageStateImpl(
|
||||
author: null == author
|
||||
? _value.author
|
||||
: author // ignore: cast_nullable_to_non_nullable
|
||||
as Typed<FixedEncodedString43>,
|
||||
content: null == content
|
||||
? _value.content
|
||||
: content // ignore: cast_nullable_to_non_nullable
|
||||
as proto.Message,
|
||||
timestamp: null == timestamp
|
||||
? _value.timestamp
|
||||
: timestamp // ignore: cast_nullable_to_non_nullable
|
||||
as Timestamp,
|
||||
text: null == text
|
||||
? _value.text
|
||||
: text // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
sendState: freezed == sendState
|
||||
? _value.sendState
|
||||
: sendState // ignore: cast_nullable_to_non_nullable
|
||||
@ -139,26 +132,28 @@ class __$$MessageStateImplCopyWithImpl<$Res>
|
||||
@JsonSerializable()
|
||||
class _$MessageStateImpl with DiagnosticableTreeMixin implements _MessageState {
|
||||
const _$MessageStateImpl(
|
||||
{required this.author,
|
||||
{@JsonKey(fromJson: proto.messageFromJson, toJson: proto.messageToJson)
|
||||
required this.content,
|
||||
required this.timestamp,
|
||||
required this.text,
|
||||
required this.sendState});
|
||||
|
||||
factory _$MessageStateImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$MessageStateImplFromJson(json);
|
||||
|
||||
// Content of the message
|
||||
@override
|
||||
final Typed<FixedEncodedString43> author;
|
||||
@JsonKey(fromJson: proto.messageFromJson, toJson: proto.messageToJson)
|
||||
final proto.Message content;
|
||||
// Received or delivered timestamp
|
||||
@override
|
||||
final Timestamp timestamp;
|
||||
@override
|
||||
final String text;
|
||||
// The state of the mssage
|
||||
@override
|
||||
final MessageSendState? sendState;
|
||||
|
||||
@override
|
||||
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
|
||||
return 'MessageState(author: $author, timestamp: $timestamp, text: $text, sendState: $sendState)';
|
||||
return 'MessageState(content: $content, timestamp: $timestamp, sendState: $sendState)';
|
||||
}
|
||||
|
||||
@override
|
||||
@ -166,9 +161,8 @@ class _$MessageStateImpl with DiagnosticableTreeMixin implements _MessageState {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(DiagnosticsProperty('type', 'MessageState'))
|
||||
..add(DiagnosticsProperty('author', author))
|
||||
..add(DiagnosticsProperty('content', content))
|
||||
..add(DiagnosticsProperty('timestamp', timestamp))
|
||||
..add(DiagnosticsProperty('text', text))
|
||||
..add(DiagnosticsProperty('sendState', sendState));
|
||||
}
|
||||
|
||||
@ -177,18 +171,16 @@ class _$MessageStateImpl with DiagnosticableTreeMixin implements _MessageState {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$MessageStateImpl &&
|
||||
(identical(other.author, author) || other.author == author) &&
|
||||
(identical(other.content, content) || other.content == content) &&
|
||||
(identical(other.timestamp, timestamp) ||
|
||||
other.timestamp == timestamp) &&
|
||||
(identical(other.text, text) || other.text == text) &&
|
||||
(identical(other.sendState, sendState) ||
|
||||
other.sendState == sendState));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, author, timestamp, text, sendState);
|
||||
int get hashCode => Object.hash(runtimeType, content, timestamp, sendState);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@ -206,21 +198,20 @@ class _$MessageStateImpl with DiagnosticableTreeMixin implements _MessageState {
|
||||
|
||||
abstract class _MessageState implements MessageState {
|
||||
const factory _MessageState(
|
||||
{required final Typed<FixedEncodedString43> author,
|
||||
{@JsonKey(fromJson: proto.messageFromJson, toJson: proto.messageToJson)
|
||||
required final proto.Message content,
|
||||
required final Timestamp timestamp,
|
||||
required final String text,
|
||||
required final MessageSendState? sendState}) = _$MessageStateImpl;
|
||||
|
||||
factory _MessageState.fromJson(Map<String, dynamic> json) =
|
||||
_$MessageStateImpl.fromJson;
|
||||
|
||||
@override
|
||||
Typed<FixedEncodedString43> get author;
|
||||
@override
|
||||
@override // Content of the message
|
||||
@JsonKey(fromJson: proto.messageFromJson, toJson: proto.messageToJson)
|
||||
proto.Message get content;
|
||||
@override // Received or delivered timestamp
|
||||
Timestamp get timestamp;
|
||||
@override
|
||||
String get text;
|
||||
@override
|
||||
@override // The state of the mssage
|
||||
MessageSendState? get sendState;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
|
@ -8,9 +8,8 @@ part of 'message_state.dart';
|
||||
|
||||
_$MessageStateImpl _$$MessageStateImplFromJson(Map<String, dynamic> json) =>
|
||||
_$MessageStateImpl(
|
||||
author: Typed<FixedEncodedString43>.fromJson(json['author']),
|
||||
content: messageFromJson(json['content'] as Map<String, dynamic>),
|
||||
timestamp: Timestamp.fromJson(json['timestamp']),
|
||||
text: json['text'] as String,
|
||||
sendState: json['send_state'] == null
|
||||
? null
|
||||
: MessageSendState.fromJson(json['send_state']),
|
||||
@ -18,8 +17,7 @@ _$MessageStateImpl _$$MessageStateImplFromJson(Map<String, dynamic> json) =>
|
||||
|
||||
Map<String, dynamic> _$$MessageStateImplToJson(_$MessageStateImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'author': instance.author.toJson(),
|
||||
'content': messageToJson(instance.content),
|
||||
'timestamp': instance.timestamp.toJson(),
|
||||
'text': instance.text,
|
||||
'send_state': instance.sendState?.toJson(),
|
||||
};
|
||||
|
@ -65,7 +65,7 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
|
||||
.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
// Make a record that can store the reconciled version of the chat
|
||||
final reconciledChatRecord = await (await DHTShortArray.create(
|
||||
final reconciledChatRecord = await (await DHTLog.create(
|
||||
debugName:
|
||||
'ChatListCubit::getOrCreateChatSingleContact::ReconciledChat',
|
||||
parent: accountRecordKey))
|
||||
|
@ -121,7 +121,7 @@ class ContactInvitationListCubit
|
||||
schema: DHTSchema.smpl(oCnt: 1, members: [
|
||||
DHTSchemaMember(mCnt: 1, mKey: contactRequestWriter.key)
|
||||
]),
|
||||
crypto: const DHTRecordCryptoPublic()))
|
||||
crypto: const VeilidCryptoPublic()))
|
||||
.deleteScope((contactRequestInbox) async {
|
||||
// Store ContactRequest in owner subkey
|
||||
await contactRequestInbox.eventualWriteProtobuf(creq);
|
||||
@ -129,7 +129,7 @@ class ContactInvitationListCubit
|
||||
await contactRequestInbox.eventualWriteBytes(Uint8List(0),
|
||||
subkey: 1,
|
||||
writer: contactRequestWriter,
|
||||
crypto: await DHTRecordCryptoPrivate.fromTypedKeyPair(
|
||||
crypto: await VeilidCryptoPrivate.fromTypedKeyPair(
|
||||
TypedKeyPair.fromKeyPair(
|
||||
contactRequestInbox.key.kind, contactRequestWriter)));
|
||||
|
||||
|
@ -37,7 +37,7 @@ class ContactRequestInboxCubit
|
||||
return pool.openRecordRead(recordKey,
|
||||
debugName: 'ContactRequestInboxCubit::_open::'
|
||||
'ContactRequestInbox',
|
||||
crypto: await DHTRecordCryptoPrivate.fromTypedKeyPair(writer),
|
||||
crypto: await VeilidCryptoPrivate.fromTypedKeyPair(writer),
|
||||
parent: accountRecordKey,
|
||||
defaultSubkey: 1);
|
||||
}
|
||||
|
@ -285,13 +285,13 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
||||
required ActiveAccountInfo activeAccountInfo,
|
||||
required TypedKey remoteIdentityPublicKey,
|
||||
required TypedKey localConversationKey,
|
||||
required FutureOr<T> Function(DHTShortArray) callback,
|
||||
required FutureOr<T> Function(DHTLog) callback,
|
||||
}) async {
|
||||
final crypto =
|
||||
await activeAccountInfo.makeConversationCrypto(remoteIdentityPublicKey);
|
||||
final writer = activeAccountInfo.conversationWriter;
|
||||
|
||||
return (await DHTShortArray.create(
|
||||
return (await DHTLog.create(
|
||||
debugName: 'ConversationCubit::initLocalMessages::LocalMessages',
|
||||
parent: localConversationKey,
|
||||
crypto: crypto,
|
||||
@ -327,7 +327,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
||||
return update;
|
||||
}
|
||||
|
||||
Future<DHTRecordCrypto> _cachedConversationCrypto() async {
|
||||
Future<VeilidCrypto> _cachedConversationCrypto() async {
|
||||
var conversationCrypto = _conversationCrypto;
|
||||
if (conversationCrypto != null) {
|
||||
return conversationCrypto;
|
||||
@ -350,6 +350,6 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
||||
ConversationState _incrementalState = const ConversationState(
|
||||
localConversation: null, remoteConversation: null);
|
||||
//
|
||||
DHTRecordCrypto? _conversationCrypto;
|
||||
VeilidCrypto? _conversationCrypto;
|
||||
final WaitSet<void> _initWait = WaitSet();
|
||||
}
|
||||
|
12
lib/proto/extensions.dart
Normal file
12
lib/proto/extensions.dart
Normal file
@ -0,0 +1,12 @@
|
||||
import 'proto.dart' as proto;
|
||||
|
||||
proto.Message messageFromJson(Map<String, dynamic> j) =>
|
||||
proto.Message.create()..mergeFromJsonMap(j);
|
||||
|
||||
Map<String, dynamic> messageToJson(proto.Message m) => m.writeToJsonMap();
|
||||
|
||||
proto.ReconciledMessage reconciledMessageFromJson(Map<String, dynamic> j) =>
|
||||
proto.ReconciledMessage.create()..mergeFromJsonMap(j);
|
||||
|
||||
Map<String, dynamic> reconciledMessageToJson(proto.ReconciledMessage m) =>
|
||||
m.writeToJsonMap();
|
@ -1,5 +1,7 @@
|
||||
export 'package:veilid_support/dht_support/proto/proto.dart';
|
||||
export 'package:veilid_support/proto/proto.dart';
|
||||
|
||||
export 'extensions.dart';
|
||||
export 'veilidchat.pb.dart';
|
||||
export 'veilidchat.pbenum.dart';
|
||||
export 'veilidchat.pbjson.dart';
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -13,23 +13,6 @@ import 'dart:core' as $core;
|
||||
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
class AttachmentKind extends $pb.ProtobufEnum {
|
||||
static const AttachmentKind ATTACHMENT_KIND_UNSPECIFIED = AttachmentKind._(0, _omitEnumNames ? '' : 'ATTACHMENT_KIND_UNSPECIFIED');
|
||||
static const AttachmentKind ATTACHMENT_KIND_FILE = AttachmentKind._(1, _omitEnumNames ? '' : 'ATTACHMENT_KIND_FILE');
|
||||
static const AttachmentKind ATTACHMENT_KIND_IMAGE = AttachmentKind._(2, _omitEnumNames ? '' : 'ATTACHMENT_KIND_IMAGE');
|
||||
|
||||
static const $core.List<AttachmentKind> values = <AttachmentKind> [
|
||||
ATTACHMENT_KIND_UNSPECIFIED,
|
||||
ATTACHMENT_KIND_FILE,
|
||||
ATTACHMENT_KIND_IMAGE,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, AttachmentKind> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static AttachmentKind? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const AttachmentKind._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class Availability extends $pb.ProtobufEnum {
|
||||
static const Availability AVAILABILITY_UNSPECIFIED = Availability._(0, _omitEnumNames ? '' : 'AVAILABILITY_UNSPECIFIED');
|
||||
static const Availability AVAILABILITY_OFFLINE = Availability._(1, _omitEnumNames ? '' : 'AVAILABILITY_OFFLINE');
|
||||
@ -51,23 +34,6 @@ class Availability extends $pb.ProtobufEnum {
|
||||
const Availability._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class ChatType extends $pb.ProtobufEnum {
|
||||
static const ChatType CHAT_TYPE_UNSPECIFIED = ChatType._(0, _omitEnumNames ? '' : 'CHAT_TYPE_UNSPECIFIED');
|
||||
static const ChatType SINGLE_CONTACT = ChatType._(1, _omitEnumNames ? '' : 'SINGLE_CONTACT');
|
||||
static const ChatType GROUP = ChatType._(2, _omitEnumNames ? '' : 'GROUP');
|
||||
|
||||
static const $core.List<ChatType> values = <ChatType> [
|
||||
CHAT_TYPE_UNSPECIFIED,
|
||||
SINGLE_CONTACT,
|
||||
GROUP,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, ChatType> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static ChatType? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const ChatType._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class EncryptionKeyType extends $pb.ProtobufEnum {
|
||||
static const EncryptionKeyType ENCRYPTION_KEY_TYPE_UNSPECIFIED = EncryptionKeyType._(0, _omitEnumNames ? '' : 'ENCRYPTION_KEY_TYPE_UNSPECIFIED');
|
||||
static const EncryptionKeyType ENCRYPTION_KEY_TYPE_NONE = EncryptionKeyType._(1, _omitEnumNames ? '' : 'ENCRYPTION_KEY_TYPE_NONE');
|
||||
@ -87,5 +53,26 @@ class EncryptionKeyType extends $pb.ProtobufEnum {
|
||||
const EncryptionKeyType._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class Scope extends $pb.ProtobufEnum {
|
||||
static const Scope WATCHERS = Scope._(0, _omitEnumNames ? '' : 'WATCHERS');
|
||||
static const Scope MODERATED = Scope._(1, _omitEnumNames ? '' : 'MODERATED');
|
||||
static const Scope TALKERS = Scope._(2, _omitEnumNames ? '' : 'TALKERS');
|
||||
static const Scope MODERATORS = Scope._(3, _omitEnumNames ? '' : 'MODERATORS');
|
||||
static const Scope ADMINS = Scope._(4, _omitEnumNames ? '' : 'ADMINS');
|
||||
|
||||
static const $core.List<Scope> values = <Scope> [
|
||||
WATCHERS,
|
||||
MODERATED,
|
||||
TALKERS,
|
||||
MODERATORS,
|
||||
ADMINS,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, Scope> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static Scope? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const Scope._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
|
||||
const _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names');
|
||||
|
@ -13,21 +13,6 @@ import 'dart:convert' as $convert;
|
||||
import 'dart:core' as $core;
|
||||
import 'dart:typed_data' as $typed_data;
|
||||
|
||||
@$core.Deprecated('Use attachmentKindDescriptor instead')
|
||||
const AttachmentKind$json = {
|
||||
'1': 'AttachmentKind',
|
||||
'2': [
|
||||
{'1': 'ATTACHMENT_KIND_UNSPECIFIED', '2': 0},
|
||||
{'1': 'ATTACHMENT_KIND_FILE', '2': 1},
|
||||
{'1': 'ATTACHMENT_KIND_IMAGE', '2': 2},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `AttachmentKind`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List attachmentKindDescriptor = $convert.base64Decode(
|
||||
'Cg5BdHRhY2htZW50S2luZBIfChtBVFRBQ0hNRU5UX0tJTkRfVU5TUEVDSUZJRUQQABIYChRBVF'
|
||||
'RBQ0hNRU5UX0tJTkRfRklMRRABEhkKFUFUVEFDSE1FTlRfS0lORF9JTUFHRRAC');
|
||||
|
||||
@$core.Deprecated('Use availabilityDescriptor instead')
|
||||
const Availability$json = {
|
||||
'1': 'Availability',
|
||||
@ -46,21 +31,6 @@ final $typed_data.Uint8List availabilityDescriptor = $convert.base64Decode(
|
||||
'lMSVRZX09GRkxJTkUQARIVChFBVkFJTEFCSUxJVFlfRlJFRRACEhUKEUFWQUlMQUJJTElUWV9C'
|
||||
'VVNZEAMSFQoRQVZBSUxBQklMSVRZX0FXQVkQBA==');
|
||||
|
||||
@$core.Deprecated('Use chatTypeDescriptor instead')
|
||||
const ChatType$json = {
|
||||
'1': 'ChatType',
|
||||
'2': [
|
||||
{'1': 'CHAT_TYPE_UNSPECIFIED', '2': 0},
|
||||
{'1': 'SINGLE_CONTACT', '2': 1},
|
||||
{'1': 'GROUP', '2': 2},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ChatType`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List chatTypeDescriptor = $convert.base64Decode(
|
||||
'CghDaGF0VHlwZRIZChVDSEFUX1RZUEVfVU5TUEVDSUZJRUQQABISCg5TSU5HTEVfQ09OVEFDVB'
|
||||
'ABEgkKBUdST1VQEAI=');
|
||||
|
||||
@$core.Deprecated('Use encryptionKeyTypeDescriptor instead')
|
||||
const EncryptionKeyType$json = {
|
||||
'1': 'EncryptionKeyType',
|
||||
@ -78,43 +48,249 @@ final $typed_data.Uint8List encryptionKeyTypeDescriptor = $convert.base64Decode(
|
||||
'ASHAoYRU5DUllQVElPTl9LRVlfVFlQRV9OT05FEAESGwoXRU5DUllQVElPTl9LRVlfVFlQRV9Q'
|
||||
'SU4QAhIgChxFTkNSWVBUSU9OX0tFWV9UWVBFX1BBU1NXT1JEEAM=');
|
||||
|
||||
@$core.Deprecated('Use scopeDescriptor instead')
|
||||
const Scope$json = {
|
||||
'1': 'Scope',
|
||||
'2': [
|
||||
{'1': 'WATCHERS', '2': 0},
|
||||
{'1': 'MODERATED', '2': 1},
|
||||
{'1': 'TALKERS', '2': 2},
|
||||
{'1': 'MODERATORS', '2': 3},
|
||||
{'1': 'ADMINS', '2': 4},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `Scope`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List scopeDescriptor = $convert.base64Decode(
|
||||
'CgVTY29wZRIMCghXQVRDSEVSUxAAEg0KCU1PREVSQVRFRBABEgsKB1RBTEtFUlMQAhIOCgpNT0'
|
||||
'RFUkFUT1JTEAMSCgoGQURNSU5TEAQ=');
|
||||
|
||||
@$core.Deprecated('Use attachmentDescriptor instead')
|
||||
const Attachment$json = {
|
||||
'1': 'Attachment',
|
||||
'2': [
|
||||
{'1': 'kind', '3': 1, '4': 1, '5': 14, '6': '.veilidchat.AttachmentKind', '10': 'kind'},
|
||||
{'1': 'mime', '3': 2, '4': 1, '5': 9, '10': 'mime'},
|
||||
{'1': 'name', '3': 3, '4': 1, '5': 9, '10': 'name'},
|
||||
{'1': 'content', '3': 4, '4': 1, '5': 11, '6': '.dht.DataReference', '10': 'content'},
|
||||
{'1': 'signature', '3': 5, '4': 1, '5': 11, '6': '.veilid.Signature', '10': 'signature'},
|
||||
{'1': 'media', '3': 1, '4': 1, '5': 11, '6': '.veilidchat.AttachmentMedia', '9': 0, '10': 'media'},
|
||||
{'1': 'signature', '3': 2, '4': 1, '5': 11, '6': '.veilid.Signature', '10': 'signature'},
|
||||
],
|
||||
'8': [
|
||||
{'1': 'kind'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `Attachment`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List attachmentDescriptor = $convert.base64Decode(
|
||||
'CgpBdHRhY2htZW50Ei4KBGtpbmQYASABKA4yGi52ZWlsaWRjaGF0LkF0dGFjaG1lbnRLaW5kUg'
|
||||
'RraW5kEhIKBG1pbWUYAiABKAlSBG1pbWUSEgoEbmFtZRgDIAEoCVIEbmFtZRIsCgdjb250ZW50'
|
||||
'GAQgASgLMhIuZGh0LkRhdGFSZWZlcmVuY2VSB2NvbnRlbnQSLwoJc2lnbmF0dXJlGAUgASgLMh'
|
||||
'EudmVpbGlkLlNpZ25hdHVyZVIJc2lnbmF0dXJl');
|
||||
'CgpBdHRhY2htZW50EjMKBW1lZGlhGAEgASgLMhsudmVpbGlkY2hhdC5BdHRhY2htZW50TWVkaW'
|
||||
'FIAFIFbWVkaWESLwoJc2lnbmF0dXJlGAIgASgLMhEudmVpbGlkLlNpZ25hdHVyZVIJc2lnbmF0'
|
||||
'dXJlQgYKBGtpbmQ=');
|
||||
|
||||
@$core.Deprecated('Use attachmentMediaDescriptor instead')
|
||||
const AttachmentMedia$json = {
|
||||
'1': 'AttachmentMedia',
|
||||
'2': [
|
||||
{'1': 'mime', '3': 1, '4': 1, '5': 9, '10': 'mime'},
|
||||
{'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'},
|
||||
{'1': 'content', '3': 3, '4': 1, '5': 11, '6': '.dht.DataReference', '10': 'content'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `AttachmentMedia`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List attachmentMediaDescriptor = $convert.base64Decode(
|
||||
'Cg9BdHRhY2htZW50TWVkaWESEgoEbWltZRgBIAEoCVIEbWltZRISCgRuYW1lGAIgASgJUgRuYW'
|
||||
'1lEiwKB2NvbnRlbnQYAyABKAsyEi5kaHQuRGF0YVJlZmVyZW5jZVIHY29udGVudA==');
|
||||
|
||||
@$core.Deprecated('Use permissionsDescriptor instead')
|
||||
const Permissions$json = {
|
||||
'1': 'Permissions',
|
||||
'2': [
|
||||
{'1': 'can_add_members', '3': 1, '4': 1, '5': 14, '6': '.veilidchat.Scope', '10': 'canAddMembers'},
|
||||
{'1': 'can_edit_info', '3': 2, '4': 1, '5': 14, '6': '.veilidchat.Scope', '10': 'canEditInfo'},
|
||||
{'1': 'moderated', '3': 3, '4': 1, '5': 8, '10': 'moderated'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `Permissions`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List permissionsDescriptor = $convert.base64Decode(
|
||||
'CgtQZXJtaXNzaW9ucxI5Cg9jYW5fYWRkX21lbWJlcnMYASABKA4yES52ZWlsaWRjaGF0LlNjb3'
|
||||
'BlUg1jYW5BZGRNZW1iZXJzEjUKDWNhbl9lZGl0X2luZm8YAiABKA4yES52ZWlsaWRjaGF0LlNj'
|
||||
'b3BlUgtjYW5FZGl0SW5mbxIcCgltb2RlcmF0ZWQYAyABKAhSCW1vZGVyYXRlZA==');
|
||||
|
||||
@$core.Deprecated('Use membershipDescriptor instead')
|
||||
const Membership$json = {
|
||||
'1': 'Membership',
|
||||
'2': [
|
||||
{'1': 'watchers', '3': 1, '4': 3, '5': 11, '6': '.veilid.TypedKey', '10': 'watchers'},
|
||||
{'1': 'moderated', '3': 2, '4': 3, '5': 11, '6': '.veilid.TypedKey', '10': 'moderated'},
|
||||
{'1': 'talkers', '3': 3, '4': 3, '5': 11, '6': '.veilid.TypedKey', '10': 'talkers'},
|
||||
{'1': 'moderators', '3': 4, '4': 3, '5': 11, '6': '.veilid.TypedKey', '10': 'moderators'},
|
||||
{'1': 'admins', '3': 5, '4': 3, '5': 11, '6': '.veilid.TypedKey', '10': 'admins'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `Membership`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List membershipDescriptor = $convert.base64Decode(
|
||||
'CgpNZW1iZXJzaGlwEiwKCHdhdGNoZXJzGAEgAygLMhAudmVpbGlkLlR5cGVkS2V5Ugh3YXRjaG'
|
||||
'VycxIuCgltb2RlcmF0ZWQYAiADKAsyEC52ZWlsaWQuVHlwZWRLZXlSCW1vZGVyYXRlZBIqCgd0'
|
||||
'YWxrZXJzGAMgAygLMhAudmVpbGlkLlR5cGVkS2V5Ugd0YWxrZXJzEjAKCm1vZGVyYXRvcnMYBC'
|
||||
'ADKAsyEC52ZWlsaWQuVHlwZWRLZXlSCm1vZGVyYXRvcnMSKAoGYWRtaW5zGAUgAygLMhAudmVp'
|
||||
'bGlkLlR5cGVkS2V5UgZhZG1pbnM=');
|
||||
|
||||
@$core.Deprecated('Use chatSettingsDescriptor instead')
|
||||
const ChatSettings$json = {
|
||||
'1': 'ChatSettings',
|
||||
'2': [
|
||||
{'1': 'title', '3': 1, '4': 1, '5': 9, '10': 'title'},
|
||||
{'1': 'description', '3': 2, '4': 1, '5': 9, '10': 'description'},
|
||||
{'1': 'icon', '3': 3, '4': 1, '5': 11, '6': '.dht.DataReference', '9': 0, '10': 'icon', '17': true},
|
||||
{'1': 'default_expiration', '3': 4, '4': 1, '5': 4, '10': 'defaultExpiration'},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_icon'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ChatSettings`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List chatSettingsDescriptor = $convert.base64Decode(
|
||||
'CgxDaGF0U2V0dGluZ3MSFAoFdGl0bGUYASABKAlSBXRpdGxlEiAKC2Rlc2NyaXB0aW9uGAIgAS'
|
||||
'gJUgtkZXNjcmlwdGlvbhIrCgRpY29uGAMgASgLMhIuZGh0LkRhdGFSZWZlcmVuY2VIAFIEaWNv'
|
||||
'bogBARItChJkZWZhdWx0X2V4cGlyYXRpb24YBCABKARSEWRlZmF1bHRFeHBpcmF0aW9uQgcKBV'
|
||||
'9pY29u');
|
||||
|
||||
@$core.Deprecated('Use messageDescriptor instead')
|
||||
const Message$json = {
|
||||
'1': 'Message',
|
||||
'2': [
|
||||
{'1': 'author', '3': 1, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'author'},
|
||||
{'1': 'timestamp', '3': 2, '4': 1, '5': 4, '10': 'timestamp'},
|
||||
{'1': 'text', '3': 3, '4': 1, '5': 9, '10': 'text'},
|
||||
{'1': 'signature', '3': 4, '4': 1, '5': 11, '6': '.veilid.Signature', '10': 'signature'},
|
||||
{'1': 'attachments', '3': 5, '4': 3, '5': 11, '6': '.veilidchat.Attachment', '10': 'attachments'},
|
||||
{'1': 'id', '3': 1, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'id'},
|
||||
{'1': 'author', '3': 2, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'author'},
|
||||
{'1': 'timestamp', '3': 3, '4': 1, '5': 4, '10': 'timestamp'},
|
||||
{'1': 'text', '3': 4, '4': 1, '5': 11, '6': '.veilidchat.Message.Text', '9': 0, '10': 'text'},
|
||||
{'1': 'secret', '3': 5, '4': 1, '5': 11, '6': '.veilidchat.Message.Secret', '9': 0, '10': 'secret'},
|
||||
{'1': 'delete', '3': 6, '4': 1, '5': 11, '6': '.veilidchat.Message.ControlDelete', '9': 0, '10': 'delete'},
|
||||
{'1': 'clear', '3': 7, '4': 1, '5': 11, '6': '.veilidchat.Message.ControlClear', '9': 0, '10': 'clear'},
|
||||
{'1': 'settings', '3': 8, '4': 1, '5': 11, '6': '.veilidchat.Message.ControlSettings', '9': 0, '10': 'settings'},
|
||||
{'1': 'permissions', '3': 9, '4': 1, '5': 11, '6': '.veilidchat.Message.ControlPermissions', '9': 0, '10': 'permissions'},
|
||||
{'1': 'membership', '3': 10, '4': 1, '5': 11, '6': '.veilidchat.Message.ControlMembership', '9': 0, '10': 'membership'},
|
||||
{'1': 'moderation', '3': 11, '4': 1, '5': 11, '6': '.veilidchat.Message.ControlModeration', '9': 0, '10': 'moderation'},
|
||||
{'1': 'signature', '3': 12, '4': 1, '5': 11, '6': '.veilid.Signature', '10': 'signature'},
|
||||
],
|
||||
'3': [Message_Text$json, Message_Secret$json, Message_ControlDelete$json, Message_ControlClear$json, Message_ControlSettings$json, Message_ControlPermissions$json, Message_ControlMembership$json, Message_ControlModeration$json],
|
||||
'8': [
|
||||
{'1': 'kind'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use messageDescriptor instead')
|
||||
const Message_Text$json = {
|
||||
'1': 'Text',
|
||||
'2': [
|
||||
{'1': 'text', '3': 1, '4': 1, '5': 9, '10': 'text'},
|
||||
{'1': 'topic', '3': 2, '4': 1, '5': 9, '10': 'topic'},
|
||||
{'1': 'reply_id', '3': 3, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'replyId'},
|
||||
{'1': 'expiration', '3': 4, '4': 1, '5': 4, '10': 'expiration'},
|
||||
{'1': 'view_limit', '3': 5, '4': 1, '5': 4, '10': 'viewLimit'},
|
||||
{'1': 'attachments', '3': 6, '4': 3, '5': 11, '6': '.veilidchat.Attachment', '10': 'attachments'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use messageDescriptor instead')
|
||||
const Message_Secret$json = {
|
||||
'1': 'Secret',
|
||||
'2': [
|
||||
{'1': 'ciphertext', '3': 1, '4': 1, '5': 12, '10': 'ciphertext'},
|
||||
{'1': 'expiration', '3': 2, '4': 1, '5': 4, '10': 'expiration'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use messageDescriptor instead')
|
||||
const Message_ControlDelete$json = {
|
||||
'1': 'ControlDelete',
|
||||
'2': [
|
||||
{'1': 'ids', '3': 1, '4': 3, '5': 11, '6': '.veilid.TypedKey', '10': 'ids'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use messageDescriptor instead')
|
||||
const Message_ControlClear$json = {
|
||||
'1': 'ControlClear',
|
||||
'2': [
|
||||
{'1': 'timestamp', '3': 1, '4': 1, '5': 4, '10': 'timestamp'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use messageDescriptor instead')
|
||||
const Message_ControlSettings$json = {
|
||||
'1': 'ControlSettings',
|
||||
'2': [
|
||||
{'1': 'settings', '3': 1, '4': 1, '5': 11, '6': '.veilidchat.ChatSettings', '10': 'settings'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use messageDescriptor instead')
|
||||
const Message_ControlPermissions$json = {
|
||||
'1': 'ControlPermissions',
|
||||
'2': [
|
||||
{'1': 'permissions', '3': 1, '4': 1, '5': 11, '6': '.veilidchat.Permissions', '10': 'permissions'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use messageDescriptor instead')
|
||||
const Message_ControlMembership$json = {
|
||||
'1': 'ControlMembership',
|
||||
'2': [
|
||||
{'1': 'membership', '3': 1, '4': 1, '5': 11, '6': '.veilidchat.Membership', '10': 'membership'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use messageDescriptor instead')
|
||||
const Message_ControlModeration$json = {
|
||||
'1': 'ControlModeration',
|
||||
'2': [
|
||||
{'1': 'accepted_ids', '3': 1, '4': 3, '5': 11, '6': '.veilid.TypedKey', '10': 'acceptedIds'},
|
||||
{'1': 'rejected_ids', '3': 2, '4': 3, '5': 11, '6': '.veilid.TypedKey', '10': 'rejectedIds'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `Message`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List messageDescriptor = $convert.base64Decode(
|
||||
'CgdNZXNzYWdlEigKBmF1dGhvchgBIAEoCzIQLnZlaWxpZC5UeXBlZEtleVIGYXV0aG9yEhwKCX'
|
||||
'RpbWVzdGFtcBgCIAEoBFIJdGltZXN0YW1wEhIKBHRleHQYAyABKAlSBHRleHQSLwoJc2lnbmF0'
|
||||
'dXJlGAQgASgLMhEudmVpbGlkLlNpZ25hdHVyZVIJc2lnbmF0dXJlEjgKC2F0dGFjaG1lbnRzGA'
|
||||
'UgAygLMhYudmVpbGlkY2hhdC5BdHRhY2htZW50UgthdHRhY2htZW50cw==');
|
||||
'CgdNZXNzYWdlEiAKAmlkGAEgASgLMhAudmVpbGlkLlR5cGVkS2V5UgJpZBIoCgZhdXRob3IYAi'
|
||||
'ABKAsyEC52ZWlsaWQuVHlwZWRLZXlSBmF1dGhvchIcCgl0aW1lc3RhbXAYAyABKARSCXRpbWVz'
|
||||
'dGFtcBIuCgR0ZXh0GAQgASgLMhgudmVpbGlkY2hhdC5NZXNzYWdlLlRleHRIAFIEdGV4dBI0Cg'
|
||||
'ZzZWNyZXQYBSABKAsyGi52ZWlsaWRjaGF0Lk1lc3NhZ2UuU2VjcmV0SABSBnNlY3JldBI7CgZk'
|
||||
'ZWxldGUYBiABKAsyIS52ZWlsaWRjaGF0Lk1lc3NhZ2UuQ29udHJvbERlbGV0ZUgAUgZkZWxldG'
|
||||
'USOAoFY2xlYXIYByABKAsyIC52ZWlsaWRjaGF0Lk1lc3NhZ2UuQ29udHJvbENsZWFySABSBWNs'
|
||||
'ZWFyEkEKCHNldHRpbmdzGAggASgLMiMudmVpbGlkY2hhdC5NZXNzYWdlLkNvbnRyb2xTZXR0aW'
|
||||
'5nc0gAUghzZXR0aW5ncxJKCgtwZXJtaXNzaW9ucxgJIAEoCzImLnZlaWxpZGNoYXQuTWVzc2Fn'
|
||||
'ZS5Db250cm9sUGVybWlzc2lvbnNIAFILcGVybWlzc2lvbnMSRwoKbWVtYmVyc2hpcBgKIAEoCz'
|
||||
'IlLnZlaWxpZGNoYXQuTWVzc2FnZS5Db250cm9sTWVtYmVyc2hpcEgAUgptZW1iZXJzaGlwEkcK'
|
||||
'Cm1vZGVyYXRpb24YCyABKAsyJS52ZWlsaWRjaGF0Lk1lc3NhZ2UuQ29udHJvbE1vZGVyYXRpb2'
|
||||
'5IAFIKbW9kZXJhdGlvbhIvCglzaWduYXR1cmUYDCABKAsyES52ZWlsaWQuU2lnbmF0dXJlUglz'
|
||||
'aWduYXR1cmUa1gEKBFRleHQSEgoEdGV4dBgBIAEoCVIEdGV4dBIUCgV0b3BpYxgCIAEoCVIFdG'
|
||||
'9waWMSKwoIcmVwbHlfaWQYAyABKAsyEC52ZWlsaWQuVHlwZWRLZXlSB3JlcGx5SWQSHgoKZXhw'
|
||||
'aXJhdGlvbhgEIAEoBFIKZXhwaXJhdGlvbhIdCgp2aWV3X2xpbWl0GAUgASgEUgl2aWV3TGltaX'
|
||||
'QSOAoLYXR0YWNobWVudHMYBiADKAsyFi52ZWlsaWRjaGF0LkF0dGFjaG1lbnRSC2F0dGFjaG1l'
|
||||
'bnRzGkgKBlNlY3JldBIeCgpjaXBoZXJ0ZXh0GAEgASgMUgpjaXBoZXJ0ZXh0Eh4KCmV4cGlyYX'
|
||||
'Rpb24YAiABKARSCmV4cGlyYXRpb24aMwoNQ29udHJvbERlbGV0ZRIiCgNpZHMYASADKAsyEC52'
|
||||
'ZWlsaWQuVHlwZWRLZXlSA2lkcxosCgxDb250cm9sQ2xlYXISHAoJdGltZXN0YW1wGAEgASgEUg'
|
||||
'l0aW1lc3RhbXAaRwoPQ29udHJvbFNldHRpbmdzEjQKCHNldHRpbmdzGAEgASgLMhgudmVpbGlk'
|
||||
'Y2hhdC5DaGF0U2V0dGluZ3NSCHNldHRpbmdzGk8KEkNvbnRyb2xQZXJtaXNzaW9ucxI5CgtwZX'
|
||||
'JtaXNzaW9ucxgBIAEoCzIXLnZlaWxpZGNoYXQuUGVybWlzc2lvbnNSC3Blcm1pc3Npb25zGksK'
|
||||
'EUNvbnRyb2xNZW1iZXJzaGlwEjYKCm1lbWJlcnNoaXAYASABKAsyFi52ZWlsaWRjaGF0Lk1lbW'
|
||||
'JlcnNoaXBSCm1lbWJlcnNoaXAafQoRQ29udHJvbE1vZGVyYXRpb24SMwoMYWNjZXB0ZWRfaWRz'
|
||||
'GAEgAygLMhAudmVpbGlkLlR5cGVkS2V5UgthY2NlcHRlZElkcxIzCgxyZWplY3RlZF9pZHMYAi'
|
||||
'ADKAsyEC52ZWlsaWQuVHlwZWRLZXlSC3JlamVjdGVkSWRzQgYKBGtpbmQ=');
|
||||
|
||||
@$core.Deprecated('Use reconciledMessageDescriptor instead')
|
||||
const ReconciledMessage$json = {
|
||||
'1': 'ReconciledMessage',
|
||||
'2': [
|
||||
{'1': 'content', '3': 1, '4': 1, '5': 11, '6': '.veilidchat.Message', '10': 'content'},
|
||||
{'1': 'reconciled_time', '3': 2, '4': 1, '5': 4, '10': 'reconciledTime'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ReconciledMessage`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List reconciledMessageDescriptor = $convert.base64Decode(
|
||||
'ChFSZWNvbmNpbGVkTWVzc2FnZRItCgdjb250ZW50GAEgASgLMhMudmVpbGlkY2hhdC5NZXNzYW'
|
||||
'dlUgdjb250ZW50EicKD3JlY29uY2lsZWRfdGltZRgCIAEoBFIOcmVjb25jaWxlZFRpbWU=');
|
||||
|
||||
@$core.Deprecated('Use conversationDescriptor instead')
|
||||
const Conversation$json = {
|
||||
@ -132,6 +308,91 @@ final $typed_data.Uint8List conversationDescriptor = $convert.base64Decode(
|
||||
'JvZmlsZRIwChRpZGVudGl0eV9tYXN0ZXJfanNvbhgCIAEoCVISaWRlbnRpdHlNYXN0ZXJKc29u'
|
||||
'EiwKCG1lc3NhZ2VzGAMgASgLMhAudmVpbGlkLlR5cGVkS2V5UghtZXNzYWdlcw==');
|
||||
|
||||
@$core.Deprecated('Use chatDescriptor instead')
|
||||
const Chat$json = {
|
||||
'1': 'Chat',
|
||||
'2': [
|
||||
{'1': 'settings', '3': 1, '4': 1, '5': 11, '6': '.veilidchat.ChatSettings', '10': 'settings'},
|
||||
{'1': 'local_conversation_record_key', '3': 2, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'localConversationRecordKey'},
|
||||
{'1': 'remote_conversation_record_key', '3': 3, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'remoteConversationRecordKey'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `Chat`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List chatDescriptor = $convert.base64Decode(
|
||||
'CgRDaGF0EjQKCHNldHRpbmdzGAEgASgLMhgudmVpbGlkY2hhdC5DaGF0U2V0dGluZ3NSCHNldH'
|
||||
'RpbmdzElMKHWxvY2FsX2NvbnZlcnNhdGlvbl9yZWNvcmRfa2V5GAIgASgLMhAudmVpbGlkLlR5'
|
||||
'cGVkS2V5Uhpsb2NhbENvbnZlcnNhdGlvblJlY29yZEtleRJVCh5yZW1vdGVfY29udmVyc2F0aW'
|
||||
'9uX3JlY29yZF9rZXkYAyABKAsyEC52ZWlsaWQuVHlwZWRLZXlSG3JlbW90ZUNvbnZlcnNhdGlv'
|
||||
'blJlY29yZEtleQ==');
|
||||
|
||||
@$core.Deprecated('Use groupChatDescriptor instead')
|
||||
const GroupChat$json = {
|
||||
'1': 'GroupChat',
|
||||
'2': [
|
||||
{'1': 'settings', '3': 1, '4': 1, '5': 11, '6': '.veilidchat.ChatSettings', '10': 'settings'},
|
||||
{'1': 'local_conversation_record_key', '3': 2, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'localConversationRecordKey'},
|
||||
{'1': 'remote_conversation_record_keys', '3': 3, '4': 3, '5': 11, '6': '.veilid.TypedKey', '10': 'remoteConversationRecordKeys'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `GroupChat`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List groupChatDescriptor = $convert.base64Decode(
|
||||
'CglHcm91cENoYXQSNAoIc2V0dGluZ3MYASABKAsyGC52ZWlsaWRjaGF0LkNoYXRTZXR0aW5nc1'
|
||||
'IIc2V0dGluZ3MSUwodbG9jYWxfY29udmVyc2F0aW9uX3JlY29yZF9rZXkYAiABKAsyEC52ZWls'
|
||||
'aWQuVHlwZWRLZXlSGmxvY2FsQ29udmVyc2F0aW9uUmVjb3JkS2V5ElcKH3JlbW90ZV9jb252ZX'
|
||||
'JzYXRpb25fcmVjb3JkX2tleXMYAyADKAsyEC52ZWlsaWQuVHlwZWRLZXlSHHJlbW90ZUNvbnZl'
|
||||
'cnNhdGlvblJlY29yZEtleXM=');
|
||||
|
||||
@$core.Deprecated('Use profileDescriptor instead')
|
||||
const Profile$json = {
|
||||
'1': 'Profile',
|
||||
'2': [
|
||||
{'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'},
|
||||
{'1': 'pronouns', '3': 2, '4': 1, '5': 9, '10': 'pronouns'},
|
||||
{'1': 'about', '3': 3, '4': 1, '5': 9, '10': 'about'},
|
||||
{'1': 'status', '3': 4, '4': 1, '5': 9, '10': 'status'},
|
||||
{'1': 'availability', '3': 5, '4': 1, '5': 14, '6': '.veilidchat.Availability', '10': 'availability'},
|
||||
{'1': 'avatar', '3': 6, '4': 1, '5': 11, '6': '.veilid.TypedKey', '9': 0, '10': 'avatar', '17': true},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_avatar'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `Profile`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List profileDescriptor = $convert.base64Decode(
|
||||
'CgdQcm9maWxlEhIKBG5hbWUYASABKAlSBG5hbWUSGgoIcHJvbm91bnMYAiABKAlSCHByb25vdW'
|
||||
'5zEhQKBWFib3V0GAMgASgJUgVhYm91dBIWCgZzdGF0dXMYBCABKAlSBnN0YXR1cxI8CgxhdmFp'
|
||||
'bGFiaWxpdHkYBSABKA4yGC52ZWlsaWRjaGF0LkF2YWlsYWJpbGl0eVIMYXZhaWxhYmlsaXR5Ei'
|
||||
'0KBmF2YXRhchgGIAEoCzIQLnZlaWxpZC5UeXBlZEtleUgAUgZhdmF0YXKIAQFCCQoHX2F2YXRh'
|
||||
'cg==');
|
||||
|
||||
@$core.Deprecated('Use accountDescriptor instead')
|
||||
const Account$json = {
|
||||
'1': 'Account',
|
||||
'2': [
|
||||
{'1': 'profile', '3': 1, '4': 1, '5': 11, '6': '.veilidchat.Profile', '10': 'profile'},
|
||||
{'1': 'invisible', '3': 2, '4': 1, '5': 8, '10': 'invisible'},
|
||||
{'1': 'auto_away_timeout_sec', '3': 3, '4': 1, '5': 13, '10': 'autoAwayTimeoutSec'},
|
||||
{'1': 'contact_list', '3': 4, '4': 1, '5': 11, '6': '.dht.OwnedDHTRecordPointer', '10': 'contactList'},
|
||||
{'1': 'contact_invitation_records', '3': 5, '4': 1, '5': 11, '6': '.dht.OwnedDHTRecordPointer', '10': 'contactInvitationRecords'},
|
||||
{'1': 'chat_list', '3': 6, '4': 1, '5': 11, '6': '.dht.OwnedDHTRecordPointer', '10': 'chatList'},
|
||||
{'1': 'group_chat_list', '3': 7, '4': 1, '5': 11, '6': '.dht.OwnedDHTRecordPointer', '10': 'groupChatList'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `Account`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List accountDescriptor = $convert.base64Decode(
|
||||
'CgdBY2NvdW50Ei0KB3Byb2ZpbGUYASABKAsyEy52ZWlsaWRjaGF0LlByb2ZpbGVSB3Byb2ZpbG'
|
||||
'USHAoJaW52aXNpYmxlGAIgASgIUglpbnZpc2libGUSMQoVYXV0b19hd2F5X3RpbWVvdXRfc2Vj'
|
||||
'GAMgASgNUhJhdXRvQXdheVRpbWVvdXRTZWMSPQoMY29udGFjdF9saXN0GAQgASgLMhouZGh0Lk'
|
||||
'93bmVkREhUUmVjb3JkUG9pbnRlclILY29udGFjdExpc3QSWAoaY29udGFjdF9pbnZpdGF0aW9u'
|
||||
'X3JlY29yZHMYBSABKAsyGi5kaHQuT3duZWRESFRSZWNvcmRQb2ludGVyUhhjb250YWN0SW52aX'
|
||||
'RhdGlvblJlY29yZHMSNwoJY2hhdF9saXN0GAYgASgLMhouZGh0Lk93bmVkREhUUmVjb3JkUG9p'
|
||||
'bnRlclIIY2hhdExpc3QSQgoPZ3JvdXBfY2hhdF9saXN0GAcgASgLMhouZGh0Lk93bmVkREhUUm'
|
||||
'Vjb3JkUG9pbnRlclINZ3JvdXBDaGF0TGlzdA==');
|
||||
|
||||
@$core.Deprecated('Use contactDescriptor instead')
|
||||
const Contact$json = {
|
||||
'1': 'Contact',
|
||||
@ -158,68 +419,6 @@ final $typed_data.Uint8List contactDescriptor = $convert.base64Decode(
|
||||
'lSGmxvY2FsQ29udmVyc2F0aW9uUmVjb3JkS2V5EisKEXNob3dfYXZhaWxhYmlsaXR5GAcgASgI'
|
||||
'UhBzaG93QXZhaWxhYmlsaXR5');
|
||||
|
||||
@$core.Deprecated('Use profileDescriptor instead')
|
||||
const Profile$json = {
|
||||
'1': 'Profile',
|
||||
'2': [
|
||||
{'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'},
|
||||
{'1': 'pronouns', '3': 2, '4': 1, '5': 9, '10': 'pronouns'},
|
||||
{'1': 'status', '3': 3, '4': 1, '5': 9, '10': 'status'},
|
||||
{'1': 'availability', '3': 4, '4': 1, '5': 14, '6': '.veilidchat.Availability', '10': 'availability'},
|
||||
{'1': 'avatar', '3': 5, '4': 1, '5': 11, '6': '.veilid.TypedKey', '9': 0, '10': 'avatar', '17': true},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_avatar'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `Profile`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List profileDescriptor = $convert.base64Decode(
|
||||
'CgdQcm9maWxlEhIKBG5hbWUYASABKAlSBG5hbWUSGgoIcHJvbm91bnMYAiABKAlSCHByb25vdW'
|
||||
'5zEhYKBnN0YXR1cxgDIAEoCVIGc3RhdHVzEjwKDGF2YWlsYWJpbGl0eRgEIAEoDjIYLnZlaWxp'
|
||||
'ZGNoYXQuQXZhaWxhYmlsaXR5UgxhdmFpbGFiaWxpdHkSLQoGYXZhdGFyGAUgASgLMhAudmVpbG'
|
||||
'lkLlR5cGVkS2V5SABSBmF2YXRhcogBAUIJCgdfYXZhdGFy');
|
||||
|
||||
@$core.Deprecated('Use chatDescriptor instead')
|
||||
const Chat$json = {
|
||||
'1': 'Chat',
|
||||
'2': [
|
||||
{'1': 'type', '3': 1, '4': 1, '5': 14, '6': '.veilidchat.ChatType', '10': 'type'},
|
||||
{'1': 'remote_conversation_record_key', '3': 2, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'remoteConversationRecordKey'},
|
||||
{'1': 'reconciled_chat_record', '3': 3, '4': 1, '5': 11, '6': '.dht.OwnedDHTRecordPointer', '10': 'reconciledChatRecord'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `Chat`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List chatDescriptor = $convert.base64Decode(
|
||||
'CgRDaGF0EigKBHR5cGUYASABKA4yFC52ZWlsaWRjaGF0LkNoYXRUeXBlUgR0eXBlElUKHnJlbW'
|
||||
'90ZV9jb252ZXJzYXRpb25fcmVjb3JkX2tleRgCIAEoCzIQLnZlaWxpZC5UeXBlZEtleVIbcmVt'
|
||||
'b3RlQ29udmVyc2F0aW9uUmVjb3JkS2V5ElAKFnJlY29uY2lsZWRfY2hhdF9yZWNvcmQYAyABKA'
|
||||
'syGi5kaHQuT3duZWRESFRSZWNvcmRQb2ludGVyUhRyZWNvbmNpbGVkQ2hhdFJlY29yZA==');
|
||||
|
||||
@$core.Deprecated('Use accountDescriptor instead')
|
||||
const Account$json = {
|
||||
'1': 'Account',
|
||||
'2': [
|
||||
{'1': 'profile', '3': 1, '4': 1, '5': 11, '6': '.veilidchat.Profile', '10': 'profile'},
|
||||
{'1': 'invisible', '3': 2, '4': 1, '5': 8, '10': 'invisible'},
|
||||
{'1': 'auto_away_timeout_sec', '3': 3, '4': 1, '5': 13, '10': 'autoAwayTimeoutSec'},
|
||||
{'1': 'contact_list', '3': 4, '4': 1, '5': 11, '6': '.dht.OwnedDHTRecordPointer', '10': 'contactList'},
|
||||
{'1': 'contact_invitation_records', '3': 5, '4': 1, '5': 11, '6': '.dht.OwnedDHTRecordPointer', '10': 'contactInvitationRecords'},
|
||||
{'1': 'chat_list', '3': 6, '4': 1, '5': 11, '6': '.dht.OwnedDHTRecordPointer', '10': 'chatList'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `Account`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List accountDescriptor = $convert.base64Decode(
|
||||
'CgdBY2NvdW50Ei0KB3Byb2ZpbGUYASABKAsyEy52ZWlsaWRjaGF0LlByb2ZpbGVSB3Byb2ZpbG'
|
||||
'USHAoJaW52aXNpYmxlGAIgASgIUglpbnZpc2libGUSMQoVYXV0b19hd2F5X3RpbWVvdXRfc2Vj'
|
||||
'GAMgASgNUhJhdXRvQXdheVRpbWVvdXRTZWMSPQoMY29udGFjdF9saXN0GAQgASgLMhouZGh0Lk'
|
||||
'93bmVkREhUUmVjb3JkUG9pbnRlclILY29udGFjdExpc3QSWAoaY29udGFjdF9pbnZpdGF0aW9u'
|
||||
'X3JlY29yZHMYBSABKAsyGi5kaHQuT3duZWRESFRSZWNvcmRQb2ludGVyUhhjb250YWN0SW52aX'
|
||||
'RhdGlvblJlY29yZHMSNwoJY2hhdF9saXN0GAYgASgLMhouZGh0Lk93bmVkREhUUmVjb3JkUG9p'
|
||||
'bnRlclIIY2hhdExpc3Q=');
|
||||
|
||||
@$core.Deprecated('Use contactInvitationDescriptor instead')
|
||||
const ContactInvitation$json = {
|
||||
'1': 'ContactInvitation',
|
||||
|
@ -1,51 +1,230 @@
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// VeilidChat Protocol Buffer Definitions
|
||||
//
|
||||
// * Timestamps are in microseconds (us) since epoch
|
||||
// * Durations are in microseconds (us)
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
syntax = "proto3";
|
||||
package veilidchat;
|
||||
|
||||
import "veilid.proto";
|
||||
import "dht.proto";
|
||||
|
||||
// AttachmentKind
|
||||
// Enumeration of well-known attachment types
|
||||
enum AttachmentKind {
|
||||
ATTACHMENT_KIND_UNSPECIFIED = 0;
|
||||
ATTACHMENT_KIND_FILE = 1;
|
||||
ATTACHMENT_KIND_IMAGE = 2;
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// Enumerations
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Contact availability
|
||||
enum Availability {
|
||||
AVAILABILITY_UNSPECIFIED = 0;
|
||||
AVAILABILITY_OFFLINE = 1;
|
||||
AVAILABILITY_FREE = 2;
|
||||
AVAILABILITY_BUSY = 3;
|
||||
AVAILABILITY_AWAY = 4;
|
||||
}
|
||||
|
||||
// Encryption used on secret keys
|
||||
enum EncryptionKeyType {
|
||||
ENCRYPTION_KEY_TYPE_UNSPECIFIED = 0;
|
||||
ENCRYPTION_KEY_TYPE_NONE = 1;
|
||||
ENCRYPTION_KEY_TYPE_PIN = 2;
|
||||
ENCRYPTION_KEY_TYPE_PASSWORD = 3;
|
||||
}
|
||||
|
||||
// Scope of a chat
|
||||
enum Scope {
|
||||
// Can read chats but not send messages
|
||||
WATCHERS = 0;
|
||||
// Can send messages subject to moderation
|
||||
// If moderation is disabled, this is equivalent to WATCHERS
|
||||
MODERATED = 1;
|
||||
// Can send messages without moderation
|
||||
TALKERS = 2;
|
||||
// Can moderate messages sent my members if moderation is enabled
|
||||
MODERATORS = 3;
|
||||
// Can perform all actions
|
||||
ADMINS = 4;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// Attachments
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// A single attachment
|
||||
message Attachment {
|
||||
// Type of the data
|
||||
AttachmentKind kind = 1;
|
||||
// MIME type of the data
|
||||
string mime = 2;
|
||||
// Title or filename
|
||||
string name = 3;
|
||||
// Pointer to the data content
|
||||
dht.DataReference content = 4;
|
||||
oneof kind {
|
||||
AttachmentMedia media = 1;
|
||||
}
|
||||
// Author signature over all attachment fields and content fields and bytes
|
||||
veilid.Signature signature = 5;
|
||||
veilid.Signature signature = 2;
|
||||
}
|
||||
|
||||
// A file, audio, image, or video attachment
|
||||
message AttachmentMedia {
|
||||
// MIME type of the data
|
||||
string mime = 1;
|
||||
// Title or filename
|
||||
string name = 2;
|
||||
// Pointer to the data content
|
||||
dht.DataReference content = 3;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// Chat room controls
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Permissions of a chat
|
||||
message Permissions {
|
||||
// Parties in this scope or higher can add members to their own group or lower
|
||||
Scope can_add_members = 1;
|
||||
// Parties in this scope or higher can change the 'info' of a group
|
||||
Scope can_edit_info = 2;
|
||||
// If moderation is enabled or not.
|
||||
bool moderated = 3;
|
||||
}
|
||||
|
||||
// The membership of a chat
|
||||
message Membership {
|
||||
// Conversation keys for parties in the 'watchers' group
|
||||
repeated veilid.TypedKey watchers = 1;
|
||||
// Conversation keys for parties in the 'moderated' group
|
||||
repeated veilid.TypedKey moderated = 2;
|
||||
// Conversation keys for parties in the 'talkers' group
|
||||
repeated veilid.TypedKey talkers = 3;
|
||||
// Conversation keys for parties in the 'moderators' group
|
||||
repeated veilid.TypedKey moderators = 4;
|
||||
// Conversation keys for parties in the 'admins' group
|
||||
repeated veilid.TypedKey admins = 5;
|
||||
}
|
||||
|
||||
// The chat settings
|
||||
message ChatSettings {
|
||||
// Title for the chat
|
||||
string title = 1;
|
||||
// Description for the chat
|
||||
string description = 2;
|
||||
// Icon for the chat
|
||||
optional dht.DataReference icon = 3;
|
||||
// Default message expiration duration (in us)
|
||||
uint64 default_expiration = 4;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// Messages
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// A single message as part of a series of messages
|
||||
message Message {
|
||||
// Author of the message
|
||||
veilid.TypedKey author = 1;
|
||||
// Time the message was sent (us since epoch)
|
||||
uint64 timestamp = 2;
|
||||
// Text of the message
|
||||
string text = 3;
|
||||
|
||||
// A text message
|
||||
message Text {
|
||||
// Text of the message
|
||||
string text = 1;
|
||||
// Topic of the message / Content warning
|
||||
string topic = 2;
|
||||
// Message id replied to
|
||||
veilid.TypedKey reply_id = 3;
|
||||
// Message expiration timestamp
|
||||
uint64 expiration = 4;
|
||||
// Message view limit before deletion
|
||||
uint64 view_limit = 5;
|
||||
// Attachments on the message
|
||||
repeated Attachment attachments = 6;
|
||||
}
|
||||
|
||||
// A secret message
|
||||
message Secret {
|
||||
// Text message protobuf encrypted by a key
|
||||
bytes ciphertext = 1;
|
||||
// Secret expiration timestamp
|
||||
// This is the time after which an un-revealed secret will get deleted
|
||||
uint64 expiration = 2;
|
||||
}
|
||||
|
||||
// A 'delete' control message
|
||||
// Deletes a set of messages by their ids
|
||||
message ControlDelete {
|
||||
repeated veilid.TypedKey ids = 1;
|
||||
}
|
||||
// A 'clear' control message
|
||||
// Deletes a set of messages from before some timestamp
|
||||
message ControlClear {
|
||||
// The latest timestamp to delete messages before
|
||||
// If this is zero then all messages are cleared
|
||||
uint64 timestamp = 1;
|
||||
}
|
||||
// A 'change settings' control message
|
||||
message ControlSettings {
|
||||
ChatSettings settings = 1;
|
||||
}
|
||||
|
||||
// A 'change permissions' control message
|
||||
// Changes the permissions of a chat
|
||||
message ControlPermissions {
|
||||
Permissions permissions = 1;
|
||||
}
|
||||
|
||||
// A 'change membership' control message
|
||||
// Changes the
|
||||
message ControlMembership {
|
||||
Membership membership = 1;
|
||||
}
|
||||
|
||||
// A 'moderation' control message
|
||||
// Accepts or rejects a set of messages
|
||||
message ControlModeration {
|
||||
repeated veilid.TypedKey accepted_ids = 1;
|
||||
repeated veilid.TypedKey rejected_ids = 2;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Hash of previous message from the same author,
|
||||
// including its previous hash.
|
||||
// Also serves as a unique key for the message.
|
||||
veilid.TypedKey id = 1;
|
||||
// Author of the message (identity public key)
|
||||
veilid.TypedKey author = 2;
|
||||
// Time the message was sent according to sender
|
||||
uint64 timestamp = 3;
|
||||
|
||||
// Message kind
|
||||
oneof kind {
|
||||
Text text = 4;
|
||||
Secret secret = 5;
|
||||
ControlDelete delete = 6;
|
||||
ControlClear clear = 7;
|
||||
ControlSettings settings = 8;
|
||||
ControlPermissions permissions = 9;
|
||||
ControlMembership membership = 10;
|
||||
ControlModeration moderation = 11;
|
||||
}
|
||||
|
||||
// Author signature over all of the fields and attachment signatures
|
||||
veilid.Signature signature = 4;
|
||||
// Attachments on the message
|
||||
repeated Attachment attachments = 5;
|
||||
veilid.Signature signature = 12;
|
||||
}
|
||||
|
||||
// Locally stored messages for chats
|
||||
message ReconciledMessage {
|
||||
// The message as sent
|
||||
Message content = 1;
|
||||
// The timestamp the message was reconciled
|
||||
uint64 reconciled_time = 2;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// Chats
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// The means of direct communications that is synchronized between
|
||||
// two users. Visible and encrypted for the other party.
|
||||
// Includes communications for:
|
||||
// * Profile changes
|
||||
// * Identity changes
|
||||
// * 1-1 chat messages
|
||||
// * Group chat messages
|
||||
//
|
||||
// DHT Schema: SMPL(0,1,[identityPublicKey])
|
||||
// DHT Key (UnicastOutbox): localConversation
|
||||
@ -54,12 +233,84 @@ message Message {
|
||||
message Conversation {
|
||||
// Profile to publish to friend
|
||||
Profile profile = 1;
|
||||
// Identity master (JSON) to publish to friend
|
||||
// Identity master (JSON) to publish to friend or chat room
|
||||
string identity_master_json = 2;
|
||||
// Messages DHTLog (xxx for now DHTShortArray)
|
||||
// Messages DHTLog
|
||||
veilid.TypedKey messages = 3;
|
||||
}
|
||||
|
||||
// Either a 1-1 conversation or a group chat
|
||||
// Privately encrypted, this is the local user's copy of the chat
|
||||
message Chat {
|
||||
// Settings
|
||||
ChatSettings settings = 1;
|
||||
// Conversation key for this user
|
||||
veilid.TypedKey local_conversation_record_key = 2;
|
||||
// Conversation key for the other party
|
||||
veilid.TypedKey remote_conversation_record_key = 3;
|
||||
}
|
||||
|
||||
// A group chat
|
||||
// Privately encrypted, this is the local user's copy of the chat
|
||||
message GroupChat {
|
||||
// Settings
|
||||
ChatSettings settings = 1;
|
||||
// Conversation key for this user
|
||||
veilid.TypedKey local_conversation_record_key = 2;
|
||||
// Conversation keys for the other parties
|
||||
repeated veilid.TypedKey remote_conversation_record_keys = 3;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// Accounts
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Publicly shared profile information for both contacts and accounts
|
||||
// Contains:
|
||||
// Name - Friendly name
|
||||
// Pronouns - Pronouns of user
|
||||
// Icon - Little picture to represent user in contact list
|
||||
message Profile {
|
||||
// Friendy name
|
||||
string name = 1;
|
||||
// Pronouns of user
|
||||
string pronouns = 2;
|
||||
// Description of the user
|
||||
string about = 3;
|
||||
// Status/away message
|
||||
string status = 4;
|
||||
// Availability
|
||||
Availability availability = 5;
|
||||
// Avatar DHTData
|
||||
optional veilid.TypedKey avatar = 6;
|
||||
}
|
||||
|
||||
// A record of an individual account
|
||||
// Pointed to by the identity account map in the identity key
|
||||
//
|
||||
// DHT Schema: DFLT(1)
|
||||
// DHT Private: accountSecretKey
|
||||
message Account {
|
||||
// The user's profile that gets shared with contacts
|
||||
Profile profile = 1;
|
||||
// Invisibility makes you always look 'Offline'
|
||||
bool invisible = 2;
|
||||
// Auto-away sets 'away' mode after an inactivity time
|
||||
uint32 auto_away_timeout_sec = 3;
|
||||
// The contacts DHTList for this account
|
||||
// DHT Private
|
||||
dht.OwnedDHTRecordPointer contact_list = 4;
|
||||
// The ContactInvitationRecord DHTShortArray for this account
|
||||
// DHT Private
|
||||
dht.OwnedDHTRecordPointer contact_invitation_records = 5;
|
||||
// The Chats DHTList for this account
|
||||
// DHT Private
|
||||
dht.OwnedDHTRecordPointer chat_list = 6;
|
||||
// The GroupChats DHTList for this account
|
||||
// DHT Private
|
||||
dht.OwnedDHTRecordPointer group_chat_list = 7;
|
||||
}
|
||||
|
||||
// A record of a contact that has accepted a contact invitation
|
||||
// Contains a copy of the most recent remote profile as well as
|
||||
// a locally edited profile.
|
||||
@ -80,87 +331,13 @@ message Contact {
|
||||
veilid.TypedKey remote_conversation_record_key = 5;
|
||||
// Our conversation key for friend to sync
|
||||
veilid.TypedKey local_conversation_record_key = 6;
|
||||
// Show availability
|
||||
// Show availability to this contact
|
||||
bool show_availability = 7;
|
||||
}
|
||||
|
||||
// Contact availability
|
||||
enum Availability {
|
||||
AVAILABILITY_UNSPECIFIED = 0;
|
||||
AVAILABILITY_OFFLINE = 1;
|
||||
AVAILABILITY_FREE = 2;
|
||||
AVAILABILITY_BUSY = 3;
|
||||
AVAILABILITY_AWAY = 4;
|
||||
}
|
||||
|
||||
// Publicly shared profile information for both contacts and accounts
|
||||
// Contains:
|
||||
// Name - Friendly name
|
||||
// Pronouns - Pronouns of user
|
||||
// Icon - Little picture to represent user in contact list
|
||||
message Profile {
|
||||
// Friendy name
|
||||
string name = 1;
|
||||
// Pronouns of user
|
||||
string pronouns = 2;
|
||||
// Status/away message
|
||||
string status = 3;
|
||||
// Availability
|
||||
Availability availability = 4;
|
||||
// Avatar DHTData
|
||||
optional veilid.TypedKey avatar = 5;
|
||||
}
|
||||
|
||||
|
||||
enum ChatType {
|
||||
CHAT_TYPE_UNSPECIFIED = 0;
|
||||
SINGLE_CONTACT = 1;
|
||||
GROUP = 2;
|
||||
}
|
||||
|
||||
// Either a 1-1 conversation or a group chat (eventually)
|
||||
// Privately encrypted, this is the local user's copy of the chat
|
||||
message Chat {
|
||||
// What kind of chat is this
|
||||
ChatType type = 1;
|
||||
// Conversation key for the other party
|
||||
veilid.TypedKey remote_conversation_record_key = 2;
|
||||
// Reconciled chat record DHTLog (xxx for now DHTShortArray)
|
||||
dht.OwnedDHTRecordPointer reconciled_chat_record = 3;
|
||||
}
|
||||
|
||||
// A record of an individual account
|
||||
// Pointed to by the identity account map in the identity key
|
||||
//
|
||||
// DHT Schema: DFLT(1)
|
||||
// DHT Private: accountSecretKey
|
||||
message Account {
|
||||
// The user's profile that gets shared with contacts
|
||||
Profile profile = 1;
|
||||
// Invisibility makes you always look 'Offline'
|
||||
bool invisible = 2;
|
||||
// Auto-away sets 'away' mode after an inactivity time
|
||||
uint32 auto_away_timeout_sec = 3;
|
||||
// The contacts DHTList for this account
|
||||
// DHT Private
|
||||
dht.OwnedDHTRecordPointer contact_list = 4;
|
||||
// The ContactInvitationRecord DHTShortArray for this account
|
||||
// DHT Private
|
||||
dht.OwnedDHTRecordPointer contact_invitation_records = 5;
|
||||
// The chats DHTList for this account
|
||||
// DHT Private
|
||||
dht.OwnedDHTRecordPointer chat_list = 6;
|
||||
|
||||
}
|
||||
|
||||
// EncryptionKeyType
|
||||
// Encryption of secret
|
||||
enum EncryptionKeyType {
|
||||
ENCRYPTION_KEY_TYPE_UNSPECIFIED = 0;
|
||||
ENCRYPTION_KEY_TYPE_NONE = 1;
|
||||
ENCRYPTION_KEY_TYPE_PIN = 2;
|
||||
ENCRYPTION_KEY_TYPE_PASSWORD = 3;
|
||||
}
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// Invitations
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Invitation that is shared for VeilidChat contact connections
|
||||
// serialized to QR code or data blob, not send over DHT, out of band.
|
||||
|
@ -44,7 +44,7 @@ Widget waitingPage({String? text}) => Builder(builder: (context) {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
return ColoredBox(
|
||||
color: scale.tertiaryScale.primaryText,
|
||||
color: scale.tertiaryScale.appBackground,
|
||||
child: Center(
|
||||
child: Column(children: [
|
||||
buildProgressIndicator().expanded(),
|
||||
|
@ -7,6 +7,7 @@ import 'fixtures/fixtures.dart';
|
||||
import 'test_dht_log.dart';
|
||||
import 'test_dht_record_pool.dart';
|
||||
import 'test_dht_short_array.dart';
|
||||
import 'test_table_db_array.dart';
|
||||
|
||||
void main() {
|
||||
final startTime = DateTime.now();
|
||||
@ -34,6 +35,17 @@ void main() {
|
||||
setUpAll(veilidFixture.attach);
|
||||
tearDownAll(veilidFixture.detach);
|
||||
|
||||
group('TableDB Tests', () {
|
||||
group('TableDBArray Tests', () {
|
||||
test('create TableDBArray', makeTestTableDBArrayCreateDelete());
|
||||
test(
|
||||
timeout: const Timeout(Duration(seconds: 480)),
|
||||
'add/truncate TableDBArray',
|
||||
makeTestDHTLogAddTruncate(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('DHT Support Tests', () {
|
||||
setUpAll(updateProcessorFixture.setUp);
|
||||
setUpAll(tickerFixture.setUp);
|
||||
|
@ -64,8 +64,7 @@ Future<void> Function() makeTestDHTLogAddTruncate({required int stride}) =>
|
||||
const chunk = 25;
|
||||
for (var n = 0; n < dataset.length; n += chunk) {
|
||||
print('$n-${n + chunk - 1} ');
|
||||
final success =
|
||||
await w.tryAppendItems(dataset.sublist(n, n + chunk));
|
||||
final success = await w.tryAddItems(dataset.sublist(n, n + chunk));
|
||||
expect(success, isTrue);
|
||||
}
|
||||
});
|
||||
@ -94,7 +93,7 @@ Future<void> Function() makeTestDHTLogAddTruncate({required int stride}) =>
|
||||
}
|
||||
print('truncate\n');
|
||||
{
|
||||
await dlog.operateAppend((w) async => w.truncate(5));
|
||||
await dlog.operateAppend((w) async => w.truncate(w.length - 5));
|
||||
}
|
||||
{
|
||||
final dataset6 = await dlog
|
||||
@ -103,7 +102,7 @@ Future<void> Function() makeTestDHTLogAddTruncate({required int stride}) =>
|
||||
}
|
||||
print('truncate 2\n');
|
||||
{
|
||||
await dlog.operateAppend((w) async => w.truncate(251));
|
||||
await dlog.operateAppend((w) async => w.truncate(w.length - 251));
|
||||
}
|
||||
{
|
||||
final dataset7 = await dlog
|
||||
|
@ -0,0 +1,134 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
Future<void> Function() makeTestTableDBArrayCreateDelete() => () async {
|
||||
// Close before delete
|
||||
{
|
||||
final arr = await TableDBArray(
|
||||
table: 'test', crypto: const VeilidCryptoPublic());
|
||||
|
||||
expect(await arr.operate((r) async => r.length), isZero);
|
||||
expect(arr.isOpen, isTrue);
|
||||
await arr.close();
|
||||
expect(arr.isOpen, isFalse);
|
||||
await arr.delete();
|
||||
// Operate should fail
|
||||
await expectLater(() async => arr.operate((r) async => r.length),
|
||||
throwsA(isA<StateError>()));
|
||||
}
|
||||
|
||||
// Close after delete
|
||||
{
|
||||
final arr = await DHTShortArray.create(
|
||||
debugName: 'sa_create_delete 2 stride $stride', stride: stride);
|
||||
await arr.delete();
|
||||
// Operate should still succeed because things aren't closed
|
||||
expect(await arr.operate((r) async => r.length), isZero);
|
||||
await arr.close();
|
||||
// Operate should fail
|
||||
await expectLater(() async => arr.operate((r) async => r.length),
|
||||
throwsA(isA<StateError>()));
|
||||
}
|
||||
|
||||
// Close after delete multiple
|
||||
// Okay to request delete multiple times before close
|
||||
{
|
||||
final arr = await DHTShortArray.create(
|
||||
debugName: 'sa_create_delete 3 stride $stride', stride: stride);
|
||||
await arr.delete();
|
||||
await arr.delete();
|
||||
// Operate should still succeed because things aren't closed
|
||||
expect(await arr.operate((r) async => r.length), isZero);
|
||||
await arr.close();
|
||||
await expectLater(() async => arr.close(), throwsA(isA<StateError>()));
|
||||
// Operate should fail
|
||||
await expectLater(() async => arr.operate((r) async => r.length),
|
||||
throwsA(isA<StateError>()));
|
||||
}
|
||||
};
|
||||
|
||||
Future<void> Function() makeTestTableDBArrayAdd({required int stride}) =>
|
||||
() async {
|
||||
final arr = await DHTShortArray.create(
|
||||
debugName: 'sa_add 1 stride $stride', stride: stride);
|
||||
|
||||
final dataset = Iterable<int>.generate(256)
|
||||
.map((n) => utf8.encode('elem $n'))
|
||||
.toList();
|
||||
|
||||
print('adding singles\n');
|
||||
{
|
||||
final res = await arr.operateWrite((w) async {
|
||||
for (var n = 4; n < 8; n++) {
|
||||
print('$n ');
|
||||
final success = await w.tryAddItem(dataset[n]);
|
||||
expect(success, isTrue);
|
||||
}
|
||||
});
|
||||
expect(res, isNull);
|
||||
}
|
||||
|
||||
print('adding batch\n');
|
||||
{
|
||||
final res = await arr.operateWrite((w) async {
|
||||
print('${dataset.length ~/ 2}-${dataset.length}');
|
||||
final success = await w.tryAddItems(
|
||||
dataset.sublist(dataset.length ~/ 2, dataset.length));
|
||||
expect(success, isTrue);
|
||||
});
|
||||
expect(res, isNull);
|
||||
}
|
||||
|
||||
print('inserting singles\n');
|
||||
{
|
||||
final res = await arr.operateWrite((w) async {
|
||||
for (var n = 0; n < 4; n++) {
|
||||
print('$n ');
|
||||
final success = await w.tryInsertItem(n, dataset[n]);
|
||||
expect(success, isTrue);
|
||||
}
|
||||
});
|
||||
expect(res, isNull);
|
||||
}
|
||||
|
||||
print('inserting batch\n');
|
||||
{
|
||||
final res = await arr.operateWrite((w) async {
|
||||
print('8-${dataset.length ~/ 2}');
|
||||
final success = await w.tryInsertItems(
|
||||
8, dataset.sublist(8, dataset.length ~/ 2));
|
||||
expect(success, isTrue);
|
||||
});
|
||||
expect(res, isNull);
|
||||
}
|
||||
|
||||
//print('get all\n');
|
||||
{
|
||||
final dataset2 = await arr.operate((r) async => r.getItemRange(0));
|
||||
expect(dataset2, equals(dataset));
|
||||
}
|
||||
{
|
||||
final dataset3 =
|
||||
await arr.operate((r) async => r.getItemRange(64, length: 128));
|
||||
expect(dataset3, equals(dataset.sublist(64, 64 + 128)));
|
||||
}
|
||||
|
||||
//print('clear\n');
|
||||
{
|
||||
await arr.operateWriteEventual((w) async {
|
||||
await w.clear();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
//print('get all\n');
|
||||
{
|
||||
final dataset4 = await arr.operate((r) async => r.getItemRange(0));
|
||||
expect(dataset4, isEmpty);
|
||||
}
|
||||
|
||||
await arr.delete();
|
||||
await arr.close();
|
||||
};
|
@ -9,11 +9,11 @@ import 'package:meta/meta.dart';
|
||||
|
||||
import '../../../veilid_support.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../interfaces/dht_append_truncate.dart';
|
||||
import '../interfaces/dht_append.dart';
|
||||
|
||||
part 'dht_log_spine.dart';
|
||||
part 'dht_log_read.dart';
|
||||
part 'dht_log_append.dart';
|
||||
part 'dht_log_write.dart';
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
|
||||
@ -60,7 +60,7 @@ class DHTLog implements DHTDeleteable<DHTLog, DHTLog> {
|
||||
int stride = DHTShortArray.maxElements,
|
||||
VeilidRoutingContext? routingContext,
|
||||
TypedKey? parent,
|
||||
DHTRecordCrypto? crypto,
|
||||
VeilidCrypto? crypto,
|
||||
KeyPair? writer}) async {
|
||||
assert(stride <= DHTShortArray.maxElements, 'stride too long');
|
||||
final pool = DHTRecordPool.instance;
|
||||
@ -102,7 +102,7 @@ class DHTLog implements DHTDeleteable<DHTLog, DHTLog> {
|
||||
{required String debugName,
|
||||
VeilidRoutingContext? routingContext,
|
||||
TypedKey? parent,
|
||||
DHTRecordCrypto? crypto}) async {
|
||||
VeilidCrypto? crypto}) async {
|
||||
final spineRecord = await DHTRecordPool.instance.openRecordRead(
|
||||
logRecordKey,
|
||||
debugName: debugName,
|
||||
@ -125,7 +125,7 @@ class DHTLog implements DHTDeleteable<DHTLog, DHTLog> {
|
||||
required String debugName,
|
||||
VeilidRoutingContext? routingContext,
|
||||
TypedKey? parent,
|
||||
DHTRecordCrypto? crypto,
|
||||
VeilidCrypto? crypto,
|
||||
}) async {
|
||||
final spineRecord = await DHTRecordPool.instance.openRecordWrite(
|
||||
logRecordKey, writer,
|
||||
@ -148,7 +148,7 @@ class DHTLog implements DHTDeleteable<DHTLog, DHTLog> {
|
||||
required String debugName,
|
||||
required TypedKey parent,
|
||||
VeilidRoutingContext? routingContext,
|
||||
DHTRecordCrypto? crypto,
|
||||
VeilidCrypto? crypto,
|
||||
}) =>
|
||||
openWrite(
|
||||
ownedLogRecordPointer.recordKey,
|
||||
@ -209,7 +209,8 @@ class DHTLog implements DHTDeleteable<DHTLog, DHTLog> {
|
||||
OwnedDHTRecordPointer get recordPointer => _spine.recordPointer;
|
||||
|
||||
/// Runs a closure allowing read-only access to the log
|
||||
Future<T?> operate<T>(Future<T?> Function(DHTRandomRead) closure) async {
|
||||
Future<T?> operate<T>(
|
||||
Future<T?> Function(DHTLogReadOperations) closure) async {
|
||||
if (!isOpen) {
|
||||
throw StateError('log is not open"');
|
||||
}
|
||||
@ -226,13 +227,13 @@ class DHTLog implements DHTDeleteable<DHTLog, DHTLog> {
|
||||
/// Throws DHTOperateException if the write could not be performed
|
||||
/// at this time
|
||||
Future<T> operateAppend<T>(
|
||||
Future<T> Function(DHTAppendTruncateRandomRead) closure) async {
|
||||
Future<T> Function(DHTLogWriteOperations) closure) async {
|
||||
if (!isOpen) {
|
||||
throw StateError('log is not open"');
|
||||
}
|
||||
|
||||
return _spine.operateAppend((spine) async {
|
||||
final writer = _DHTLogAppend._(spine);
|
||||
final writer = _DHTLogWrite._(spine);
|
||||
return closure(writer);
|
||||
});
|
||||
}
|
||||
@ -244,14 +245,14 @@ class DHTLog implements DHTDeleteable<DHTLog, DHTLog> {
|
||||
/// succeeded, returning false will trigger another eventual consistency
|
||||
/// attempt.
|
||||
Future<void> operateAppendEventual(
|
||||
Future<bool> Function(DHTAppendTruncateRandomRead) closure,
|
||||
Future<bool> Function(DHTLogWriteOperations) closure,
|
||||
{Duration? timeout}) async {
|
||||
if (!isOpen) {
|
||||
throw StateError('log is not open"');
|
||||
}
|
||||
|
||||
return _spine.operateAppendEventual((spine) async {
|
||||
final writer = _DHTLogAppend._(spine);
|
||||
final writer = _DHTLogWrite._(spine);
|
||||
return closure(writer);
|
||||
}, timeout: timeout);
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../../../veilid_support.dart';
|
||||
import '../interfaces/dht_append_truncate.dart';
|
||||
|
||||
@immutable
|
||||
class DHTLogElementState<T> extends Equatable {
|
||||
@ -184,19 +183,20 @@ class DHTLogCubit<T> extends Cubit<DHTLogBusyState<T>>
|
||||
await super.close();
|
||||
}
|
||||
|
||||
Future<R?> operate<R>(Future<R?> Function(DHTRandomRead) closure) async {
|
||||
Future<R?> operate<R>(
|
||||
Future<R?> Function(DHTLogReadOperations) closure) async {
|
||||
await _initWait();
|
||||
return _log.operate(closure);
|
||||
}
|
||||
|
||||
Future<R> operateAppend<R>(
|
||||
Future<R> Function(DHTAppendTruncateRandomRead) closure) async {
|
||||
Future<R> Function(DHTLogWriteOperations) closure) async {
|
||||
await _initWait();
|
||||
return _log.operateAppend(closure);
|
||||
}
|
||||
|
||||
Future<void> operateAppendEventual(
|
||||
Future<bool> Function(DHTAppendTruncateRandomRead) closure,
|
||||
Future<bool> Function(DHTLogWriteOperations) closure,
|
||||
{Duration? timeout}) async {
|
||||
await _initWait();
|
||||
return _log.operateAppendEventual(closure, timeout: timeout);
|
||||
|
@ -3,7 +3,9 @@ part of 'dht_log.dart';
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Reader-only implementation
|
||||
|
||||
class _DHTLogRead implements DHTRandomRead {
|
||||
abstract class DHTLogReadOperations implements DHTRandomRead {}
|
||||
|
||||
class _DHTLogRead implements DHTLogReadOperations {
|
||||
_DHTLogRead._(_DHTLogSpine spine) : _spine = spine;
|
||||
|
||||
@override
|
||||
|
@ -1,13 +1,32 @@
|
||||
part of 'dht_log.dart';
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Append/truncate implementation
|
||||
// Writer implementation
|
||||
|
||||
class _DHTLogAppend extends _DHTLogRead implements DHTAppendTruncateRandomRead {
|
||||
_DHTLogAppend._(super.spine) : super._();
|
||||
abstract class DHTLogWriteOperations
|
||||
implements DHTRandomRead, DHTRandomWrite, DHTAdd, DHTTruncate, DHTClear {}
|
||||
|
||||
class _DHTLogWrite extends _DHTLogRead implements DHTLogWriteOperations {
|
||||
_DHTLogWrite._(super.spine) : super._();
|
||||
|
||||
@override
|
||||
Future<bool> tryAppendItem(Uint8List value) async {
|
||||
Future<bool> tryWriteItem(int pos, Uint8List newValue,
|
||||
{Output<Uint8List>? output}) async {
|
||||
if (pos < 0 || pos >= _spine.length) {
|
||||
throw IndexError.withLength(pos, _spine.length);
|
||||
}
|
||||
final lookup = await _spine.lookupPosition(pos);
|
||||
if (lookup == null) {
|
||||
throw StateError("can't write to dht log");
|
||||
}
|
||||
|
||||
// Write item to the segment
|
||||
return lookup.scope((sa) => sa.operateWrite((write) async =>
|
||||
write.tryWriteItem(lookup.pos, newValue, output: output)));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> tryAddItem(Uint8List value) async {
|
||||
// Allocate empty index at the end of the list
|
||||
final insertPos = _spine.length;
|
||||
_spine.allocateTail(1);
|
||||
@ -30,7 +49,7 @@ class _DHTLogAppend extends _DHTLogRead implements DHTAppendTruncateRandomRead {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> tryAppendItems(List<Uint8List> values) async {
|
||||
Future<bool> tryAddItems(List<Uint8List> values) async {
|
||||
// Allocate empty index at the end of the list
|
||||
final insertPos = _spine.length;
|
||||
_spine.allocateTail(values.length);
|
||||
@ -76,15 +95,14 @@ class _DHTLogAppend extends _DHTLogRead implements DHTAppendTruncateRandomRead {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> truncate(int count) async {
|
||||
count = min(count, _spine.length);
|
||||
if (count == 0) {
|
||||
Future<void> truncate(int newLength) async {
|
||||
if (newLength < 0) {
|
||||
throw StateError('can not truncate to negative length');
|
||||
}
|
||||
if (newLength >= _spine.length) {
|
||||
return;
|
||||
}
|
||||
if (count < 0) {
|
||||
throw StateError('can not remove negative items');
|
||||
}
|
||||
await _spine.releaseHead(count);
|
||||
await _spine.releaseHead(_spine.length - newLength);
|
||||
}
|
||||
|
||||
@override
|
@ -1,4 +1,3 @@
|
||||
export 'default_dht_record_cubit.dart';
|
||||
export 'dht_record_crypto.dart';
|
||||
export 'dht_record_cubit.dart';
|
||||
export 'dht_record_pool.dart';
|
||||
|
@ -42,7 +42,7 @@ class DHTRecord implements DHTDeleteable<DHTRecord, DHTRecord> {
|
||||
required SharedDHTRecordData sharedDHTRecordData,
|
||||
required int defaultSubkey,
|
||||
required KeyPair? writer,
|
||||
required DHTRecordCrypto crypto,
|
||||
required VeilidCrypto crypto,
|
||||
required this.debugName})
|
||||
: _crypto = crypto,
|
||||
_routingContext = routingContext,
|
||||
@ -104,7 +104,7 @@ class DHTRecord implements DHTDeleteable<DHTRecord, DHTRecord> {
|
||||
int get subkeyCount =>
|
||||
_sharedDHTRecordData.recordDescriptor.schema.subkeyCount();
|
||||
KeyPair? get writer => _writer;
|
||||
DHTRecordCrypto get crypto => _crypto;
|
||||
VeilidCrypto get crypto => _crypto;
|
||||
OwnedDHTRecordPointer get ownedDHTRecordPointer =>
|
||||
OwnedDHTRecordPointer(recordKey: key, owner: ownerKeyPair!);
|
||||
int subkeyOrDefault(int subkey) => (subkey == -1) ? _defaultSubkey : subkey;
|
||||
@ -118,7 +118,7 @@ class DHTRecord implements DHTDeleteable<DHTRecord, DHTRecord> {
|
||||
/// returned if one was returned.
|
||||
Future<Uint8List?> get(
|
||||
{int subkey = -1,
|
||||
DHTRecordCrypto? crypto,
|
||||
VeilidCrypto? crypto,
|
||||
DHTRecordRefreshMode refreshMode = DHTRecordRefreshMode.cached,
|
||||
Output<int>? outSeqNum}) async {
|
||||
subkey = subkeyOrDefault(subkey);
|
||||
@ -146,7 +146,7 @@ class DHTRecord implements DHTDeleteable<DHTRecord, DHTRecord> {
|
||||
return null;
|
||||
}
|
||||
// If we're returning a value, decrypt it
|
||||
final out = (crypto ?? _crypto).decrypt(valueData.data, subkey);
|
||||
final out = (crypto ?? _crypto).decrypt(valueData.data);
|
||||
if (outSeqNum != null) {
|
||||
outSeqNum.save(valueData.seq);
|
||||
}
|
||||
@ -163,7 +163,7 @@ class DHTRecord implements DHTDeleteable<DHTRecord, DHTRecord> {
|
||||
/// returned if one was returned.
|
||||
Future<T?> getJson<T>(T Function(dynamic) fromJson,
|
||||
{int subkey = -1,
|
||||
DHTRecordCrypto? crypto,
|
||||
VeilidCrypto? crypto,
|
||||
DHTRecordRefreshMode refreshMode = DHTRecordRefreshMode.cached,
|
||||
Output<int>? outSeqNum}) async {
|
||||
final data = await get(
|
||||
@ -189,7 +189,7 @@ class DHTRecord implements DHTDeleteable<DHTRecord, DHTRecord> {
|
||||
Future<T?> getProtobuf<T extends GeneratedMessage>(
|
||||
T Function(List<int> i) fromBuffer,
|
||||
{int subkey = -1,
|
||||
DHTRecordCrypto? crypto,
|
||||
VeilidCrypto? crypto,
|
||||
DHTRecordRefreshMode refreshMode = DHTRecordRefreshMode.cached,
|
||||
Output<int>? outSeqNum}) async {
|
||||
final data = await get(
|
||||
@ -208,13 +208,12 @@ class DHTRecord implements DHTDeleteable<DHTRecord, DHTRecord> {
|
||||
/// If the value was succesfully written, null is returned
|
||||
Future<Uint8List?> tryWriteBytes(Uint8List newValue,
|
||||
{int subkey = -1,
|
||||
DHTRecordCrypto? crypto,
|
||||
VeilidCrypto? crypto,
|
||||
KeyPair? writer,
|
||||
Output<int>? outSeqNum}) async {
|
||||
subkey = subkeyOrDefault(subkey);
|
||||
final lastSeq = await _localSubkeySeq(subkey);
|
||||
final encryptedNewValue =
|
||||
await (crypto ?? _crypto).encrypt(newValue, subkey);
|
||||
final encryptedNewValue = await (crypto ?? _crypto).encrypt(newValue);
|
||||
|
||||
// Set the new data if possible
|
||||
var newValueData = await _routingContext
|
||||
@ -246,7 +245,7 @@ class DHTRecord implements DHTDeleteable<DHTRecord, DHTRecord> {
|
||||
|
||||
// Decrypt value to return it
|
||||
final decryptedNewValue =
|
||||
await (crypto ?? _crypto).decrypt(newValueData.data, subkey);
|
||||
await (crypto ?? _crypto).decrypt(newValueData.data);
|
||||
if (isUpdated) {
|
||||
DHTRecordPool.instance
|
||||
.processLocalValueChange(key, decryptedNewValue, subkey);
|
||||
@ -259,13 +258,12 @@ class DHTRecord implements DHTDeleteable<DHTRecord, DHTRecord> {
|
||||
/// will be made to write the subkey until this succeeds
|
||||
Future<void> eventualWriteBytes(Uint8List newValue,
|
||||
{int subkey = -1,
|
||||
DHTRecordCrypto? crypto,
|
||||
VeilidCrypto? crypto,
|
||||
KeyPair? writer,
|
||||
Output<int>? outSeqNum}) async {
|
||||
subkey = subkeyOrDefault(subkey);
|
||||
final lastSeq = await _localSubkeySeq(subkey);
|
||||
final encryptedNewValue =
|
||||
await (crypto ?? _crypto).encrypt(newValue, subkey);
|
||||
final encryptedNewValue = await (crypto ?? _crypto).encrypt(newValue);
|
||||
|
||||
ValueData? newValueData;
|
||||
do {
|
||||
@ -309,7 +307,7 @@ class DHTRecord implements DHTDeleteable<DHTRecord, DHTRecord> {
|
||||
Future<void> eventualUpdateBytes(
|
||||
Future<Uint8List> Function(Uint8List? oldValue) update,
|
||||
{int subkey = -1,
|
||||
DHTRecordCrypto? crypto,
|
||||
VeilidCrypto? crypto,
|
||||
KeyPair? writer,
|
||||
Output<int>? outSeqNum}) async {
|
||||
subkey = subkeyOrDefault(subkey);
|
||||
@ -334,7 +332,7 @@ class DHTRecord implements DHTDeleteable<DHTRecord, DHTRecord> {
|
||||
/// Like 'tryWriteBytes' but with JSON marshal/unmarshal of the value
|
||||
Future<T?> tryWriteJson<T>(T Function(dynamic) fromJson, T newValue,
|
||||
{int subkey = -1,
|
||||
DHTRecordCrypto? crypto,
|
||||
VeilidCrypto? crypto,
|
||||
KeyPair? writer,
|
||||
Output<int>? outSeqNum}) =>
|
||||
tryWriteBytes(jsonEncodeBytes(newValue),
|
||||
@ -353,7 +351,7 @@ class DHTRecord implements DHTDeleteable<DHTRecord, DHTRecord> {
|
||||
Future<T?> tryWriteProtobuf<T extends GeneratedMessage>(
|
||||
T Function(List<int>) fromBuffer, T newValue,
|
||||
{int subkey = -1,
|
||||
DHTRecordCrypto? crypto,
|
||||
VeilidCrypto? crypto,
|
||||
KeyPair? writer,
|
||||
Output<int>? outSeqNum}) =>
|
||||
tryWriteBytes(newValue.writeToBuffer(),
|
||||
@ -371,7 +369,7 @@ class DHTRecord implements DHTDeleteable<DHTRecord, DHTRecord> {
|
||||
/// Like 'eventualWriteBytes' but with JSON marshal/unmarshal of the value
|
||||
Future<void> eventualWriteJson<T>(T newValue,
|
||||
{int subkey = -1,
|
||||
DHTRecordCrypto? crypto,
|
||||
VeilidCrypto? crypto,
|
||||
KeyPair? writer,
|
||||
Output<int>? outSeqNum}) =>
|
||||
eventualWriteBytes(jsonEncodeBytes(newValue),
|
||||
@ -380,7 +378,7 @@ class DHTRecord implements DHTDeleteable<DHTRecord, DHTRecord> {
|
||||
/// Like 'eventualWriteBytes' but with protobuf marshal/unmarshal of the value
|
||||
Future<void> eventualWriteProtobuf<T extends GeneratedMessage>(T newValue,
|
||||
{int subkey = -1,
|
||||
DHTRecordCrypto? crypto,
|
||||
VeilidCrypto? crypto,
|
||||
KeyPair? writer,
|
||||
Output<int>? outSeqNum}) =>
|
||||
eventualWriteBytes(newValue.writeToBuffer(),
|
||||
@ -390,7 +388,7 @@ class DHTRecord implements DHTDeleteable<DHTRecord, DHTRecord> {
|
||||
Future<void> eventualUpdateJson<T>(
|
||||
T Function(dynamic) fromJson, Future<T> Function(T?) update,
|
||||
{int subkey = -1,
|
||||
DHTRecordCrypto? crypto,
|
||||
VeilidCrypto? crypto,
|
||||
KeyPair? writer,
|
||||
Output<int>? outSeqNum}) =>
|
||||
eventualUpdateBytes(jsonUpdate(fromJson, update),
|
||||
@ -400,7 +398,7 @@ class DHTRecord implements DHTDeleteable<DHTRecord, DHTRecord> {
|
||||
Future<void> eventualUpdateProtobuf<T extends GeneratedMessage>(
|
||||
T Function(List<int>) fromBuffer, Future<T> Function(T?) update,
|
||||
{int subkey = -1,
|
||||
DHTRecordCrypto? crypto,
|
||||
VeilidCrypto? crypto,
|
||||
KeyPair? writer,
|
||||
Output<int>? outSeqNum}) =>
|
||||
eventualUpdateBytes(protobufUpdate(fromBuffer, update),
|
||||
@ -433,7 +431,7 @@ class DHTRecord implements DHTDeleteable<DHTRecord, DHTRecord> {
|
||||
DHTRecord record, Uint8List? data, List<ValueSubkeyRange> subkeys)
|
||||
onUpdate, {
|
||||
bool localChanges = true,
|
||||
DHTRecordCrypto? crypto,
|
||||
VeilidCrypto? crypto,
|
||||
}) async {
|
||||
// Set up watch requirements
|
||||
_watchController ??=
|
||||
@ -457,8 +455,7 @@ class DHTRecord implements DHTDeleteable<DHTRecord, DHTRecord> {
|
||||
final changeData = change.data;
|
||||
data = changeData == null
|
||||
? null
|
||||
: await (crypto ?? _crypto)
|
||||
.decrypt(changeData, change.subkeys.first.low);
|
||||
: await (crypto ?? _crypto).decrypt(changeData);
|
||||
}
|
||||
await onUpdate(this, data, change.subkeys);
|
||||
});
|
||||
@ -544,7 +541,7 @@ class DHTRecord implements DHTDeleteable<DHTRecord, DHTRecord> {
|
||||
final VeilidRoutingContext _routingContext;
|
||||
final int _defaultSubkey;
|
||||
final KeyPair? _writer;
|
||||
final DHTRecordCrypto _crypto;
|
||||
final VeilidCrypto _crypto;
|
||||
final String debugName;
|
||||
final _mutex = Mutex();
|
||||
int _openCount;
|
||||
|
@ -1,53 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import '../../../../../veilid_support.dart';
|
||||
|
||||
abstract class DHTRecordCrypto {
|
||||
Future<Uint8List> encrypt(Uint8List data, int subkey);
|
||||
Future<Uint8List> decrypt(Uint8List data, int subkey);
|
||||
}
|
||||
|
||||
////////////////////////////////////
|
||||
/// Private DHT Record: Encrypted for a specific symmetric key
|
||||
class DHTRecordCryptoPrivate implements DHTRecordCrypto {
|
||||
DHTRecordCryptoPrivate._(
|
||||
VeilidCryptoSystem cryptoSystem, SharedSecret secretKey)
|
||||
: _cryptoSystem = cryptoSystem,
|
||||
_secretKey = secretKey;
|
||||
final VeilidCryptoSystem _cryptoSystem;
|
||||
final SharedSecret _secretKey;
|
||||
|
||||
static Future<DHTRecordCryptoPrivate> fromTypedKeyPair(
|
||||
TypedKeyPair typedKeyPair) async {
|
||||
final cryptoSystem =
|
||||
await Veilid.instance.getCryptoSystem(typedKeyPair.kind);
|
||||
final secretKey = typedKeyPair.secret;
|
||||
return DHTRecordCryptoPrivate._(cryptoSystem, secretKey);
|
||||
}
|
||||
|
||||
static Future<DHTRecordCryptoPrivate> fromSecret(
|
||||
CryptoKind kind, SharedSecret secretKey) async {
|
||||
final cryptoSystem = await Veilid.instance.getCryptoSystem(kind);
|
||||
return DHTRecordCryptoPrivate._(cryptoSystem, secretKey);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List> encrypt(Uint8List data, int subkey) =>
|
||||
_cryptoSystem.encryptNoAuthWithNonce(data, _secretKey);
|
||||
|
||||
@override
|
||||
Future<Uint8List> decrypt(Uint8List data, int subkey) =>
|
||||
_cryptoSystem.decryptNoAuthWithNonce(data, _secretKey);
|
||||
}
|
||||
|
||||
////////////////////////////////////
|
||||
/// Public DHT Record: No encryption
|
||||
class DHTRecordCryptoPublic implements DHTRecordCrypto {
|
||||
const DHTRecordCryptoPublic();
|
||||
|
||||
@override
|
||||
Future<Uint8List> encrypt(Uint8List data, int subkey) async => data;
|
||||
|
||||
@override
|
||||
Future<Uint8List> decrypt(Uint8List data, int subkey) async => data;
|
||||
}
|
@ -526,7 +526,7 @@ class DHTRecordPool with TableDBBackedJson<DHTRecordPoolAllocations> {
|
||||
TypedKey? parent,
|
||||
DHTSchema schema = const DHTSchema.dflt(oCnt: 1),
|
||||
int defaultSubkey = 0,
|
||||
DHTRecordCrypto? crypto,
|
||||
VeilidCrypto? crypto,
|
||||
KeyPair? writer,
|
||||
}) async =>
|
||||
_mutex.protect(() async {
|
||||
@ -547,7 +547,7 @@ class DHTRecordPool with TableDBBackedJson<DHTRecordPoolAllocations> {
|
||||
writer: writer ??
|
||||
openedRecordInfo.shared.recordDescriptor.ownerKeyPair(),
|
||||
crypto: crypto ??
|
||||
await DHTRecordCryptoPrivate.fromTypedKeyPair(openedRecordInfo
|
||||
await VeilidCryptoPrivate.fromTypedKeyPair(openedRecordInfo
|
||||
.shared.recordDescriptor
|
||||
.ownerTypedKeyPair()!));
|
||||
|
||||
@ -562,7 +562,7 @@ class DHTRecordPool with TableDBBackedJson<DHTRecordPoolAllocations> {
|
||||
VeilidRoutingContext? routingContext,
|
||||
TypedKey? parent,
|
||||
int defaultSubkey = 0,
|
||||
DHTRecordCrypto? crypto}) async =>
|
||||
VeilidCrypto? crypto}) async =>
|
||||
_mutex.protect(() async {
|
||||
final dhtctx = routingContext ?? _routingContext;
|
||||
|
||||
@ -578,7 +578,7 @@ class DHTRecordPool with TableDBBackedJson<DHTRecordPoolAllocations> {
|
||||
defaultSubkey: defaultSubkey,
|
||||
sharedDHTRecordData: openedRecordInfo.shared,
|
||||
writer: null,
|
||||
crypto: crypto ?? const DHTRecordCryptoPublic());
|
||||
crypto: crypto ?? const VeilidCryptoPublic());
|
||||
|
||||
openedRecordInfo.records.add(rec);
|
||||
|
||||
@ -593,7 +593,7 @@ class DHTRecordPool with TableDBBackedJson<DHTRecordPoolAllocations> {
|
||||
VeilidRoutingContext? routingContext,
|
||||
TypedKey? parent,
|
||||
int defaultSubkey = 0,
|
||||
DHTRecordCrypto? crypto,
|
||||
VeilidCrypto? crypto,
|
||||
}) async =>
|
||||
_mutex.protect(() async {
|
||||
final dhtctx = routingContext ?? _routingContext;
|
||||
@ -612,7 +612,7 @@ class DHTRecordPool with TableDBBackedJson<DHTRecordPoolAllocations> {
|
||||
writer: writer,
|
||||
sharedDHTRecordData: openedRecordInfo.shared,
|
||||
crypto: crypto ??
|
||||
await DHTRecordCryptoPrivate.fromTypedKeyPair(
|
||||
await VeilidCryptoPrivate.fromTypedKeyPair(
|
||||
TypedKeyPair.fromKeyPair(recordKey.kind, writer)));
|
||||
|
||||
openedRecordInfo.records.add(rec);
|
||||
@ -632,7 +632,7 @@ class DHTRecordPool with TableDBBackedJson<DHTRecordPoolAllocations> {
|
||||
required TypedKey parent,
|
||||
VeilidRoutingContext? routingContext,
|
||||
int defaultSubkey = 0,
|
||||
DHTRecordCrypto? crypto,
|
||||
VeilidCrypto? crypto,
|
||||
}) =>
|
||||
openRecordWrite(
|
||||
ownedDHTRecordPointer.recordKey,
|
||||
|
@ -33,7 +33,7 @@ class DHTShortArray implements DHTDeleteable<DHTShortArray, DHTShortArray> {
|
||||
int stride = maxElements,
|
||||
VeilidRoutingContext? routingContext,
|
||||
TypedKey? parent,
|
||||
DHTRecordCrypto? crypto,
|
||||
VeilidCrypto? crypto,
|
||||
KeyPair? writer}) async {
|
||||
assert(stride <= maxElements, 'stride too long');
|
||||
final pool = DHTRecordPool.instance;
|
||||
@ -79,7 +79,7 @@ class DHTShortArray implements DHTDeleteable<DHTShortArray, DHTShortArray> {
|
||||
{required String debugName,
|
||||
VeilidRoutingContext? routingContext,
|
||||
TypedKey? parent,
|
||||
DHTRecordCrypto? crypto}) async {
|
||||
VeilidCrypto? crypto}) async {
|
||||
final dhtRecord = await DHTRecordPool.instance.openRecordRead(headRecordKey,
|
||||
debugName: debugName,
|
||||
parent: parent,
|
||||
@ -101,7 +101,7 @@ class DHTShortArray implements DHTDeleteable<DHTShortArray, DHTShortArray> {
|
||||
required String debugName,
|
||||
VeilidRoutingContext? routingContext,
|
||||
TypedKey? parent,
|
||||
DHTRecordCrypto? crypto,
|
||||
VeilidCrypto? crypto,
|
||||
}) async {
|
||||
final dhtRecord = await DHTRecordPool.instance.openRecordWrite(
|
||||
headRecordKey, writer,
|
||||
@ -124,7 +124,7 @@ class DHTShortArray implements DHTDeleteable<DHTShortArray, DHTShortArray> {
|
||||
required String debugName,
|
||||
required TypedKey parent,
|
||||
VeilidRoutingContext? routingContext,
|
||||
DHTRecordCrypto? crypto,
|
||||
VeilidCrypto? crypto,
|
||||
}) =>
|
||||
openWrite(
|
||||
ownedShortArrayRecordPointer.recordKey,
|
||||
@ -186,7 +186,8 @@ class DHTShortArray implements DHTDeleteable<DHTShortArray, DHTShortArray> {
|
||||
OwnedDHTRecordPointer get recordPointer => _head.recordPointer;
|
||||
|
||||
/// Runs a closure allowing read-only access to the shortarray
|
||||
Future<T> operate<T>(Future<T> Function(DHTRandomRead) closure) async {
|
||||
Future<T> operate<T>(
|
||||
Future<T> Function(DHTShortArrayReadOperations) closure) async {
|
||||
if (!isOpen) {
|
||||
throw StateError('short array is not open"');
|
||||
}
|
||||
@ -203,7 +204,7 @@ class DHTShortArray implements DHTDeleteable<DHTShortArray, DHTShortArray> {
|
||||
/// Throws DHTOperateException if the write could not be performed
|
||||
/// at this time
|
||||
Future<T> operateWrite<T>(
|
||||
Future<T> Function(DHTRandomReadWrite) closure) async {
|
||||
Future<T> Function(DHTShortArrayWriteOperations) closure) async {
|
||||
if (!isOpen) {
|
||||
throw StateError('short array is not open"');
|
||||
}
|
||||
@ -221,7 +222,7 @@ class DHTShortArray implements DHTDeleteable<DHTShortArray, DHTShortArray> {
|
||||
/// succeeded, returning false will trigger another eventual consistency
|
||||
/// attempt.
|
||||
Future<void> operateWriteEventual(
|
||||
Future<bool> Function(DHTRandomReadWrite) closure,
|
||||
Future<bool> Function(DHTShortArrayWriteOperations) closure,
|
||||
{Duration? timeout}) async {
|
||||
if (!isOpen) {
|
||||
throw StateError('short array is not open"');
|
||||
|
@ -91,19 +91,20 @@ class DHTShortArrayCubit<T> extends Cubit<DHTShortArrayBusyState<T>>
|
||||
await super.close();
|
||||
}
|
||||
|
||||
Future<R> operate<R>(Future<R> Function(DHTRandomRead) closure) async {
|
||||
Future<R> operate<R>(
|
||||
Future<R> Function(DHTShortArrayReadOperations) closure) async {
|
||||
await _initWait();
|
||||
return _shortArray.operate(closure);
|
||||
}
|
||||
|
||||
Future<R> operateWrite<R>(
|
||||
Future<R> Function(DHTRandomReadWrite) closure) async {
|
||||
Future<R> Function(DHTShortArrayWriteOperations) closure) async {
|
||||
await _initWait();
|
||||
return _shortArray.operateWrite(closure);
|
||||
}
|
||||
|
||||
Future<void> operateWriteEventual(
|
||||
Future<bool> Function(DHTRandomReadWrite) closure,
|
||||
Future<bool> Function(DHTShortArrayWriteOperations) closure,
|
||||
{Duration? timeout}) async {
|
||||
await _initWait();
|
||||
return _shortArray.operateWriteEventual(closure, timeout: timeout);
|
||||
|
@ -3,7 +3,9 @@ part of 'dht_short_array.dart';
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Reader-only implementation
|
||||
|
||||
class _DHTShortArrayRead implements DHTRandomRead {
|
||||
abstract class DHTShortArrayReadOperations implements DHTRandomRead {}
|
||||
|
||||
class _DHTShortArrayRead implements DHTShortArrayReadOperations {
|
||||
_DHTShortArrayRead._(_DHTShortArrayHead head) : _head = head;
|
||||
|
||||
@override
|
||||
|
@ -3,8 +3,16 @@ part of 'dht_short_array.dart';
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Writer implementation
|
||||
|
||||
abstract class DHTShortArrayWriteOperations
|
||||
implements
|
||||
DHTRandomRead,
|
||||
DHTRandomWrite,
|
||||
DHTInsertRemove,
|
||||
DHTAdd,
|
||||
DHTClear {}
|
||||
|
||||
class _DHTShortArrayWrite extends _DHTShortArrayRead
|
||||
implements DHTRandomReadWrite {
|
||||
implements DHTShortArrayWriteOperations {
|
||||
_DHTShortArrayWrite._(super.head) : super._();
|
||||
|
||||
@override
|
||||
|
@ -0,0 +1,41 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
import '../../../veilid_support.dart';
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Add
|
||||
abstract class DHTAdd {
|
||||
/// Try to add an item to the DHT container.
|
||||
/// Return true if the element was successfully added, and false if the state
|
||||
/// changed before the element could be added or a newer value was found on
|
||||
/// the network.
|
||||
/// Throws a StateError if the container exceeds its maximum size.
|
||||
Future<bool> tryAddItem(Uint8List value);
|
||||
|
||||
/// Try to add a list of items to the DHT container.
|
||||
/// Return true if the elements were successfully added, and false if the
|
||||
/// state changed before the element could be added or a newer value was found
|
||||
/// on the network.
|
||||
/// Throws a StateError if the container exceeds its maximum size.
|
||||
Future<bool> tryAddItems(List<Uint8List> values);
|
||||
}
|
||||
|
||||
extension DHTAddExt on DHTAdd {
|
||||
/// Convenience function:
|
||||
/// Like tryAddItem but also encodes the input value as JSON and parses the
|
||||
/// returned element as JSON
|
||||
Future<bool> tryAppendItemJson<T>(
|
||||
T newValue,
|
||||
) =>
|
||||
tryAddItem(jsonEncodeBytes(newValue));
|
||||
|
||||
/// Convenience function:
|
||||
/// Like tryAddItem but also encodes the input value as a protobuf object
|
||||
/// and parses the returned element as a protobuf object
|
||||
Future<bool> tryAddItemProtobuf<T extends GeneratedMessage>(
|
||||
T newValue,
|
||||
) =>
|
||||
tryAddItem(newValue.writeToBuffer());
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
import '../../../veilid_support.dart';
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Append/truncate interface
|
||||
abstract class DHTAppendTruncate {
|
||||
/// Try to add an item to the end of the DHT data structure.
|
||||
/// Return true if the element was successfully added, and false if the state
|
||||
/// changed before the element could be added or a newer value was found on
|
||||
/// the network.
|
||||
/// This may throw an exception if the number elements added exceeds limits.
|
||||
Future<bool> tryAppendItem(Uint8List value);
|
||||
|
||||
/// Try to add a list of items to the end of the DHT data structure.
|
||||
/// Return true if the elements were successfully added, and false if the
|
||||
/// state changed before the element could be added or a newer value was found
|
||||
/// on the network.
|
||||
/// This may throw an exception if the number elements added exceeds limits.
|
||||
Future<bool> tryAppendItems(List<Uint8List> values);
|
||||
|
||||
/// Try to remove a number of items from the head of the DHT data structure.
|
||||
/// Throws StateError if count < 0
|
||||
Future<void> truncate(int count);
|
||||
|
||||
/// Remove all items in the DHT data structure.
|
||||
Future<void> clear();
|
||||
}
|
||||
|
||||
abstract class DHTAppendTruncateRandomRead
|
||||
implements DHTAppendTruncate, DHTRandomRead {}
|
||||
|
||||
extension DHTAppendTruncateExt on DHTAppendTruncate {
|
||||
/// Convenience function:
|
||||
/// Like tryAppendItem but also encodes the input value as JSON and parses the
|
||||
/// returned element as JSON
|
||||
Future<bool> tryAppendItemJson<T>(
|
||||
T newValue,
|
||||
) =>
|
||||
tryAppendItem(jsonEncodeBytes(newValue));
|
||||
|
||||
/// Convenience function:
|
||||
/// Like tryAppendItem but also encodes the input value as a protobuf object
|
||||
/// and parses the returned element as a protobuf object
|
||||
Future<bool> tryAppendItemProtobuf<T extends GeneratedMessage>(
|
||||
T newValue,
|
||||
) =>
|
||||
tryAppendItem(newValue.writeToBuffer());
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Clear interface
|
||||
// ignore: one_member_abstracts
|
||||
abstract class DHTClear {
|
||||
/// Remove all items in the DHT container.
|
||||
Future<void> clear();
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
import '../../../veilid_support.dart';
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Insert/Remove interface
|
||||
abstract class DHTInsertRemove {
|
||||
/// Try to insert an item as position 'pos' of the DHT container.
|
||||
/// Return true if the element was successfully inserted, and false if the
|
||||
/// state changed before the element could be inserted or a newer value was
|
||||
/// found on the network.
|
||||
/// Throws an IndexError if the position removed exceeds the length of
|
||||
/// the container.
|
||||
/// Throws a StateError if the container exceeds its maximum size.
|
||||
Future<bool> tryInsertItem(int pos, Uint8List value);
|
||||
|
||||
/// Try to insert items at position 'pos' of the DHT container.
|
||||
/// Return true if the elements were successfully inserted, and false if the
|
||||
/// state changed before the elements could be inserted or a newer value was
|
||||
/// found on the network.
|
||||
/// Throws an IndexError if the position removed exceeds the length of
|
||||
/// the container.
|
||||
/// Throws a StateError if the container exceeds its maximum size.
|
||||
Future<bool> tryInsertItems(int pos, List<Uint8List> values);
|
||||
|
||||
/// Swap items at position 'aPos' and 'bPos' in the DHTArray.
|
||||
/// Throws an IndexError if either of the positions swapped exceeds the length
|
||||
/// of the container
|
||||
Future<void> swapItem(int aPos, int bPos);
|
||||
|
||||
/// Remove an item at position 'pos' in the DHT container.
|
||||
/// If the remove was successful this returns:
|
||||
/// * outValue will return the prior contents of the element
|
||||
/// Throws an IndexError if the position removed exceeds the length of
|
||||
/// the container.
|
||||
Future<void> removeItem(int pos, {Output<Uint8List>? output});
|
||||
}
|
||||
|
||||
extension DHTInsertRemoveExt on DHTInsertRemove {
|
||||
/// Convenience function:
|
||||
/// Like removeItem but also parses the returned element as JSON
|
||||
Future<void> removeItemJson<T>(T Function(dynamic) fromJson, int pos,
|
||||
{Output<T>? output}) async {
|
||||
final outValueBytes = output == null ? null : Output<Uint8List>();
|
||||
await removeItem(pos, output: outValueBytes);
|
||||
output.mapSave(outValueBytes, (b) => jsonDecodeBytes(fromJson, b));
|
||||
}
|
||||
|
||||
/// Convenience function:
|
||||
/// Like removeItem but also parses the returned element as JSON
|
||||
Future<void> removeItemProtobuf<T extends GeneratedMessage>(
|
||||
T Function(List<int>) fromBuffer, int pos,
|
||||
{Output<T>? output}) async {
|
||||
final outValueBytes = output == null ? null : Output<Uint8List>();
|
||||
await removeItem(pos, output: outValueBytes);
|
||||
output.mapSave(outValueBytes, fromBuffer);
|
||||
}
|
||||
}
|
@ -7,22 +7,21 @@ import '../../../veilid_support.dart';
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Reader interface
|
||||
abstract class DHTRandomRead {
|
||||
/// Returns the number of elements in the DHTArray
|
||||
/// This number will be >= 0 and <= DHTShortArray.maxElements (256)
|
||||
/// Returns the number of elements in the DHT container
|
||||
int get length;
|
||||
|
||||
/// Return the item at position 'pos' in the DHTArray. If 'forceRefresh'
|
||||
/// Return the item at position 'pos' in the DHT container. If 'forceRefresh'
|
||||
/// is specified, the network will always be checked for newer values
|
||||
/// rather than returning the existing locally stored copy of the elements.
|
||||
/// * 'pos' must be >= 0 and < 'length'
|
||||
/// Throws an IndexError if the 'pos' is not within the length
|
||||
/// of the container.
|
||||
Future<Uint8List?> getItem(int pos, {bool forceRefresh = false});
|
||||
|
||||
/// Return a list of a range of items in the DHTArray. If 'forceRefresh'
|
||||
/// is specified, the network will always be checked for newer values
|
||||
/// rather than returning the existing locally stored copy of the elements.
|
||||
/// * 'start' must be >= 0
|
||||
/// * 'len' must be >= 0 and <= DHTShortArray.maxElements (256) and defaults
|
||||
/// to the maximum length
|
||||
/// Throws an IndexError if either 'start' or '(start+length)' is not within
|
||||
/// the length of the container.
|
||||
Future<List<Uint8List>?> getItemRange(int start,
|
||||
{int? length, bool forceRefresh = false});
|
||||
|
||||
|
@ -6,8 +6,9 @@ import '../../../veilid_support.dart';
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Writer interface
|
||||
// ignore: one_member_abstracts
|
||||
abstract class DHTRandomWrite {
|
||||
/// Try to set an item at position 'pos' of the DHTArray.
|
||||
/// Try to set an item at position 'pos' of the DHT container.
|
||||
/// If the set was successful this returns:
|
||||
/// * A boolean true
|
||||
/// * outValue will return the prior contents of the element,
|
||||
@ -18,55 +19,10 @@ abstract class DHTRandomWrite {
|
||||
/// * outValue will return the newer value of the element,
|
||||
/// or null if the head record changed.
|
||||
///
|
||||
/// This may throw an exception if the position exceeds the built-in limit of
|
||||
/// 'maxElements = 256' entries.
|
||||
/// Throws an IndexError if the position is not within the length
|
||||
/// of the container.
|
||||
Future<bool> tryWriteItem(int pos, Uint8List newValue,
|
||||
{Output<Uint8List>? output});
|
||||
|
||||
/// Try to add an item to the end of the DHTArray. Return true if the
|
||||
/// element was successfully added, and false if the state changed before
|
||||
/// the element could be added or a newer value was found on the network.
|
||||
/// This may throw an exception if the number elements added exceeds the
|
||||
/// built-in limit of 'maxElements = 256' entries.
|
||||
Future<bool> tryAddItem(Uint8List value);
|
||||
|
||||
/// Try to add a list of items to the end of the DHTArray. Return true if the
|
||||
/// elements were successfully added, and false if the state changed before
|
||||
/// the elements could be added or a newer value was found on the network.
|
||||
/// This may throw an exception if the number elements added exceeds the
|
||||
/// built-in limit of 'maxElements = 256' entries.
|
||||
Future<bool> tryAddItems(List<Uint8List> values);
|
||||
|
||||
/// Try to insert an item as position 'pos' of the DHTArray.
|
||||
/// Return true if the element was successfully inserted, and false if the
|
||||
/// state changed before the element could be inserted or a newer value was
|
||||
/// found on the network.
|
||||
/// This may throw an exception if the number elements added exceeds the
|
||||
/// built-in limit of 'maxElements = 256' entries.
|
||||
Future<bool> tryInsertItem(int pos, Uint8List value);
|
||||
|
||||
/// Try to insert items at position 'pos' of the DHTArray.
|
||||
/// Return true if the elements were successfully inserted, and false if the
|
||||
/// state changed before the elements could be inserted or a newer value was
|
||||
/// found on the network.
|
||||
/// This may throw an exception if the number elements added exceeds the
|
||||
/// built-in limit of 'maxElements = 256' entries.
|
||||
Future<bool> tryInsertItems(int pos, List<Uint8List> values);
|
||||
|
||||
/// Swap items at position 'aPos' and 'bPos' in the DHTArray.
|
||||
/// Throws IndexError if either of the positions swapped exceed
|
||||
/// the length of the list
|
||||
Future<void> swapItem(int aPos, int bPos);
|
||||
|
||||
/// Remove an item at position 'pos' in the DHTArray.
|
||||
/// If the remove was successful this returns:
|
||||
/// * outValue will return the prior contents of the element
|
||||
/// Throws IndexError if the position removed exceeds the length of
|
||||
/// the list.
|
||||
Future<void> removeItem(int pos, {Output<Uint8List>? output});
|
||||
|
||||
/// Remove all items in the DHTShortArray.
|
||||
Future<void> clear();
|
||||
}
|
||||
|
||||
extension DHTRandomWriteExt on DHTRandomWrite {
|
||||
@ -95,25 +51,4 @@ extension DHTRandomWriteExt on DHTRandomWrite {
|
||||
output.mapSave(outValueBytes, fromBuffer);
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Convenience function:
|
||||
/// Like removeItem but also parses the returned element as JSON
|
||||
Future<void> removeItemJson<T>(T Function(dynamic) fromJson, int pos,
|
||||
{Output<T>? output}) async {
|
||||
final outValueBytes = output == null ? null : Output<Uint8List>();
|
||||
await removeItem(pos, output: outValueBytes);
|
||||
output.mapSave(outValueBytes, (b) => jsonDecodeBytes(fromJson, b));
|
||||
}
|
||||
|
||||
/// Convenience function:
|
||||
/// Like removeItem but also parses the returned element as JSON
|
||||
Future<void> removeItemProtobuf<T extends GeneratedMessage>(
|
||||
T Function(List<int>) fromBuffer, int pos,
|
||||
{Output<T>? output}) async {
|
||||
final outValueBytes = output == null ? null : Output<Uint8List>();
|
||||
await removeItem(pos, output: outValueBytes);
|
||||
output.mapSave(outValueBytes, fromBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class DHTRandomReadWrite implements DHTRandomRead, DHTRandomWrite {}
|
||||
|
@ -0,0 +1,8 @@
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Truncate interface
|
||||
// ignore: one_member_abstracts
|
||||
abstract class DHTTruncate {
|
||||
/// Remove items from the DHT container to shrink its size to 'newLength'
|
||||
/// Throws StateError if newLength < 0
|
||||
Future<void> truncate(int newLength);
|
||||
}
|
@ -1,4 +1,8 @@
|
||||
export 'dht_append.dart';
|
||||
export 'dht_clear.dart';
|
||||
export 'dht_closeable.dart';
|
||||
export 'dht_insert_remove.dart';
|
||||
export 'dht_random_read.dart';
|
||||
export 'dht_random_write.dart';
|
||||
export 'dht_truncate.dart';
|
||||
export 'exceptions.dart';
|
||||
|
@ -130,7 +130,7 @@ extension IdentityMasterExtension on IdentityMaster {
|
||||
// Read the identity key to get the account keys
|
||||
final pool = DHTRecordPool.instance;
|
||||
|
||||
final identityRecordCrypto = await DHTRecordCryptoPrivate.fromSecret(
|
||||
final identityRecordCrypto = await VeilidCryptoPrivate.fromSecret(
|
||||
identityRecordKey.kind, identitySecret);
|
||||
|
||||
late final List<AccountRecordInfo> accountRecordInfo;
|
||||
@ -234,7 +234,7 @@ class IdentityMasterWithSecrets {
|
||||
return (await pool.createRecord(
|
||||
debugName:
|
||||
'IdentityMasterWithSecrets::create::IdentityMasterRecord',
|
||||
crypto: const DHTRecordCryptoPublic()))
|
||||
crypto: const VeilidCryptoPublic()))
|
||||
.deleteScope((masterRec) async {
|
||||
veilidLoggy.debug('Creating identity record');
|
||||
// Identity record is private
|
||||
|
517
packages/veilid_support/lib/src/table_db_array.dart
Normal file
517
packages/veilid_support/lib/src/table_db_array.dart
Normal file
@ -0,0 +1,517 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:async_tools/async_tools.dart';
|
||||
import 'package:charcode/charcode.dart';
|
||||
|
||||
import '../veilid_support.dart';
|
||||
|
||||
class TableDBArray {
|
||||
TableDBArray({
|
||||
required String table,
|
||||
required VeilidCrypto crypto,
|
||||
}) : _table = table,
|
||||
_crypto = crypto {
|
||||
_initWait.add(_init);
|
||||
}
|
||||
|
||||
Future<void> _init() async {
|
||||
// Load the array details
|
||||
await _mutex.protect(() async {
|
||||
_tableDB = await Veilid.instance.openTableDB(_table, 1);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> close({bool delete = false}) async {
|
||||
// Ensure the init finished
|
||||
await _initWait();
|
||||
|
||||
await _mutex.acquire();
|
||||
|
||||
await _changeStream.close();
|
||||
_tableDB.close();
|
||||
|
||||
if (delete) {
|
||||
await Veilid.instance.deleteTableDB(_table);
|
||||
}
|
||||
}
|
||||
|
||||
Future<StreamSubscription<void>> listen(void Function() onChanged) async =>
|
||||
_changeStream.stream.listen((_) => onChanged());
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Public interface
|
||||
|
||||
int get length => _length;
|
||||
|
||||
Future<void> add(Uint8List value) async {
|
||||
await _initWait();
|
||||
return _writeTransaction((t) async => _addInner(t, value));
|
||||
}
|
||||
|
||||
Future<void> addAll(List<Uint8List> values) async {
|
||||
await _initWait();
|
||||
return _writeTransaction((t) async => _addAllInner(t, values));
|
||||
}
|
||||
|
||||
Future<void> insert(int pos, Uint8List value) async {
|
||||
await _initWait();
|
||||
return _writeTransaction((t) async => _insertInner(t, pos, value));
|
||||
}
|
||||
|
||||
Future<void> insertAll(int pos, List<Uint8List> values) async {
|
||||
await _initWait();
|
||||
return _writeTransaction((t) async => _insertAllInner(t, pos, values));
|
||||
}
|
||||
|
||||
Future<Uint8List> get(int pos) async {
|
||||
await _initWait();
|
||||
return _mutex.protect(() async => _getInner(pos));
|
||||
}
|
||||
|
||||
Future<List<Uint8List>> getAll(int start, int length) async {
|
||||
await _initWait();
|
||||
return _mutex.protect(() async => _getAllInner(start, length));
|
||||
}
|
||||
|
||||
Future<void> remove(int pos, {Output<Uint8List>? out}) async {
|
||||
await _initWait();
|
||||
return _writeTransaction((t) async => _removeInner(t, pos, out: out));
|
||||
}
|
||||
|
||||
Future<void> removeRange(int start, int length,
|
||||
{Output<List<Uint8List>>? out}) async {
|
||||
await _initWait();
|
||||
return _writeTransaction(
|
||||
(t) async => _removeRangeInner(t, start, length, out: out));
|
||||
}
|
||||
|
||||
Future<void> clear() async {
|
||||
await _initWait();
|
||||
return _writeTransaction((t) async {
|
||||
final keys = await _tableDB.getKeys(0);
|
||||
for (final key in keys) {
|
||||
await t.delete(0, key);
|
||||
}
|
||||
_length = 0;
|
||||
_nextFree = 0;
|
||||
_dirtyChunks.clear();
|
||||
_chunkCache.clear();
|
||||
});
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Inner interface
|
||||
|
||||
Future<void> _addInner(VeilidTableDBTransaction t, Uint8List value) async {
|
||||
// Allocate an entry to store the value
|
||||
final entry = await _allocateEntry();
|
||||
await _storeEntry(t, entry, value);
|
||||
|
||||
// Put the entry in the index
|
||||
final pos = _length;
|
||||
_length++;
|
||||
await _setIndexEntry(pos, entry);
|
||||
}
|
||||
|
||||
Future<void> _addAllInner(
|
||||
VeilidTableDBTransaction t, List<Uint8List> values) async {
|
||||
var pos = _length;
|
||||
_length += values.length;
|
||||
for (final value in values) {
|
||||
// Allocate an entry to store the value
|
||||
final entry = await _allocateEntry();
|
||||
await _storeEntry(t, entry, value);
|
||||
|
||||
// Put the entry in the index
|
||||
await _setIndexEntry(pos, entry);
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _insertInner(
|
||||
VeilidTableDBTransaction t, int pos, Uint8List value) async {
|
||||
if (pos == _length) {
|
||||
return _addInner(t, value);
|
||||
}
|
||||
if (pos < 0 || pos >= _length) {
|
||||
throw IndexError.withLength(pos, _length);
|
||||
}
|
||||
// Allocate an entry to store the value
|
||||
final entry = await _allocateEntry();
|
||||
await _storeEntry(t, entry, value);
|
||||
|
||||
// Put the entry in the index
|
||||
await _insertIndexEntry(pos);
|
||||
await _setIndexEntry(pos, entry);
|
||||
}
|
||||
|
||||
Future<void> _insertAllInner(
|
||||
VeilidTableDBTransaction t, int pos, List<Uint8List> values) async {
|
||||
if (pos == _length) {
|
||||
return _addAllInner(t, values);
|
||||
}
|
||||
if (pos < 0 || pos >= _length) {
|
||||
throw IndexError.withLength(pos, _length);
|
||||
}
|
||||
await _insertIndexEntries(pos, values.length);
|
||||
for (final value in values) {
|
||||
// Allocate an entry to store the value
|
||||
final entry = await _allocateEntry();
|
||||
await _storeEntry(t, entry, value);
|
||||
|
||||
// Put the entry in the index
|
||||
await _setIndexEntry(pos, entry);
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Uint8List> _getInner(int pos) async {
|
||||
if (pos < 0 || pos >= _length) {
|
||||
throw IndexError.withLength(pos, _length);
|
||||
}
|
||||
final entry = await _getIndexEntry(pos);
|
||||
return (await _loadEntry(entry))!;
|
||||
}
|
||||
|
||||
Future<List<Uint8List>> _getAllInner(int start, int length) async {
|
||||
if (length < 0) {
|
||||
throw StateError('length should not be negative');
|
||||
}
|
||||
if (start < 0 || start >= _length) {
|
||||
throw IndexError.withLength(start, _length);
|
||||
}
|
||||
if ((start + length) > _length) {
|
||||
throw IndexError.withLength(start + length, _length);
|
||||
}
|
||||
|
||||
final out = <Uint8List>[];
|
||||
for (var pos = start; pos < (start + length); pos++) {
|
||||
final entry = await _getIndexEntry(pos);
|
||||
final value = (await _loadEntry(entry))!;
|
||||
out.add(value);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Future<void> _removeInner(VeilidTableDBTransaction t, int pos,
|
||||
{Output<Uint8List>? out}) async {
|
||||
if (pos < 0 || pos >= _length) {
|
||||
throw IndexError.withLength(pos, _length);
|
||||
}
|
||||
|
||||
final entry = await _getIndexEntry(pos);
|
||||
if (out != null) {
|
||||
final value = (await _loadEntry(entry))!;
|
||||
out.save(value);
|
||||
}
|
||||
|
||||
await _freeEntry(t, entry);
|
||||
await _removeIndexEntry(pos);
|
||||
}
|
||||
|
||||
Future<void> _removeRangeInner(
|
||||
VeilidTableDBTransaction t, int start, int length,
|
||||
{Output<List<Uint8List>>? out}) async {
|
||||
if (length < 0) {
|
||||
throw StateError('length should not be negative');
|
||||
}
|
||||
if (start < 0 || start >= _length) {
|
||||
throw IndexError.withLength(start, _length);
|
||||
}
|
||||
if ((start + length) > _length) {
|
||||
throw IndexError.withLength(start + length, _length);
|
||||
}
|
||||
|
||||
final outList = <Uint8List>[];
|
||||
for (var pos = start; pos < (start + length); pos++) {
|
||||
final entry = await _getIndexEntry(pos);
|
||||
if (out != null) {
|
||||
final value = (await _loadEntry(entry))!;
|
||||
outList.add(value);
|
||||
}
|
||||
await _freeEntry(t, entry);
|
||||
}
|
||||
if (out != null) {
|
||||
out.save(outList);
|
||||
}
|
||||
|
||||
await _removeIndexEntries(start, length);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Private implementation
|
||||
|
||||
static final Uint8List _headKey = Uint8List.fromList([$_, $H, $E, $A, $D]);
|
||||
static Uint8List _entryKey(int k) =>
|
||||
(ByteData(4)..setUint32(0, k)).buffer.asUint8List();
|
||||
static Uint8List _chunkKey(int n) =>
|
||||
(ByteData(2)..setUint16(0, n)).buffer.asUint8List();
|
||||
|
||||
Future<T> _writeTransaction<T>(
|
||||
Future<T> Function(VeilidTableDBTransaction) closure) async =>
|
||||
_mutex.protect(() async {
|
||||
final _oldLength = _length;
|
||||
final _oldNextFree = _nextFree;
|
||||
try {
|
||||
final out = await transactionScope(_tableDB, (t) async {
|
||||
final out = closure(t);
|
||||
await _saveHead(t);
|
||||
await _flushDirtyChunks(t);
|
||||
return out;
|
||||
});
|
||||
|
||||
return out;
|
||||
} on Exception {
|
||||
// restore head
|
||||
_length = _oldLength;
|
||||
_nextFree = _oldNextFree;
|
||||
// invalidate caches because they could have been written to
|
||||
_chunkCache.clear();
|
||||
_dirtyChunks.clear();
|
||||
// propagate exception
|
||||
rethrow;
|
||||
}
|
||||
});
|
||||
|
||||
Future<void> _storeEntry(
|
||||
VeilidTableDBTransaction t, int entry, Uint8List value) async =>
|
||||
t.store(0, _entryKey(entry), await _crypto.encrypt(value));
|
||||
|
||||
Future<Uint8List?> _loadEntry(int entry) async {
|
||||
final encryptedValue = await _tableDB.load(0, _entryKey(entry));
|
||||
return (encryptedValue == null) ? null : _crypto.decrypt(encryptedValue);
|
||||
}
|
||||
|
||||
Future<int> _getIndexEntry(int pos) async {
|
||||
if (pos < 0 || pos >= _length) {
|
||||
throw IndexError.withLength(pos, _length);
|
||||
}
|
||||
final chunkNumber = pos ~/ _indexStride;
|
||||
final chunkOffset = pos % _indexStride;
|
||||
|
||||
final chunk = await _loadIndexChunk(chunkNumber);
|
||||
|
||||
return chunk.buffer.asByteData().getUint32(chunkOffset * 4);
|
||||
}
|
||||
|
||||
Future<void> _setIndexEntry(int pos, int entry) async {
|
||||
if (pos < 0 || pos >= _length) {
|
||||
throw IndexError.withLength(pos, _length);
|
||||
}
|
||||
|
||||
final chunkNumber = pos ~/ _indexStride;
|
||||
final chunkOffset = pos % _indexStride;
|
||||
|
||||
final chunk = await _loadIndexChunk(chunkNumber);
|
||||
chunk.buffer.asByteData().setUint32(chunkOffset * 4, entry);
|
||||
|
||||
_dirtyChunks[chunkNumber] = chunk;
|
||||
}
|
||||
|
||||
Future<void> _insertIndexEntry(int pos) async => _insertIndexEntries(pos, 1);
|
||||
|
||||
Future<void> _insertIndexEntries(int start, int length) async {
|
||||
if (length == 0) {
|
||||
return;
|
||||
}
|
||||
if (length < 0) {
|
||||
throw StateError('length should not be negative');
|
||||
}
|
||||
if (start < 0 || start >= _length) {
|
||||
throw IndexError.withLength(start, _length);
|
||||
}
|
||||
final end = start + length - 1;
|
||||
|
||||
// Slide everything over in reverse
|
||||
final toCopyTotal = _length - start;
|
||||
var dest = end + toCopyTotal;
|
||||
var src = _length - 1;
|
||||
|
||||
(int, Uint8List)? lastSrcChunk;
|
||||
(int, Uint8List)? lastDestChunk;
|
||||
while (src >= start) {
|
||||
final srcChunkNumber = src ~/ _indexStride;
|
||||
final srcIndex = src % _indexStride;
|
||||
final srcLength = srcIndex + 1;
|
||||
|
||||
final srcChunk =
|
||||
(lastSrcChunk != null && (lastSrcChunk.$1 == srcChunkNumber))
|
||||
? lastSrcChunk.$2
|
||||
: await _loadIndexChunk(srcChunkNumber);
|
||||
lastSrcChunk = (srcChunkNumber, srcChunk);
|
||||
|
||||
final destChunkNumber = dest ~/ _indexStride;
|
||||
final destIndex = dest % _indexStride;
|
||||
final destLength = destIndex + 1;
|
||||
|
||||
final destChunk =
|
||||
(lastDestChunk != null && (lastDestChunk.$1 == destChunkNumber))
|
||||
? lastDestChunk.$2
|
||||
: await _loadIndexChunk(destChunkNumber);
|
||||
lastDestChunk = (destChunkNumber, destChunk);
|
||||
|
||||
final toCopy = min(srcLength, destLength);
|
||||
destChunk.setRange((destIndex - (toCopy - 1)) * 4, (destIndex + 1) * 4,
|
||||
srcChunk, (srcIndex - (toCopy - 1)) * 4);
|
||||
|
||||
dest -= toCopy;
|
||||
src -= toCopy;
|
||||
}
|
||||
|
||||
// Then add to length
|
||||
_length += length;
|
||||
}
|
||||
|
||||
Future<void> _removeIndexEntry(int pos) async => _removeIndexEntries(pos, 1);
|
||||
|
||||
Future<void> _removeIndexEntries(int start, int length) async {
|
||||
if (length == 0) {
|
||||
return;
|
||||
}
|
||||
if (length < 0) {
|
||||
throw StateError('length should not be negative');
|
||||
}
|
||||
if (start < 0 || start >= _length) {
|
||||
throw IndexError.withLength(start, _length);
|
||||
}
|
||||
final end = start + length - 1;
|
||||
if (end < 0 || end >= _length) {
|
||||
throw IndexError.withLength(end, _length);
|
||||
}
|
||||
|
||||
// Slide everything over
|
||||
var dest = start;
|
||||
var src = end + 1;
|
||||
(int, Uint8List)? lastSrcChunk;
|
||||
(int, Uint8List)? lastDestChunk;
|
||||
while (src < _length) {
|
||||
final srcChunkNumber = src ~/ _indexStride;
|
||||
final srcIndex = src % _indexStride;
|
||||
final srcLength = _indexStride - srcIndex;
|
||||
|
||||
final srcChunk =
|
||||
(lastSrcChunk != null && (lastSrcChunk.$1 == srcChunkNumber))
|
||||
? lastSrcChunk.$2
|
||||
: await _loadIndexChunk(srcChunkNumber);
|
||||
lastSrcChunk = (srcChunkNumber, srcChunk);
|
||||
|
||||
final destChunkNumber = dest ~/ _indexStride;
|
||||
final destIndex = dest % _indexStride;
|
||||
final destLength = _indexStride - destIndex;
|
||||
|
||||
final destChunk =
|
||||
(lastDestChunk != null && (lastDestChunk.$1 == destChunkNumber))
|
||||
? lastDestChunk.$2
|
||||
: await _loadIndexChunk(destChunkNumber);
|
||||
lastDestChunk = (destChunkNumber, destChunk);
|
||||
|
||||
final toCopy = min(srcLength, destLength);
|
||||
destChunk.setRange(
|
||||
destIndex * 4, (destIndex + toCopy) * 4, srcChunk, srcIndex * 4);
|
||||
|
||||
dest += toCopy;
|
||||
src += toCopy;
|
||||
}
|
||||
|
||||
// Then truncate
|
||||
_length -= length;
|
||||
}
|
||||
|
||||
Future<Uint8List> _loadIndexChunk(int chunkNumber) async {
|
||||
// Get it from the dirty chunks if we have it
|
||||
final dirtyChunk = _dirtyChunks[chunkNumber];
|
||||
if (dirtyChunk != null) {
|
||||
return dirtyChunk;
|
||||
}
|
||||
|
||||
// Get from cache if we have it
|
||||
for (var i = 0; i < _chunkCache.length; i++) {
|
||||
if (_chunkCache[i].$1 == chunkNumber) {
|
||||
// Touch the element
|
||||
final x = _chunkCache.removeAt(i);
|
||||
_chunkCache.add(x);
|
||||
// Return the chunk for this position
|
||||
return x.$2;
|
||||
}
|
||||
}
|
||||
|
||||
// Get chunk from disk
|
||||
var chunk = await _tableDB.load(0, _chunkKey(chunkNumber));
|
||||
chunk ??= Uint8List(_indexStride * 4);
|
||||
|
||||
// Cache the chunk
|
||||
_chunkCache.add((chunkNumber, chunk));
|
||||
if (_chunkCache.length > _chunkCacheLength) {
|
||||
// Trim the LRU cache
|
||||
final (_, _) = _chunkCache.removeAt(0);
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
Future<void> _flushDirtyChunks(VeilidTableDBTransaction t) async {
|
||||
for (final ec in _dirtyChunks.entries) {
|
||||
await _tableDB.store(0, _chunkKey(ec.key), ec.value);
|
||||
}
|
||||
_dirtyChunks.clear();
|
||||
}
|
||||
|
||||
Future<void> _loadHead() async {
|
||||
assert(_mutex.isLocked, 'should be locked');
|
||||
final headBytes = await _tableDB.load(0, _headKey);
|
||||
if (headBytes == null) {
|
||||
_length = 0;
|
||||
_nextFree = 0;
|
||||
} else {
|
||||
final b = headBytes.buffer.asByteData();
|
||||
_length = b.getUint32(0);
|
||||
_nextFree = b.getUint32(4);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _saveHead(VeilidTableDBTransaction t) async {
|
||||
assert(_mutex.isLocked, 'should be locked');
|
||||
final b = ByteData(8)
|
||||
..setUint32(0, _length)
|
||||
..setUint32(4, _nextFree);
|
||||
await t.store(0, _headKey, b.buffer.asUint8List());
|
||||
}
|
||||
|
||||
Future<int> _allocateEntry() async {
|
||||
assert(_mutex.isLocked, 'should be locked');
|
||||
if (_nextFree == 0) {
|
||||
return _length;
|
||||
}
|
||||
// pop endogenous free list
|
||||
final free = _nextFree;
|
||||
final nextFreeBytes = await _tableDB.load(0, _entryKey(free));
|
||||
_nextFree = nextFreeBytes!.buffer.asByteData().getUint8(0);
|
||||
return free;
|
||||
}
|
||||
|
||||
Future<void> _freeEntry(VeilidTableDBTransaction t, int entry) async {
|
||||
assert(_mutex.isLocked, 'should be locked');
|
||||
// push endogenous free list
|
||||
final b = ByteData(4)..setUint32(0, _nextFree);
|
||||
await t.store(0, _entryKey(entry), b.buffer.asUint8List());
|
||||
_nextFree = entry;
|
||||
}
|
||||
|
||||
final String _table;
|
||||
late final VeilidTableDB _tableDB;
|
||||
final VeilidCrypto _crypto;
|
||||
final WaitSet<void> _initWait = WaitSet();
|
||||
final Mutex _mutex = Mutex();
|
||||
|
||||
// Head state
|
||||
int _length = 0;
|
||||
int _nextFree = 0;
|
||||
static const int _indexStride = 16384;
|
||||
final List<(int, Uint8List)> _chunkCache = [];
|
||||
final Map<int, Uint8List> _dirtyChunks = {};
|
||||
static const int _chunkCacheLength = 3;
|
||||
|
||||
final StreamController<void> _changeStream = StreamController.broadcast();
|
||||
}
|
52
packages/veilid_support/lib/src/veilid_crypto.dart
Normal file
52
packages/veilid_support/lib/src/veilid_crypto.dart
Normal file
@ -0,0 +1,52 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import '../../../veilid_support.dart';
|
||||
|
||||
abstract class VeilidCrypto {
|
||||
Future<Uint8List> encrypt(Uint8List data);
|
||||
Future<Uint8List> decrypt(Uint8List data);
|
||||
}
|
||||
|
||||
////////////////////////////////////
|
||||
/// Encrypted for a specific symmetric key
|
||||
class VeilidCryptoPrivate implements VeilidCrypto {
|
||||
VeilidCryptoPrivate._(VeilidCryptoSystem cryptoSystem, SharedSecret secretKey)
|
||||
: _cryptoSystem = cryptoSystem,
|
||||
_secretKey = secretKey;
|
||||
final VeilidCryptoSystem _cryptoSystem;
|
||||
final SharedSecret _secretKey;
|
||||
|
||||
static Future<VeilidCryptoPrivate> fromTypedKeyPair(
|
||||
TypedKeyPair typedKeyPair) async {
|
||||
final cryptoSystem =
|
||||
await Veilid.instance.getCryptoSystem(typedKeyPair.kind);
|
||||
final secretKey = typedKeyPair.secret;
|
||||
return VeilidCryptoPrivate._(cryptoSystem, secretKey);
|
||||
}
|
||||
|
||||
static Future<VeilidCryptoPrivate> fromSecret(
|
||||
CryptoKind kind, SharedSecret secretKey) async {
|
||||
final cryptoSystem = await Veilid.instance.getCryptoSystem(kind);
|
||||
return VeilidCryptoPrivate._(cryptoSystem, secretKey);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List> encrypt(Uint8List data) =>
|
||||
_cryptoSystem.encryptNoAuthWithNonce(data, _secretKey);
|
||||
|
||||
@override
|
||||
Future<Uint8List> decrypt(Uint8List data) =>
|
||||
_cryptoSystem.decryptNoAuthWithNonce(data, _secretKey);
|
||||
}
|
||||
|
||||
////////////////////////////////////
|
||||
/// No encryption
|
||||
class VeilidCryptoPublic implements VeilidCrypto {
|
||||
const VeilidCryptoPublic();
|
||||
|
||||
@override
|
||||
Future<Uint8List> encrypt(Uint8List data) async => data;
|
||||
|
||||
@override
|
||||
Future<Uint8List> decrypt(Uint8List data) async => data;
|
||||
}
|
@ -14,4 +14,6 @@ export 'src/output.dart';
|
||||
export 'src/persistent_queue.dart';
|
||||
export 'src/protobuf_tools.dart';
|
||||
export 'src/table_db.dart';
|
||||
export 'src/table_db_array.dart';
|
||||
export 'src/veilid_crypto.dart';
|
||||
export 'src/veilid_log.dart' hide veilidLoggy;
|
||||
|
@ -146,7 +146,7 @@ packages:
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: charcode
|
||||
sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306
|
||||
|
@ -10,6 +10,7 @@ dependencies:
|
||||
async_tools: ^0.1.1
|
||||
bloc: ^8.1.4
|
||||
bloc_advanced_tools: ^0.1.1
|
||||
charcode: ^1.3.1
|
||||
collection: ^1.18.0
|
||||
equatable: ^2.0.5
|
||||
fast_immutable_collections: ^10.2.3
|
||||
|
Loading…
x
Reference in New Issue
Block a user