Browse Source

update keystore and add new transfer widget

pull/18/head
Sun 4 years ago
parent
commit
cc675ab0fe
  1. 30
      lib/apps/assistant/page.dart
  2. 8
      lib/apps/chat/models.dart
  3. 3
      lib/apps/wallet/page.dart
  4. 1
      lib/l10n/localizations.dart
  5. 2
      lib/l10n/localizations_en.dart
  6. 2
      lib/l10n/localizations_zh.dart
  7. 346
      lib/widgets/transfer.dart
  8. 89
      src/account.rs
  9. 10
      src/apps/chat/layer.rs
  10. 777
      src/apps/chat/models.rs
  11. 255
      src/apps/chat/models/friend.rs
  12. 366
      src/apps/chat/models/message.rs
  13. 177
      src/apps/chat/models/request.rs
  14. 4
      src/apps/chat/rpc.rs
  15. 17
      src/apps/wallet/rpc.rs
  16. 3
      src/event.rs
  17. 25
      src/group.rs
  18. 1
      src/migrate/account.rs
  19. 2
      src/migrate/chat.rs
  20. 142
      src/utils/crypto.rs

30
lib/apps/assistant/page.dart

@ -9,6 +9,7 @@ import 'package:esse/widgets/emoji.dart'; @@ -9,6 +9,7 @@ import 'package:esse/widgets/emoji.dart';
import 'package:esse/widgets/shadow_dialog.dart';
import 'package:esse/widgets/audio_recorder.dart';
import 'package:esse/widgets/show_contact.dart';
import 'package:esse/widgets/transfer.dart';
import 'package:esse/global.dart';
import 'package:esse/options.dart';
@ -149,6 +150,29 @@ class _AssistantDetailState extends State<AssistantDetail> { @@ -149,6 +150,29 @@ class _AssistantDetailState extends State<AssistantDetail> {
);
}
_tokenCallback(String hash, String to, String amount, String name) {
//context.read<AssistantProvider>().create(MessageType.Transfer, "");
setState(() {
textFocus.requestFocus();
emojiShow = false;
sendShow = false;
menuShow = false;
recordShow = false;
});
}
// TEST CODE.
void _sendToken(ColorScheme color, AppLocalizations lang) {
return;
showShadowDialog(
context,
Icons.paid,
lang.transfer,
Transfer(callback: _tokenCallback, to: "0xdac17f958d2ee523a2206206994597c13d831ec7"),
0.0
);
}
@override
Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme;
@ -390,6 +414,12 @@ class _AssistantDetailState extends State<AssistantDetail> { @@ -390,6 +414,12 @@ class _AssistantDetailState extends State<AssistantDetail> {
action: () => _sendContact(color, lang),
bgColor: color.surface,
iconColor: color.primary),
ExtensionButton(
icon: Icons.paid_rounded,
text: lang.transfer,
action: () => _sendToken(color, lang),
bgColor: color.surface,
iconColor: color.primary),
],
),
)

8
lib/apps/chat/models.dart

@ -9,6 +9,7 @@ class Friend { @@ -9,6 +9,7 @@ class Friend {
String gid = '';
String name = '';
String addr = '';
String wallet = '';
String remark = '';
bool isClosed = false;
RelativeTime time = RelativeTime();
@ -26,9 +27,10 @@ class Friend { @@ -26,9 +27,10 @@ class Friend {
this.gid = params[1];
this.addr = params[2];
this.name = params[3];
this.remark = params[4];
this.isClosed = params[5];
this.time = RelativeTime.fromInt(params[6]);
this.wallet = params[4];
this.remark = params[5];
this.isClosed = params[6];
this.time = RelativeTime.fromInt(params[7]);
}
}

3
lib/apps/wallet/page.dart

@ -660,7 +660,8 @@ class _ImportAccount extends StatelessWidget { @@ -660,7 +660,8 @@ class _ImportAccount extends StatelessWidget {
if (secret.length < 32) {
return;
}
rpc.send('wallet-import', [chain.toInt(), secret]);
final pin = context.read<AccountProvider>().activedAccount.pin;
rpc.send('wallet-import', [chain.toInt(), secret, pin]);
Navigator.pop(context);
}),
]

1
lib/l10n/localizations.dart

@ -270,6 +270,7 @@ abstract class AppLocalizations { @@ -270,6 +270,7 @@ abstract class AppLocalizations {
String get contract;
String get main;
String get setMain;
String get transfer;
}
class _AppLocalizationsDelegate

2
lib/l10n/localizations_en.dart

@ -443,4 +443,6 @@ class AppLocalizationsEn extends AppLocalizations { @@ -443,4 +443,6 @@ class AppLocalizationsEn extends AppLocalizations {
String get main => 'Main';
@override
String get setMain => 'Set to main';
@override
String get transfer => 'Transfer';
}

2
lib/l10n/localizations_zh.dart

@ -443,4 +443,6 @@ class AppLocalizationsZh extends AppLocalizations { @@ -443,4 +443,6 @@ class AppLocalizationsZh extends AppLocalizations {
String get main => '主账户';
@override
String get setMain => '设为主账户';
@override
String get transfer => '转账';
}

346
lib/widgets/transfer.dart

@ -0,0 +1,346 @@ @@ -0,0 +1,346 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:esse/l10n/localizations.dart';
import 'package:esse/widgets/show_pin.dart';
import 'package:esse/widgets/button_text.dart';
import 'package:esse/widgets/input_text.dart';
import 'package:esse/widgets/shadow_dialog.dart';
import 'package:esse/apps/wallet/models.dart';
import 'package:esse/provider.dart';
import 'package:esse/global.dart';
import 'package:esse/rpc.dart';
class Transfer extends StatefulWidget {
final Function callback;
final String to;
const Transfer({Key? key, required this.callback, required this.to}) : super(key: key);
@override
_TransferState createState() => _TransferState();
}
class _TransferState extends State<Transfer> {
Network _selectedNetwork = Network.EthMain;
Color _networkColor = Colors.green;
List<Network> _networks = [];
Address? _selectedAddress;
List<Address> _addresses = [];
Token _selectedToken = Token.eth(Network.EthMain);
Token _mainToken = Token.eth(Network.EthMain);
List<Token> _tokens = [];
List<String> _nft = [];
String _selectedNft = '';
TextEditingController _amountController = TextEditingController();
FocusNode _amountFocus = FocusNode();
bool _checked = false;
bool _checking = false;
String _price = '';
String _gas = '0';
String _networkError = '';
@override
initState() {
rpc.addListener('wallet-token', _walletToken, false);
rpc.addListener('wallet-balance', _walletBalance, false);
_amountController.addListener(() {
setState(() {
this._checked = false;
this._checking = false;
});
});
super.initState();
_loadWallet();
}
_walletToken(List params) {
final network = NetworkExtension.fromInt(params[0]);
if (network == this._selectedNetwork) {
this._tokens.clear();
params[1].forEach((param) {
this._tokens.add(Token.fromList(param, '0'));
});
}
setState(() {});
}
_walletBalance(List params) {
final address = params[0];
final network = NetworkExtension.fromInt(params[1]);
if (address == this._selectedAddress!.address && network == this._selectedNetwork) {
final balance = params[2];
if (params.length == 4) {
for (int i=0;i<this._tokens.length;i++) {
if (this._tokens[i].contract == params[3][4]) {
this._tokens[i].updateBalance(balance);
}
}
} else {
this._mainToken.updateBalance(balance);
}
setState(() {});
}
}
_loadWallet() async {
final res = await httpPost(Global.httpRpc, 'wallet-list', []);
if (res.isOk) {
this._addresses.clear();
res.params.forEach((param) {
final address = Address.fromList(param);
this._addresses.add(address);
if (address.isMain) {
_changeAddress(address);
}
});
setState(() {});
} else {
print(res.error);
}
}
_changeAddress(Address address) {
this._selectedAddress = address;
this._networks = address.networks();
if (!this._networks.contains(this._selectedNetwork)) {
_changeNetwork(this._networks[0]);
} else {
rpc.send('wallet-token', [
this._selectedNetwork.toInt(), this._selectedAddress!.address
]);
}
this._mainToken = address.mainToken(this._selectedNetwork);
this._selectedToken = _mainToken;
}
_changeNetwork(Network network) {
this._selectedNetwork = network;
this._networkColor = network.params()[1];
rpc.send('wallet-token', [
this._selectedNetwork.toInt(), this._selectedAddress!.address
]);
}
_gasPrice(String amount) async {
final res = await httpPost(Global.httpRpc, 'wallet-gas-price', [
this._selectedToken.chain.toInt(), this._selectedNetwork.toInt(),
this._selectedAddress!.address, widget.to, amount,
this._selectedToken.contract
]);
if (res.isOk) {
this._price = unitBalance(res.params[0], 9, 0);
this._gas = unitBalance(res.params[1], 18, 6);
this._networkError = '';
this._checked = true;
} else {
this._networkError = res.error;
}
this._checking = false;
setState(() {});
}
@override
Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme;
final lang = AppLocalizations.of(context);
if (_selectedAddress == null) {
return Center(child: Text(lang.waiting));
}
final mainWidget = [DropdownMenuItem<Token>(
value: this._mainToken,
child: Row(
children: [
Text(this._mainToken.name),
Spacer(),
Text(this._selectedAddress!.balance(this._selectedNetwork)),
const SizedBox(width: 10.0),
]
),
)];
return Column(
children: [
Text('-> ' + widget.to, style: TextStyle(color: color.primary)),
const SizedBox(height: 10.0),
Row(
children: [
Icon(Icons.public, color: this._networkColor),
const SizedBox(width: 10.0),
Expanded(
child: DropdownButtonHideUnderline(
child: DropdownButton<Network>(
iconEnabledColor: Color(0xFFADB0BB),
isExpanded: true,
value: this._selectedNetwork,
onChanged: (Network? network) {
if (network != null) {
setState(() {
this._checked = false;
_changeNetwork(network);
});
}
},
items: this._networks.map((network) {
final params = network.params();
return DropdownMenuItem<Network>(
value: network,
child: Text(params[0], style: TextStyle(fontSize: 16, color: params[1]))
);
}).toList(),
),
)
)]), // network select.
Row(
children: [
const Icon(Icons.account_circle, color: Color(0xFF6174FF)),
const SizedBox(width: 10.0),
Expanded(
child: DropdownButtonHideUnderline(
child: DropdownButton<Address>(
iconEnabledColor: Color(0xFFADB0BB),
isExpanded: true,
value: this._selectedAddress,
onChanged: (Address? addr) {
if (addr != null) {
setState(() {
this._checked = false;
_changeAddress(addr);
});
}
},
items: this._addresses.map((address) {
return DropdownMenuItem<Address>(
value: address,
child: Row(
children: [
Text(address.name),
Spacer(),
Text('(' + address.short() + ')'),
const SizedBox(width: 10.0),
]
),
);
}).toList(),
),
),
)]), // address select.
Row(
children: [
const SizedBox(width: 2.0),
Container(
width: 20.0,
height: 20.0,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(this._selectedToken.logo),
fit: BoxFit.cover,
),
),
),
const SizedBox(width: 10.0),
Expanded(
child: DropdownButtonHideUnderline(
child: DropdownButton<Token>(
iconEnabledColor: Color(0xFFADB0BB),
isExpanded: true,
value: this._selectedToken,
onChanged: (Token? token) {
if (token != null) {
setState(() {
this._checked = false;
this._selectedToken = token;
});
}
},
items: mainWidget + this._tokens.map((token) {
return DropdownMenuItem<Token>(
value: token,
child: Row(
children: [
Text(token.name),
Spacer(),
Text(token.balance),
const SizedBox(width: 10.0),
]
),
);
}).toList(),
),
),
)]), // token select.
const SizedBox(height: 20.0),
InputText(
icon: this._selectedToken.isNft() ? Icons.verified : Icons.paid,
text: this._selectedToken.isNft() ? 'TokenID' : '0.0',
controller: _amountController,
focus: _amountFocus
),
const SizedBox(height: 20.0),
this._checked
? ButtonText(
text: lang.send,
action: () {
String a = _amountController.text.trim();
if (this._selectedToken.isNft()) {
a = this._selectedNft;
}
if (a.length == 0 || (!this._selectedToken.isNft() && double.parse(a) == 0)) {
_amountFocus.requestFocus();
return;
}
final amount = restoreBalance(a, this._selectedToken.decimal);
final gid = context.read<AccountProvider>().activedAccount.gid;
showShadowDialog(
context,
Icons.security_rounded,
lang.verifyPin,
PinWords(
gid: gid,
callback: (key) async {
Navigator.of(context).pop();
final res = await httpPost(Global.httpRpc, 'wallet-transfer', [
this._selectedToken.chain.toInt(), this._selectedNetwork.toInt(),
this._selectedAddress!.id, widget.to, amount,
this._selectedToken.contract, key,
]);
if (res.isOk) {
final addressId = res.params[0];
final network = NetworkExtension.fromInt(res.params[1]);
final tx = Transaction.fromList(res.params[2]);
widget.callback(tx.hash, tx.to, amount, this._selectedToken.name);
Navigator.of(context).pop();
} else {
this._networkError = res.error;
}
}),
0.0,
);
})
: ButtonText(
enable: !this._checking,
text: this._checking ? lang.waiting : lang.check,
action: () {
String a = _amountController.text.trim();
if (this._selectedToken.isNft()) {
a = this._selectedNft;
}
if (a.length == 0 || (!this._selectedToken.isNft() && double.parse(a) == 0)) {
_amountFocus.requestFocus();
return;
}
final amount = restoreBalance(a, this._selectedToken.decimal);
_gasPrice(amount);
setState(() {
this._checking = true;
});
})
]);
}
}

89
src/account.rs

@ -1,12 +1,16 @@ @@ -1,12 +1,16 @@
use rand::Rng;
use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};
use tdn::types::{
group::{EventId, GroupId},
primitive::Result,
primitive::{PeerId, Result},
};
use tdn_did::{generate_id, Keypair, Language};
use tdn_storage::local::{DStorage, DsValue};
use crate::utils::crypto::{check_pin, decrypt, decrypt_multiple, encrypt_multiple, hash_pin};
use crate::utils::crypto::{
check_pin, decrypt, decrypt_key, encrypt_key, encrypt_multiple, hash_pin,
};
fn _mnemonic_lang_to_i64(lang: Language) -> i64 {
match lang {
@ -48,8 +52,9 @@ pub(crate) struct Account { @@ -48,8 +52,9 @@ pub(crate) struct Account {
pub pass: String,
pub name: String,
pub avatar: Vec<u8>,
pub lock: Vec<u8>, // hashed-lock.
pub secret: Vec<u8>, // encrypted value.
pub lock: Vec<u8>, // hashed-lock.
pub secret: Vec<u8>, // encrypted value.
pub encrypt: Vec<u8>, // encrypted encrypt key.
pub height: u64,
pub event: EventId,
pub datetime: i64,
@ -66,6 +71,7 @@ impl Account { @@ -66,6 +71,7 @@ impl Account {
avatar: Vec<u8>,
mnemonic: Vec<u8>,
secret: Vec<u8>,
encrypt: Vec<u8>,
) -> Self {
let start = SystemTime::now();
let datetime = start
@ -85,6 +91,7 @@ impl Account { @@ -85,6 +91,7 @@ impl Account {
lock,
mnemonic,
secret,
encrypt,
avatar,
datetime,
}
@ -112,7 +119,10 @@ impl Account { @@ -112,7 +119,10 @@ impl Account {
if pass.len() > 0 { Some(pass) } else { None },
)?;
let mut ebytes = encrypt_multiple(salt, lock, vec![&sk.to_bytes(), mnemonic.as_bytes()])?;
let key = rand::thread_rng().gen::<[u8; 32]>();
let ckey = encrypt_key(salt, lock, &key)?;
let mut ebytes =
encrypt_multiple(salt, lock, &ckey, vec![&sk.to_bytes(), mnemonic.as_bytes()])?;
let mnemonic = ebytes.pop().unwrap_or(vec![]);
let secret = ebytes.pop().unwrap_or(vec![]);
let index = index as i64;
@ -128,6 +138,7 @@ impl Account { @@ -128,6 +138,7 @@ impl Account {
avatar,
mnemonic,
secret,
ckey,
),
sk,
))
@ -143,24 +154,22 @@ impl Account { @@ -143,24 +154,22 @@ impl Account {
pub fn pin(&mut self, salt: &[u8], old: &str, new: &str) -> Result<()> {
self.check_lock(salt, old)?;
let pbytes = decrypt_multiple(salt, old, vec![&self.secret, &self.mnemonic])?;
let mut ebytes = encrypt_multiple(salt, new, pbytes.iter().map(|v| v.as_ref()).collect())?;
self.mnemonic = ebytes.pop().unwrap_or(vec![]);
self.secret = ebytes.pop().unwrap_or(vec![]);
self.lock = hash_pin(salt, new, self.index);
let key = decrypt_key(salt, old, &self.encrypt)?;
self.encrypt = encrypt_key(salt, new, &key)?;
Ok(())
}
pub fn mnemonic(&self, salt: &[u8], lock: &str) -> Result<String> {
self.check_lock(salt, lock)?;
let pbytes = decrypt(salt, lock, &self.mnemonic)?;
let pbytes = decrypt(salt, lock, &self.encrypt, &self.mnemonic)?;
String::from_utf8(pbytes).or(Err(anyhow!("mnemonic unlock invalid.")))
}
pub fn secret(&self, salt: &[u8], lock: &str) -> Result<Keypair> {
self.check_lock(salt, lock)?;
let pbytes = decrypt(salt, lock, &self.secret)?;
let pbytes = decrypt(salt, lock, &self.encrypt, &self.secret)?;
Keypair::from_bytes(&pbytes).or(Err(anyhow!("secret unlock invalid.")))
}
@ -171,6 +180,7 @@ impl Account { @@ -171,6 +180,7 @@ impl Account {
event: EventId::from_hex(v.pop().unwrap().as_str()).unwrap_or(EventId::default()),
height: v.pop().unwrap().as_i64() as u64,
avatar: base64::decode(v.pop().unwrap().as_str()).unwrap_or(vec![]),
encrypt: base64::decode(v.pop().unwrap().as_str()).unwrap_or(vec![]),
secret: base64::decode(v.pop().unwrap().as_str()).unwrap_or(vec![]),
mnemonic: base64::decode(v.pop().unwrap().as_str()).unwrap_or(vec![]),
lock: base64::decode(v.pop().unwrap().as_string()).unwrap_or(vec![]),
@ -185,28 +195,24 @@ impl Account { @@ -185,28 +195,24 @@ impl Account {
pub fn _get(db: &DStorage, gid: &GroupId) -> Result<Option<Account>> {
let sql = format!(
"SELECT id, gid, indx, lang, pass, name, lock, mnemonic, secret, avatar, height, event, datetime FROM accounts WHERE gid = '{}'",
"SELECT id, gid, indx, lang, pass, name, lock, mnemonic, secret, encrypt, avatar, height, event, datetime FROM accounts WHERE gid = '{}'",
gid.to_hex()
);
let mut matrix = db.query(&sql)?;
if matrix.len() > 0 {
let values = matrix.pop().unwrap(); // safe unwrap()
if values.len() == 13 {
return Ok(Some(Account::from_values(values)));
}
return Ok(Some(Account::from_values(values)));
}
Ok(None)
}
pub fn all(db: &DStorage) -> Result<Vec<Account>> {
let matrix = db.query(
"SELECT id, gid, indx, lang, pass, name, lock, mnemonic, secret, avatar, height, event, datetime FROM accounts ORDER BY datetime DESC",
"SELECT id, gid, indx, lang, pass, name, lock, mnemonic, secret, encrypt, avatar, height, event, datetime FROM accounts ORDER BY datetime DESC",
)?;
let mut accounts = vec![];
for values in matrix {
if values.len() == 13 {
accounts.push(Account::from_values(values));
}
accounts.push(Account::from_values(values));
}
Ok(accounts)
}
@ -221,7 +227,7 @@ impl Account { @@ -221,7 +227,7 @@ impl Account {
self.id = id;
self.update(db)?;
} else {
let sql = format!("INSERT INTO accounts (gid, indx, lang, pass, name, lock, mnemonic, secret, avatar, height,event, datetime) VALUES ('{}', {}, {}, '{}', '{}', '{}', '{}', '{}', '{}', {}, '{}', {})",
let sql = format!("INSERT INTO accounts (gid, indx, lang, pass, name, lock, mnemonic, secret, encrypt, avatar, height, event, datetime) VALUES ('{}', {}, {}, '{}', '{}', '{}', '{}', '{}', '{}', '{}', {}, '{}', {})",
self.gid.to_hex(),
self.index,
self.lang,
@ -230,6 +236,7 @@ impl Account { @@ -230,6 +236,7 @@ impl Account {
base64::encode(&self.lock),
base64::encode(&self.mnemonic),
base64::encode(&self.secret),
base64::encode(&self.encrypt),
base64::encode(&self.avatar),
self.height,
self.event.to_hex(),
@ -242,11 +249,10 @@ impl Account { @@ -242,11 +249,10 @@ impl Account {
}
pub fn update(&self, db: &DStorage) -> Result<usize> {
let sql = format!("UPDATE accounts SET name='{}', lock='{}', mnemonic='{}', secret='{}', avatar='{}', height={}, event='{}', datetime={} WHERE id = {}",
let sql = format!("UPDATE accounts SET name='{}', lock='{}', encrypt='{}', avatar='{}', height={}, event='{}', datetime={} WHERE id = {}",
self.name,
base64::encode(&self.lock),
base64::encode(&self.mnemonic),
base64::encode(&self.secret),
base64::encode(&self.encrypt),
base64::encode(&self.avatar),
self.height,
self.datetime,
@ -283,3 +289,40 @@ impl Account { @@ -283,3 +289,40 @@ impl Account {
db.update(&sql)
}
}
#[derive(Serialize, Deserialize, Clone)]
pub(crate) struct User {
pub id: GroupId,
pub addr: PeerId,
pub name: String,
pub wallet: String,
pub avatar: Vec<u8>,
}
impl User {
pub fn simple(id: GroupId, addr: PeerId, name: String, avatar: Vec<u8>) -> Self {
Self {
id,
addr,
name,
avatar,
wallet: String::new(),
}
}
pub fn full(
id: GroupId,
addr: PeerId,
name: String,
wallet: String,
avatar: Vec<u8>,
) -> Result<Self> {
Ok(Self {
id,
addr,
name,
wallet,
avatar,
})
}
}

10
src/apps/chat/layer.rs

@ -7,9 +7,10 @@ use tdn::types::{ @@ -7,9 +7,10 @@ use tdn::types::{
primitive::{DeliveryType, HandleResult, Peer, PeerId, Result},
rpc::RpcError,
};
use tdn_did::{user::User, Proof};
use tdn_did::Proof;
use tokio::sync::RwLock;
use crate::account::User;
use crate::event::InnerEvent;
use crate::layer::{Layer, Online};
use crate::migrate::consensus::{FRIEND_TABLE_PATH, MESSAGE_TABLE_PATH, REQUEST_TABLE_PATH};
@ -32,15 +33,15 @@ pub(crate) enum LayerEvent { @@ -32,15 +33,15 @@ pub(crate) enum LayerEvent {
Suspend(GroupId),
/// actived. extend BaseLayerEvent.
Actived(GroupId),
/// make friendship request.
/// make friendship request. user is simple.
Request(User, String),
/// agree friendship request.
/// agree friendship request. user is simple.
Agree(User, Proof),
/// reject friendship request.
Reject,
/// receiver gid, sender gid, message.
Message(EventId, NetworkMessage),
/// receiver gid, sender user.
/// user full info.
Info(User),
/// close friendship.
Close,
@ -332,6 +333,7 @@ impl LayerEvent { @@ -332,6 +333,7 @@ impl LayerEvent {
let mut f = Friend::get_id(&db, fid)?.ok_or(anyhow!("friend not found"))?;
f.name = remote.name;
f.addr = remote.addr;
f.wallet = remote.wallet;
f.remote_update(&db)?;
drop(db);
write_avatar_sync(&layer.base, &mgid, &remote.id, remote.avatar)?;

777
src/apps/chat/models.rs

@ -1,772 +1,7 @@ @@ -1,772 +1,7 @@
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
use tdn::types::{
group::{EventId, GroupId},
primitive::{PeerId, Result},
rpc::{json, RpcParam},
};
use tdn_storage::local::{DStorage, DsValue};
mod friend;
mod message;
mod request;
use crate::session::{Session, SessionType};
use crate::storage::{
read_avatar_sync, read_file_sync, read_image_sync, read_record_sync, write_avatar_sync,
write_file_sync, write_image_sync, write_record_sync,
};
pub(crate) struct Friend {
pub id: i64,
pub gid: GroupId,
pub addr: PeerId,
pub name: String,
pub remark: String,
pub is_closed: bool,
pub is_deleted: bool,
pub datetime: i64,
}
#[derive(Clone)]
pub(crate) struct Request {
pub id: i64,
pub gid: GroupId,
pub addr: PeerId,
pub name: String,
pub remark: String,
pub is_me: bool,
pub is_ok: bool,
pub is_over: bool,
pub is_delivery: bool,
pub datetime: i64,
pub is_deleted: bool,
}
/// message type use in network.
#[derive(Serialize, Deserialize, Clone)]
pub(crate) enum NetworkMessage {
String(String), // content
Image(Vec<u8>), // image bytes.
File(String, Vec<u8>), // filename, file bytes.
Contact(String, GroupId, PeerId, Vec<u8>), // name, gid, addr, avatar bytes.
Record(Vec<u8>, u32), // record audio bytes.
Emoji,
Phone,
Video,
Invite(String),
None,
}
impl NetworkMessage {
pub(crate) fn handle(
self,
is_me: bool,
gid: GroupId,
base: &PathBuf,
db: &DStorage,
fid: i64,
hash: EventId,
) -> Result<(Message, String)> {
// handle event.
let (m_type, raw) = match self {
NetworkMessage::String(content) => (MessageType::String, content),
NetworkMessage::Image(bytes) => {
let image_name = write_image_sync(base, &gid, bytes)?;
(MessageType::Image, image_name)
}
NetworkMessage::File(old_name, bytes) => {
let filename = write_file_sync(base, &gid, &old_name, bytes)?;
(MessageType::File, filename)
}
NetworkMessage::Contact(name, rgid, addr, avatar_bytes) => {
write_avatar_sync(base, &gid, &rgid, avatar_bytes)?;
let tmp_name = name.replace(";", "-;");
let contact_values = format!("{};;{};;{}", tmp_name, rgid.to_hex(), addr.to_hex());
(MessageType::Contact, contact_values)
}
NetworkMessage::Emoji => {
// TODO
(MessageType::Emoji, "".to_owned())
}
NetworkMessage::Record(bytes, time) => {
let record_name = write_record_sync(base, &gid, fid, time, bytes)?;
(MessageType::Record, record_name)
}
NetworkMessage::Phone => {
// TODO
(MessageType::Phone, "".to_owned())
}
NetworkMessage::Video => {
// TODO
(MessageType::Video, "".to_owned())
}
NetworkMessage::Invite(content) => (MessageType::Invite, content),
NetworkMessage::None => {
return Ok((
Message::new_with_id(
hash,
fid,
is_me,
MessageType::String,
"".to_owned(),
true,
),
"".to_owned(),
));
}
};
let scontent = match m_type {
MessageType::String => {
format!("{}:{}", m_type.to_int(), raw)
}
_ => format!("{}:", m_type.to_int()),
};
let mut msg = Message::new_with_id(hash, fid, is_me, m_type, raw, true);
msg.insert(db)?;
Ok((msg, scontent))
}
pub fn from_model(base: &PathBuf, gid: &GroupId, model: Message) -> Result<NetworkMessage> {
// handle message's type.
match model.m_type {
MessageType::String => Ok(NetworkMessage::String(model.content)),
MessageType::Image => {
let bytes = read_image_sync(base, gid, &model.content)?;
Ok(NetworkMessage::Image(bytes))
}
MessageType::File => {
let bytes = read_file_sync(base, gid, &model.content)?;
Ok(NetworkMessage::File(model.content, bytes))
}
MessageType::Contact => {
let v: Vec<&str> = model.content.split(";;").collect();
if v.len() != 3 {
return Ok(NetworkMessage::None);
}
let cname = v[0].to_owned();
let cgid = GroupId::from_hex(v[1])?;
let caddr = PeerId::from_hex(v[2])?;
let avatar_bytes = read_avatar_sync(base, gid, &cgid)?;
Ok(NetworkMessage::Contact(cname, cgid, caddr, avatar_bytes))
}
MessageType::Record => {
let (bytes, time) = if let Some(i) = model.content.find('-') {
let time = model.content[0..i].parse().unwrap_or(0);
let bytes = read_record_sync(base, gid, &model.content[i + 1..])?;
(bytes, time)
} else {
(vec![], 0)
};
Ok(NetworkMessage::Record(bytes, time))
}
MessageType::Invite => Ok(NetworkMessage::Invite(model.content)),
MessageType::Emoji => Ok(NetworkMessage::Emoji),
MessageType::Phone => Ok(NetworkMessage::Phone),
MessageType::Video => Ok(NetworkMessage::Video),
}
}
}
#[derive(Eq, PartialEq)]
pub(crate) enum MessageType {
String,
Image,
File,
Contact,
Emoji,
Record,
Phone,
Video,
Invite,
}
impl MessageType {
pub fn to_int(&self) -> i64 {
match self {
MessageType::String => 0,
MessageType::Image => 1,
MessageType::File => 2,
MessageType::Contact => 3,
MessageType::Emoji => 4,
MessageType::Record => 5,
MessageType::Phone => 6,
MessageType::Video => 7,
MessageType::Invite => 8,
}
}
pub fn from_int(i: i64) -> MessageType {
match i {
0 => MessageType::String,
1 => MessageType::Image,
2 => MessageType::File,
3 => MessageType::Contact,
4 => MessageType::Emoji,
5 => MessageType::Record,
6 => MessageType::Phone,
7 => MessageType::Video,
8 => MessageType::Invite,
_ => MessageType::String,
}
}
}
pub(crate) struct Message {
pub id: i64,
pub hash: EventId,
pub fid: i64,
pub is_me: bool,
pub m_type: MessageType,
pub content: String,
pub is_delivery: bool,
pub datetime: i64,
pub is_deleted: bool,
}
impl Friend {
pub fn new(gid: GroupId, addr: PeerId, name: String, remark: String) -> Friend {
let start = SystemTime::now();
let datetime = start
.duration_since(UNIX_EPOCH)
.map(|s| s.as_secs())
.unwrap_or(0) as i64; // safe for all life.
Friend {
id: 0,
gid,
addr,
name,
remark,
datetime,
is_closed: false,
is_deleted: false,
}
}
pub fn contains_addr(&self, addr: &PeerId) -> bool {
&self.addr == addr
}
/// here is zero-copy and unwrap is safe. checked.
fn from_values(mut v: Vec<DsValue>, contains_deleted: bool) -> Friend {
let is_deleted = if contains_deleted {
v.pop().unwrap().as_bool()
} else {
false
};
Friend {
is_deleted,
datetime: v.pop().unwrap().as_i64(),
is_closed: v.pop().unwrap().as_bool(),
remark: v.pop().unwrap().as_string(),
name: v.pop().unwrap().as_string(),
addr: PeerId::from_hex(v.pop().unwrap().as_str()).unwrap_or(PeerId::default()),
gid: GroupId::from_hex(v.pop().unwrap().as_str()).unwrap_or(GroupId::default()),
id: v.pop().unwrap().as_i64(),
}
}
pub fn from_request(db: &DStorage, request: Request) -> Result<Friend> {
if let Some(mut friend) = Friend::get_it(&db, &request.gid)? {
friend.name = request.name;
friend.addr = request.addr;
friend.is_closed = false;
friend.remote_update(&db)?;
Ok(friend)
} else {
let mut friend = request.to_friend();
friend.insert(&db)?;
Ok(friend)
}
}
pub fn to_session(&self) -> Session {
Session::new(
self.id,
self.gid,
self.addr,
SessionType::Chat,
self.name.clone(),
self.datetime,
)
}
pub fn to_rpc(&self) -> RpcParam {
json!([
self.id,
self.gid.to_hex(),
self.addr.to_hex(),
self.name,
self.remark,
self.is_closed,
self.datetime
])
}
pub fn get(db: &DStorage, gid: &GroupId) -> Result<Option<Friend>> {
let sql = format!("SELECT id, gid, addr, name, remark, is_closed, datetime FROM friends WHERE gid = '{}' and is_deleted = false", gid.to_hex());
let mut matrix = db.query(&sql)?;
if matrix.len() > 0 {
return Ok(Some(Friend::from_values(matrix.pop().unwrap(), false))); // safe unwrap()
}
Ok(None)
}
pub fn get_it(db: &DStorage, gid: &GroupId) -> Result<Option<Friend>> {
let sql = format!("SELECT id, gid, addr, name, remark, is_closed, datetime, is_deleted FROM friends WHERE gid = '{}'", gid.to_hex());
let mut matrix = db.query(&sql)?;
if matrix.len() > 0 {
return Ok(Some(Friend::from_values(matrix.pop().unwrap(), true))); // safe unwrap()
}
Ok(None)
}
pub fn get_id(db: &DStorage, id: i64) -> Result<Option<Friend>> {
let sql = format!("SELECT id, gid, addr, name, remark, is_closed, datetime, is_deleted FROM friends WHERE id = {}", id);
let mut matrix = db.query(&sql)?;
if matrix.len() > 0 {
return Ok(Some(Friend::from_values(matrix.pop().unwrap(), true))); // safe unwrap()
}
Ok(None)
}
/// use in rpc when load account friends.
pub fn all(db: &DStorage) -> Result<Vec<Friend>> {
let matrix = db.query("SELECT id, gid, addr, name, remark, is_closed, datetime FROM friends where is_deleted = false")?;
let mut friends = vec![];
for values in matrix {
friends.push(Friend::from_values(values, false));
}
Ok(friends)
}
/// use in rpc when load account friends.
pub fn _all_ok(db: &DStorage) -> Result<Vec<Friend>> {
let matrix = db.query("SELECT id, gid, addr, name, remark, is_closed, datetime FROM friends where is_closed = false")?;
let mut friends = vec![];
for values in matrix {
friends.push(Friend::from_values(values, false));
}
Ok(friends)
}
/// use in layer load friends ids.
pub fn _all_id(db: &DStorage) -> Result<Vec<(GroupId, i64)>> {
let matrix =
db.query("SELECT id, gid FROM friends where is_closed = false ORDER BY id DESC")?;
let mut friends = vec![];
for mut values in matrix {
friends.push((
GroupId::from_hex(values.pop().unwrap().as_str()).unwrap_or(GroupId::default()),
values.pop().unwrap().as_i64(),
));
}
Ok(friends)
}
pub fn insert(&mut self, db: &DStorage) -> Result<()> {
let sql = format!("INSERT INTO friends (gid, addr, name, remark, is_closed, datetime, is_deleted) VALUES ('{}', '{}', '{}', '{}', {}, {}, false)",
self.gid.to_hex(),
self.addr.to_hex(),
self.name,
self.remark,
self.is_closed,
self.datetime,
);
let id = db.insert(&sql)?;
self.id = id;
Ok(())
}
pub fn update(&self, db: &DStorage) -> Result<usize> {
let sql = format!("UPDATE friends SET addr = '{}', name = '{}', remark = '{}', is_closed = {}, is_deleted = {} WHERE id = {}",
self.addr.to_hex(),
self.name,
self.remark,
self.is_closed,
self.is_deleted,
self.id
);
db.update(&sql)
}
pub fn me_update(&mut self, db: &DStorage) -> Result<usize> {
let sql = format!(
"UPDATE friends SET remark='{}' WHERE id = {}",
self.remark, self.id,
);
db.update(&sql)
}
pub fn addr_update(db: &DStorage, id: i64, addr: &PeerId) -> Result<usize> {
let sql = format!(
"UPDATE friends SET addr='{}' WHERE id = {}",
addr.to_hex(),
id,
);
db.update(&sql)
}
pub fn remote_update(&self, db: &DStorage) -> Result<usize> {
let sql = format!(
"UPDATE friends SET addr='{}', name='{}', is_closed = false, is_deleted = false WHERE id = {}",
self.addr.to_hex(),
self.name,
self.id,
);
db.update(&sql)
}
/// used in rpc, when what to delete a friend.
pub fn close(&self, db: &DStorage) -> Result<usize> {
let sql = format!("UPDATE friends SET is_closed = true WHERE id = {}", self.id);
db.update(&sql)
}
/// used in rpc, when what to delete a friend.
pub fn delete(&self, db: &DStorage) -> Result<usize> {
let sql = format!(
"UPDATE friends SET is_deleted = true, is_closed = true WHERE id = {}",
self.id
);
db.update(&sql)
}
pub fn is_friend(db: &DStorage, gid: &GroupId) -> Result<bool> {
let sql = format!(
"SELECT id FROM friends WHERE is_closed = false and gid = '{}'",
gid.to_hex()
);
let matrix = db.query(&sql)?;
Ok(matrix.len() > 0)
}
/// used in layers, when receive remote had closed.
pub fn id_close(db: &DStorage, id: i64) -> Result<usize> {
let sql = format!("UPDATE friends SET is_closed = true WHERE id = {}", id);
db.update(&sql)
}
}
impl Request {
pub fn new(
gid: GroupId,
addr: PeerId,
name: String,
remark: String,
is_me: bool,
is_delivery: bool,
) -> Request {
let start = SystemTime::now();
let datetime = start
.duration_since(UNIX_EPOCH)
.map(|s| s.as_secs())
.unwrap_or(0) as i64; // safe for all life.
Request {
id: 0,
gid,
addr,
name,
remark,
is_me,
is_ok: false,
is_over: false,
is_delivery,
datetime: datetime,
is_deleted: false,
}
}
pub fn to_friend(self) -> Friend {
Friend::new(self.gid, self.addr, self.name, self.remark)
}
/// here is zero-copy and unwrap is safe. checked.
fn from_values(mut v: Vec<DsValue>, contains_deleted: bool) -> Request {
let is_deleted = if contains_deleted {
v.pop().unwrap().as_bool()
} else {
false
};
Request {
is_deleted,
datetime: v.pop().unwrap().as_i64(),
is_delivery: v.pop().unwrap().as_bool(),
is_over: v.pop().unwrap().as_bool(),
is_ok: v.pop().unwrap().as_bool(),
is_me: v.pop().unwrap().as_bool(),
remark: v.pop().unwrap().as_string(),
name: v.pop().unwrap().as_string(),
addr: PeerId::from_hex(v.pop().unwrap().as_str()).unwrap_or(PeerId::default()),
gid: GroupId::from_hex(v.pop().unwrap().as_str()).unwrap_or(GroupId::default()),
id: v.pop().unwrap().as_i64(),
}
}
pub fn to_rpc(&self) -> RpcParam {
json!([
self.id,
self.gid.to_hex(),
self.addr.to_hex(),
self.name,
self.remark,
self.is_me,
self.is_ok,
self.is_over,
self.is_delivery,
self.datetime,
])
}
pub fn get(db: &DStorage, gid: &GroupId) -> Result<Option<Request>> {
let sql = format!("SELECT id, gid, addr, name, remark, is_me, is_ok, is_over, is_delivery, datetime FROM requests WHERE gid = '{}' AND is_deleted = false", gid.to_hex());
let mut matrix = db.query(&sql)?;
if matrix.len() > 0 {
let values = matrix.pop().unwrap(); // safe unwrap()
return Ok(Some(Request::from_values(values, false)));
}
Ok(None)
}
pub fn get_id(db: &DStorage, id: i64) -> Result<Option<Request>> {
let sql = format!("SELECT id, gid, addr, name, remark, is_me, is_ok, is_over, is_delivery, datetime, is_deleted FROM requests WHERE id = {}", id);
let mut matrix = db.query(&sql)?;
if matrix.len() > 0 {
let values = matrix.pop().unwrap(); // safe unwrap()
return Ok(Some(Request::from_values(values, true)));
}
Ok(None)
}
pub fn all(db: &DStorage) -> Result<Vec<Request>> {
let matrix = db.query("SELECT id, gid, addr, name, remark, is_me, is_ok, is_over, is_delivery, datetime FROM requests WHERE is_deleted = false ORDER BY id DESC")?;
let mut requests = vec![];
for values in matrix {
requests.push(Request::from_values(values, false));
}
Ok(requests)
}
pub fn insert(&mut self, db: &DStorage) -> Result<()> {
let sql = format!("INSERT INTO requests (gid, addr, name, remark, is_me, is_ok, is_over, is_delivery, datetime, is_deleted) VALUES ('{}', '{}', '{}', '{}', {}, {}, {}, {}, {}, false)",
self.gid.to_hex(),
self.addr.to_hex(),
self.name,
self.remark,
self.is_me,
self.is_ok,
self.is_over,
self.is_delivery,
self.datetime,
);
let id = db.insert(&sql)?;
self.id = id;
Ok(())
}
pub fn update(&self, db: &DStorage) -> Result<usize> {
let sql = format!("UPDATE requests SET gid='{}', addr='{}', name='{}', remark='{}', is_me={}, is_ok={}, is_over={}, is_delivery={}, datetime={}, is_deleted = {} WHERE id = {}",
self.gid.to_hex(),
self.addr.to_hex(),
self.name,
self.remark,
self.is_me,
self.is_ok,
self.is_over,
self.is_delivery,
self.datetime,
self.is_deleted,
self.id,
);
db.update(&sql)
}
pub fn delivery(db: &DStorage, id: i64, is_delivery: bool) -> Result<usize> {
let sql = format!(
"UPDATE requests SET is_delivery={} WHERE id = {}",
if is_delivery { 1 } else { 0 },
id,
);
db.update(&sql)
}
pub fn delete(&self, db: &DStorage) -> Result<usize> {
let sql = format!(
"UPDATE requests SET is_deleted = true WHERE id = {}",
self.id
);
db.delete(&sql)
}
}
impl Message {
pub fn new(
gid: &GroupId,
fid: i64,
is_me: bool,
m_type: MessageType,
content: String,
is_delivery: bool,
) -> Message {
let start = SystemTime::now();
let datetime = start
.duration_since(UNIX_EPOCH)
.map(|s| s.as_secs())
.unwrap_or(0) as i64; // safe for all life.
let mut bytes = [0u8; 32];
bytes[0..8].copy_from_slice(&gid.0[0..8]);
bytes[8..16].copy_from_slice(&(fid as u64).to_le_bytes()); // 8-bytes.
bytes[16..24].copy_from_slice(&(datetime as u64).to_le_bytes()); // 8-bytes.
let content_bytes = content.as_bytes();
if content_bytes.len() >= 8 {
bytes[24..32].copy_from_slice(&content_bytes[0..8]);
} else {
bytes[24..(24 + content_bytes.len())].copy_from_slice(&content_bytes);
}
Message {
id: 0,
hash: EventId(bytes),
is_deleted: false,
fid,
is_me,
m_type,
content,
is_delivery,
datetime,
}
}
pub fn new_with_id(
hash: EventId,
fid: i64,
is_me: bool,
m_type: MessageType,
content: String,
is_delivery: bool,
) -> Message {
let start = SystemTime::now();
let datetime = start
.duration_since(UNIX_EPOCH)
.map(|s| s.as_secs())
.unwrap_or(0) as i64; // safe for all life.
Message {
id: 0,
is_deleted: false,
hash,
fid,
is_me,
m_type,
content,
is_delivery,
datetime,
}
}
/// here is zero-copy and unwrap is safe. checked.
fn from_values(mut v: Vec<DsValue>, contains_deleted: bool) -> Message {
let is_deleted = if contains_deleted {
v.pop().unwrap().as_bool()
} else {
false
};
Message {
is_deleted,
datetime: v.pop().unwrap().as_i64(),
is_delivery: v.pop().unwrap().as_bool(),
content: v.pop().unwrap().as_string(),
m_type: MessageType::from_int(v.pop().unwrap().as_i64()),
is_me: v.pop().unwrap().as_bool(),
fid: v.pop().unwrap().as_i64(),
hash: EventId::from_hex(v.pop().unwrap().as_str()).unwrap_or(EventId::default()),
id: v.pop().unwrap().as_i64(),
}
}
pub fn to_rpc(&self) -> RpcParam {
json!([
self.id,
self.hash.to_hex(),
self.fid,
self.is_me,
self.m_type.to_int(),
self.content,
self.is_delivery,
self.datetime,
])
}
pub fn get(db: &DStorage, fid: &i64) -> Result<Vec<Message>> {
let sql = format!("SELECT id, hash, fid, is_me, m_type, content, is_delivery, datetime FROM messages WHERE fid = {} and is_deleted = false ORDER BY id DESC", fid);
let matrix = db.query(&sql)?;
let mut messages = vec![];
for values in matrix {
messages.push(Message::from_values(values, false));
}
Ok(messages)
}
pub fn get_id(db: &DStorage, id: i64) -> Result<Option<Message>> {
let sql = format!("SELECT id, hash, fid, is_me, m_type, content, is_delivery, datetime, is_deleted FROM messages WHERE id = {}", id);
let mut matrix = db.query(&sql)?;
if matrix.len() > 0 {
let values = matrix.pop().unwrap(); // safe unwrap()
return Ok(Some(Message::from_values(values, true)));
}
Ok(None)
}
pub fn get_it(db: &DStorage, hash: &EventId) -> Result<Option<Message>> {
let sql = format!("SELECT id, hash, fid, is_me, m_type, content, is_delivery, datetime, is_deleted FROM messages WHERE hash = {}", hash.to_hex());
let mut matrix = db.query(&sql)?;
if matrix.len() > 0 {
let values = matrix.pop().unwrap(); // safe unwrap()
return Ok(Some(Message::from_values(values, true)));
}
Ok(None)
}
pub fn insert(&mut self, db: &DStorage) -> Result<()> {
let sql = format!(
"INSERT INTO messages (hash, fid, is_me, m_type, content, is_delivery, datetime, is_deleted) VALUES ('{}',{},{},{},'{}',{},{},false)",
self.hash.to_hex(),
self.fid,
self.is_me,
self.m_type.to_int(),
self.content,
self.is_delivery,
self.datetime,
);
self.id = db.insert(&sql)?;
Ok(())
}
pub fn delivery(db: &DStorage, id: i64, is_delivery: bool) -> Result<usize> {
let sql = format!(
"UPDATE messages SET is_delivery={} WHERE id = {}",
is_delivery, id,
);
db.update(&sql)
}
pub fn delete(&self, db: &DStorage) -> Result<usize> {
let sql = format!(
"UPDATE messages SET is_deleted = true WHERE id = {}",
self.id
);
db.delete(&sql)
}
pub fn exist(db: &DStorage, hash: &EventId) -> Result<bool> {
let sql = format!("SELECT id FROM messages WHERE hash = '{}'", hash.to_hex());
let matrix = db.query(&sql)?;
Ok(matrix.len() > 0)
}
}
pub(crate) use self::friend::Friend;
pub(crate) use self::message::{Message, MessageType, NetworkMessage};
pub(crate) use self::request::Request;

255
src/apps/chat/models/friend.rs

@ -0,0 +1,255 @@ @@ -0,0 +1,255 @@
use std::time::{SystemTime, UNIX_EPOCH};
use tdn::types::{
group::GroupId,
primitive::{PeerId, Result},
rpc::{json, RpcParam},
};
use tdn_storage::local::{DStorage, DsValue};
use crate::session::{Session, SessionType};
use super::Request;
pub(crate) struct Friend {
pub id: i64,
pub gid: GroupId,
pub addr: PeerId,
pub name: String,
pub wallet: String,
pub remark: String,
pub is_closed: bool,
pub is_deleted: bool,
pub datetime: i64,
}
impl Friend {
pub fn new(gid: GroupId, addr: PeerId, name: String, wallet: String, remark: String) -> Friend {
let start = SystemTime::now();
let datetime = start
.duration_since(UNIX_EPOCH)
.map(|s| s.as_secs())
.unwrap_or(0) as i64; // safe for all life.
Friend {
id: 0,
gid,
addr,
name,
wallet,
remark,
datetime,
is_closed: false,
is_deleted: false,
}
}
pub fn contains_addr(&self, addr: &PeerId) -> bool {
&self.addr == addr
}
/// here is zero-copy and unwrap is safe. checked.
fn from_values(mut v: Vec<DsValue>, contains_deleted: bool) -> Friend {
let is_deleted = if contains_deleted {
v.pop().unwrap().as_bool()
} else {
false
};
Friend {
is_deleted,
datetime: v.pop().unwrap().as_i64(),
is_closed: v.pop().unwrap().as_bool(),
remark: v.pop().unwrap().as_string(),
wallet: v.pop().unwrap().as_string(),
name: v.pop().unwrap().as_string(),
addr: PeerId::from_hex(v.pop().unwrap().as_str()).unwrap_or(PeerId::default()),
gid: GroupId::from_hex(v.pop().unwrap().as_str()).unwrap_or(GroupId::default()),
id: v.pop().unwrap().as_i64(),
}
}
pub fn from_request(db: &DStorage, request: Request) -> Result<Friend> {
if let Some(mut friend) = Friend::get_it(&db, &request.gid)? {
friend.name = request.name;
friend.addr = request.addr;
friend.is_closed = false;
friend.remote_update(&db)?;
Ok(friend)
} else {
let mut friend = request.to_friend();
friend.insert(&db)?;
Ok(friend)
}
}
pub fn to_session(&self) -> Session {
Session::new(
self.id,
self.gid,
self.addr,
SessionType::Chat,
self.name.clone(),
self.datetime,
)
}
pub fn to_rpc(&self) -> RpcParam {
json!([
self.id,
self.gid.to_hex(),
self.addr.to_hex(),
self.name,
self.wallet,
self.remark,
self.is_closed,
self.datetime
])
}
pub fn get(db: &DStorage, gid: &GroupId) -> Result<Option<Friend>> {
let sql = format!("SELECT id, gid, addr, name, wallet, remark, is_closed, datetime FROM friends WHERE gid = '{}' and is_deleted = false", gid.to_hex());
let mut matrix = db.query(&sql)?;
if matrix.len() > 0 {
return Ok(Some(Friend::from_values(matrix.pop().unwrap(), false))); // safe unwrap()
}
Ok(None)
}
pub fn get_it(db: &DStorage, gid: &GroupId) -> Result<Option<Friend>> {
let sql = format!("SELECT id, gid, addr, name, wallet, remark, is_closed, datetime, is_deleted FROM friends WHERE gid = '{}'", gid.to_hex());
let mut matrix = db.query(&sql)?;
if matrix.len() > 0 {
return Ok(Some(Friend::from_values(matrix.pop().unwrap(), true))); // safe unwrap()
}
Ok(None)
}
pub fn get_id(db: &DStorage, id: i64) -> Result<Option<Friend>> {
let sql = format!("SELECT id, gid, addr, name, wallet, remark, is_closed, datetime, is_deleted FROM friends WHERE id = {}", id);
let mut matrix = db.query(&sql)?;
if matrix.len() > 0 {
return Ok(Some(Friend::from_values(matrix.pop().unwrap(), true))); // safe unwrap()
}
Ok(None)
}
/// use in rpc when load account friends.
pub fn all(db: &DStorage) -> Result<Vec<Friend>> {
let matrix = db.query("SELECT id, gid, addr, name, wallet, remark, is_closed, datetime FROM friends where is_deleted = false")?;
let mut friends = vec![];
for values in matrix {
friends.push(Friend::from_values(values, false));
}
Ok(friends)
}
/// use in rpc when load account friends.
pub fn _all_ok(db: &DStorage) -> Result<Vec<Friend>> {
let matrix = db.query("SELECT id, gid, addr, name, wallet, remark, is_closed, datetime FROM friends where is_closed = false")?;
let mut friends = vec![];
for values in matrix {
friends.push(Friend::from_values(values, false));
}
Ok(friends)
}
/// use in layer load friends ids.
pub fn _all_id(db: &DStorage) -> Result<Vec<(GroupId, i64)>> {
let matrix =
db.query("SELECT id, gid FROM friends where is_closed = false ORDER BY id DESC")?;
let mut friends = vec![];
for mut values in matrix {
friends.push((
GroupId::from_hex(values.pop().unwrap().as_str()).unwrap_or(GroupId::default()),
values.pop().unwrap().as_i64(),
));
}
Ok(friends)
}
pub fn insert(&mut self, db: &DStorage) -> Result<()> {
let sql = format!("INSERT INTO friends (gid, addr, name, wallet, remark, is_closed, datetime, is_deleted) VALUES ('{}', '{}', '{}', '{}', '{}', {}, {}, false)",
self.gid.to_hex(),
self.addr.to_hex(),
self.name,
self.wallet,
self.remark,
self.is_closed,
self.datetime,
);
let id = db.insert(&sql)?;
self.id = id;
Ok(())
}
pub fn update(&self, db: &DStorage) -> Result<usize> {
let sql = format!("UPDATE friends SET addr = '{}', name = '{}', wallet = '{}', remark = '{}', is_closed = {}, is_deleted = {} WHERE id = {}",
self.addr.to_hex(),
self.name,
self.wallet,
self.remark,
self.is_closed,
self.is_deleted,
self.id
);
db.update(&sql)
}
pub fn me_update(&mut self, db: &DStorage) -> Result<usize> {
let sql = format!(
"UPDATE friends SET remark='{}' WHERE id = {}",
self.remark, self.id,
);
db.update(&sql)
}
pub fn addr_update(db: &DStorage, id: i64, addr: &PeerId) -> Result<usize> {
let sql = format!(
"UPDATE friends SET addr='{}' WHERE id = {}",
addr.to_hex(),
id,
);
db.update(&sql)
}
pub fn remote_update(&self, db: &DStorage) -> Result<usize> {
let sql = format!(
"UPDATE friends SET addr='{}', name='{}', wallet='{}', is_closed = false, is_deleted = false WHERE id = {}",
self.addr.to_hex(),
self.name,
self.wallet,
self.id,
);
db.update(&sql)
}
/// used in rpc, when what to delete a friend.
pub fn close(&self, db: &DStorage) -> Result<usize> {
let sql = format!("UPDATE friends SET is_closed = true WHERE id = {}", self.id);
db.update(&sql)
}
/// used in rpc, when what to delete a friend.
pub fn delete(&self, db: &DStorage) -> Result<usize> {
let sql = format!(
"UPDATE friends SET is_deleted = true, is_closed = true WHERE id = {}",
self.id
);
db.update(&sql)
}
pub fn is_friend(db: &DStorage, gid: &GroupId) -> Result<bool> {
let sql = format!(
"SELECT id FROM friends WHERE is_closed = false and gid = '{}'",
gid.to_hex()
);
let matrix = db.query(&sql)?;
Ok(matrix.len() > 0)
}
/// used in layers, when receive remote had closed.
pub fn id_close(db: &DStorage, id: i64) -> Result<usize> {
let sql = format!("UPDATE friends SET is_closed = true WHERE id = {}", id);
db.update(&sql)
}
}

366
src/apps/chat/models/message.rs

@ -0,0 +1,366 @@ @@ -0,0 +1,366 @@
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
use tdn::types::{
group::{EventId, GroupId},
primitive::{PeerId, Result},
rpc::{json, RpcParam},
};
use tdn_storage::local::{DStorage, DsValue};
use crate::storage::{
read_avatar_sync, read_file_sync, read_image_sync, read_record_sync, write_avatar_sync,
write_file_sync, write_image_sync, write_record_sync,
};
/// message type use in network.
#[derive(Serialize, Deserialize, Clone)]
pub(crate) enum NetworkMessage {
String(String), // content
Image(Vec<u8>), // image bytes.
File(String, Vec<u8>), // filename, file bytes.
Contact(String, GroupId, PeerId, Vec<u8>), // name, gid, addr, avatar bytes.
Record(Vec<u8>, u32), // record audio bytes.
Emoji,
Phone,
Video,
Invite(String),
None,
}
impl NetworkMessage {
pub(crate) fn handle(
self,
is_me: bool,
gid: GroupId,
base: &PathBuf,
db: &DStorage,
fid: i64,
hash: EventId,
) -> Result<(Message, String)> {
// handle event.
let (m_type, raw) = match self {
NetworkMessage::String(content) => (MessageType::String, content),
NetworkMessage::Image(bytes) => {
let image_name = write_image_sync(base, &gid, bytes)?;
(MessageType::Image, image_name)
}
NetworkMessage::File(old_name, bytes) => {
let filename = write_file_sync(base, &gid, &old_name, bytes)?;
(MessageType::File, filename)
}
NetworkMessage::Contact(name, rgid, addr, avatar_bytes) => {
write_avatar_sync(base, &gid, &rgid, avatar_bytes)?;
let tmp_name = name.replace(";", "-;");
let contact_values = format!("{};;{};;{}", tmp_name, rgid.to_hex(), addr.to_hex());
(MessageType::Contact, contact_values)
}
NetworkMessage::Emoji => {
// TODO
(MessageType::Emoji, "".to_owned())
}
NetworkMessage::Record(bytes, time) => {
let record_name = write_record_sync(base, &gid, fid, time, bytes)?;
(MessageType::Record, record_name)
}
NetworkMessage::Phone => {
// TODO
(MessageType::Phone, "".to_owned())
}
NetworkMessage::Video => {
// TODO
(MessageType::Video, "".to_owned())
}
NetworkMessage::Invite(content) => (MessageType::Invite, content),
NetworkMessage::None => {
return Ok((
Message::new_with_id(
hash,
fid,
is_me,
MessageType::String,
"".to_owned(),
true,
),
"".to_owned(),
));
}
};
let scontent = match m_type {
MessageType::String => {
format!("{}:{}", m_type.to_int(), raw)
}
_ => format!("{}:", m_type.to_int()),
};
let mut msg = Message::new_with_id(hash, fid, is_me, m_type, raw, true);
msg.insert(db)?;
Ok((msg, scontent))
}
pub fn from_model(base: &PathBuf, gid: &GroupId, model: Message) -> Result<NetworkMessage> {
// handle message's type.
match model.m_type {
MessageType::String => Ok(NetworkMessage::String(model.content)),
MessageType::Image => {
let bytes = read_image_sync(base, gid, &model.content)?;
Ok(NetworkMessage::Image(bytes))
}
MessageType::File => {
let bytes = read_file_sync(base, gid, &model.content)?;
Ok(NetworkMessage::File(model.content, bytes))
}
MessageType::Contact => {
let v: Vec<&str> = model.content.split(";;").collect();
if v.len() != 3 {
return Ok(NetworkMessage::None);
}
let cname = v[0].to_owned();
let cgid = GroupId::from_hex(v[1])?;
let caddr = PeerId::from_hex(v[2])?;
let avatar_bytes = read_avatar_sync(base, gid, &cgid)?;
Ok(NetworkMessage::Contact(cname, cgid, caddr, avatar_bytes))
}
MessageType::Record => {
let (bytes, time) = if let Some(i) = model.content.find('-') {
let time = model.content[0..i].parse().unwrap_or(0);
let bytes = read_record_sync(base, gid, &model.content[i + 1..])?;
(bytes, time)
} else {
(vec![], 0)
};
Ok(NetworkMessage::Record(bytes, time))
}
MessageType::Invite => Ok(NetworkMessage::Invite(model.content)),
MessageType::Emoji => Ok(NetworkMessage::Emoji),
MessageType::Phone => Ok(NetworkMessage::Phone),
MessageType::Video => Ok(NetworkMessage::Video),
}
}
}
#[derive(Eq, PartialEq)]
pub(crate) enum MessageType {
String,
Image,
File,
Contact,
Emoji,
Record,
Phone,
Video,
Invite,
}
impl MessageType {
pub fn to_int(&self) -> i64 {
match self {
MessageType::String => 0,
MessageType::Image => 1,
MessageType::File => 2,
MessageType::Contact => 3,
MessageType::Emoji => 4,
MessageType::Record => 5,
MessageType::Phone => 6,
MessageType::Video => 7,
MessageType::Invite => 8,
}
}
pub fn from_int(i: i64) -> MessageType {
match i {
0 => MessageType::String,
1 => MessageType::Image,
2 => MessageType::File,
3 => MessageType::Contact,
4 => MessageType::Emoji,
5 => MessageType::Record,
6 => MessageType::Phone,
7 => MessageType::Video,
8 => MessageType::Invite,
_ => MessageType::String,
}
}
}
pub(crate) struct Message {
pub id: i64,
pub hash: EventId,
pub fid: i64,
pub is_me: bool,
pub m_type: MessageType,
pub content: String,
pub is_delivery: bool,
pub datetime: i64,
pub is_deleted: bool,
}
impl Message {
pub fn new(
gid: &GroupId,
fid: i64,
is_me: bool,
m_type: MessageType,
content: String,
is_delivery: bool,
) -> Message {
let start = SystemTime::now();
let datetime = start
.duration_since(UNIX_EPOCH)
.map(|s| s.as_secs())
.unwrap_or(0) as i64; // safe for all life.
let mut bytes = [0u8; 32];
bytes[0..8].copy_from_slice(&gid.0[0..8]);
bytes[8..16].copy_from_slice(&(fid as u64).to_le_bytes()); // 8-bytes.
bytes[16..24].copy_from_slice(&(datetime as u64).to_le_bytes()); // 8-bytes.
let content_bytes = content.as_bytes();
if content_bytes.len() >= 8 {
bytes[24..32].copy_from_slice(&content_bytes[0..8]);
} else {
bytes[24..(24 + content_bytes.len())].copy_from_slice(&content_bytes);
}
Message {
id: 0,
hash: EventId(bytes),
is_deleted: false,
fid,
is_me,
m_type,
content,
is_delivery,
datetime,
}
}
pub fn new_with_id(
hash: EventId,
fid: i64,
is_me: bool,
m_type: MessageType,
content: String,
is_delivery: bool,
) -> Message {
let start = SystemTime::now();
let datetime = start
.duration_since(UNIX_EPOCH)
.map(|s| s.as_secs())
.unwrap_or(0) as i64; // safe for all life.
Message {
id: 0,
is_deleted: false,
hash,
fid,
is_me,
m_type,
content,
is_delivery,
datetime,
}
}
/// here is zero-copy and unwrap is safe. checked.
fn from_values(mut v: Vec<DsValue>, contains_deleted: bool) -> Message {
let is_deleted = if contains_deleted {
v.pop().unwrap().as_bool()
} else {
false
};
Message {
is_deleted,
datetime: v.pop().unwrap().as_i64(),
is_delivery: v.pop().unwrap().as_bool(),
content: v.pop().unwrap().as_string(),
m_type: MessageType::from_int(v.pop().unwrap().as_i64()),
is_me: v.pop().unwrap().as_bool(),
fid: v.pop().unwrap().as_i64(),
hash: EventId::from_hex(v.pop().unwrap().as_str()).unwrap_or(EventId::default()),
id: v.pop().unwrap().as_i64(),
}
}
pub fn to_rpc(&self) -> RpcParam {
json!([
self.id,
self.hash.to_hex(),
self.fid,
self.is_me,
self.m_type.to_int(),
self.content,
self.is_delivery,
self.datetime,
])
}
pub fn get(db: &DStorage, fid: &i64) -> Result<Vec<Message>> {
let sql = format!("SELECT id, hash, fid, is_me, m_type, content, is_delivery, datetime FROM messages WHERE fid = {} and is_deleted = false ORDER BY id DESC", fid);
let matrix = db.query(&sql)?;
let mut messages = vec![];
for values in matrix {
messages.push(Message::from_values(values, false));
}
Ok(messages)
}
pub fn get_id(db: &DStorage, id: i64) -> Result<Option<Message>> {
let sql = format!("SELECT id, hash, fid, is_me, m_type, content, is_delivery, datetime, is_deleted FROM messages WHERE id = {}", id);
let mut matrix = db.query(&sql)?;
if matrix.len() > 0 {
let values = matrix.pop().unwrap(); // safe unwrap()
return Ok(Some(Message::from_values(values, true)));
}
Ok(None)
}
pub fn get_it(db: &DStorage, hash: &EventId) -> Result<Option<Message>> {
let sql = format!("SELECT id, hash, fid, is_me, m_type, content, is_delivery, datetime, is_deleted FROM messages WHERE hash = {}", hash.to_hex());
let mut matrix = db.query(&sql)?;
if matrix.len() > 0 {
let values = matrix.pop().unwrap(); // safe unwrap()
return Ok(Some(Message::from_values(values, true)));
}
Ok(None)
}
pub fn insert(&mut self, db: &DStorage) -> Result<()> {
let sql = format!(
"INSERT INTO messages (hash, fid, is_me, m_type, content, is_delivery, datetime, is_deleted) VALUES ('{}',{},{},{},'{}',{},{},false)",
self.hash.to_hex(),
self.fid,
self.is_me,
self.m_type.to_int(),
self.content,
self.is_delivery,
self.datetime,
);
self.id = db.insert(&sql)?;
Ok(())
}
pub fn delivery(db: &DStorage, id: i64, is_delivery: bool) -> Result<usize> {
let sql = format!(
"UPDATE messages SET is_delivery={} WHERE id = {}",
is_delivery, id,
);
db.update(&sql)
}
pub fn delete(&self, db: &DStorage) -> Result<usize> {
let sql = format!(
"UPDATE messages SET is_deleted = true WHERE id = {}",
self.id
);
db.delete(&sql)
}
pub fn exist(db: &DStorage, hash: &EventId) -> Result<bool> {
let sql = format!("SELECT id FROM messages WHERE hash = '{}'", hash.to_hex());
let matrix = db.query(&sql)?;
Ok(matrix.len() > 0)
}
}

177
src/apps/chat/models/request.rs

@ -0,0 +1,177 @@ @@ -0,0 +1,177 @@
use std::time::{SystemTime, UNIX_EPOCH};
use tdn::types::{
group::GroupId,
primitive::{PeerId, Result},
rpc::{json, RpcParam},
};
use tdn_storage::local::{DStorage, DsValue};
use super::Friend;
#[derive(Clone)]
pub(crate) struct Request {
pub id: i64,
pub gid: GroupId,
pub addr: PeerId,
pub name: String,
pub remark: String,
pub is_me: bool,
pub is_ok: bool,
pub is_over: bool,
pub is_delivery: bool,
pub datetime: i64,
pub is_deleted: bool,
}
impl Request {
pub fn new(
gid: GroupId,
addr: PeerId,
name: String,
remark: String,
is_me: bool,
is_delivery: bool,
) -> Request {
let start = SystemTime::now();
let datetime = start
.duration_since(UNIX_EPOCH)
.map(|s| s.as_secs())
.unwrap_or(0) as i64; // safe for all life.
Request {
id: 0,
gid,
addr,
name,
remark,
is_me,
is_ok: false,
is_over: false,
is_delivery,
datetime: datetime,
is_deleted: false,
}
}
pub fn to_friend(self) -> Friend {
Friend::new(self.gid, self.addr, self.name, self.remark, "".to_owned())
}
/// here is zero-copy and unwrap is safe. checked.
fn from_values(mut v: Vec<DsValue>, contains_deleted: bool) -> Request {
let is_deleted = if contains_deleted {
v.pop().unwrap().as_bool()
} else {
false
};
Request {
is_deleted,
datetime: v.pop().unwrap().as_i64(),
is_delivery: v.pop().unwrap().as_bool(),
is_over: v.pop().unwrap().as_bool(),
is_ok: v.pop().unwrap().as_bool(),
is_me: v.pop().unwrap().as_bool(),
remark: v.pop().unwrap().as_string(),
name: v.pop().unwrap().as_string(),
addr: PeerId::from_hex(v.pop().unwrap().as_str()).unwrap_or(PeerId::default()),
gid: GroupId::from_hex(v.pop().unwrap().as_str()).unwrap_or(GroupId::default()),
id: v.pop().unwrap().as_i64(),
}
}
pub fn to_rpc(&self) -> RpcParam {
json!([
self.id,
self.gid.to_hex(),
self.addr.to_hex(),
self.name,
self.remark,
self.is_me,
self.is_ok,
self.is_over,
self.is_delivery,
self.datetime,
])
}
pub fn get(db: &DStorage, gid: &GroupId) -> Result<Option<Request>> {
let sql = format!("SELECT id, gid, addr, name, remark, is_me, is_ok, is_over, is_delivery, datetime FROM requests WHERE gid = '{}' AND is_deleted = false", gid.to_hex());
let mut matrix = db.query(&sql)?;
if matrix.len() > 0 {
let values = matrix.pop().unwrap(); // safe unwrap()
return Ok(Some(Request::from_values(values, false)));
}
Ok(None)
}
pub fn get_id(db: &DStorage, id: i64) -> Result<Option<Request>> {
let sql = format!("SELECT id, gid, addr, name, remark, is_me, is_ok, is_over, is_delivery, datetime, is_deleted FROM requests WHERE id = {}", id);
let mut matrix = db.query(&sql)?;
if matrix.len() > 0 {
let values = matrix.pop().unwrap(); // safe unwrap()
return Ok(Some(Request::from_values(values, true)));
}
Ok(None)
}
pub fn all(db: &DStorage) -> Result<Vec<Request>> {
let matrix = db.query("SELECT id, gid, addr, name, remark, is_me, is_ok, is_over, is_delivery, datetime FROM requests WHERE is_deleted = false ORDER BY id DESC")?;
let mut requests = vec![];
for values in matrix {
requests.push(Request::from_values(values, false));
}
Ok(requests)
}
pub fn insert(&mut self, db: &DStorage) -> Result<()> {
let sql = format!("INSERT INTO requests (gid, addr, name, remark, is_me, is_ok, is_over, is_delivery, datetime, is_deleted) VALUES ('{}', '{}', '{}', '{}', {}, {}, {}, {}, {}, false)",
self.gid.to_hex(),
self.addr.to_hex(),
self.name,
self.remark,
self.is_me,
self.is_ok,
self.is_over,
self.is_delivery,
self.datetime,
);
let id = db.insert(&sql)?;
self.id = id;
Ok(())
}
pub fn update(&self, db: &DStorage) -> Result<usize> {
let sql = format!("UPDATE requests SET gid='{}', addr='{}', name='{}', remark='{}', is_me={}, is_ok={}, is_over={}, is_delivery={}, datetime={}, is_deleted = {} WHERE id = {}",
self.gid.to_hex(),
self.addr.to_hex(),
self.name,
self.remark,
self.is_me,
self.is_ok,
self.is_over,
self.is_delivery,
self.datetime,
self.is_deleted,
self.id,
);
db.update(&sql)
}
pub fn delivery(db: &DStorage, id: i64, is_delivery: bool) -> Result<usize> {
let sql = format!(
"UPDATE requests SET is_delivery={} WHERE id = {}",
if is_delivery { 1 } else { 0 },
id,
);
db.update(&sql)
}
pub fn delete(&self, db: &DStorage) -> Result<usize> {
let sql = format!(
"UPDATE requests SET is_deleted = true WHERE id = {}",
self.id
);
db.delete(&sql)
}
}

4
src/apps/chat/rpc.rs

@ -6,8 +6,8 @@ use tdn::types::{ @@ -6,8 +6,8 @@ use tdn::types::{
primitive::{HandleResult, PeerId},
rpc::{json, rpc_response, RpcError, RpcHandler, RpcParam},
};
use tdn_did::user::User;
use crate::account::User;
use crate::event::InnerEvent;
use crate::migrate::consensus::{FRIEND_TABLE_PATH, MESSAGE_TABLE_PATH, REQUEST_TABLE_PATH};
use crate::rpc::{session_create, session_last, sleep_waiting_close_stable, RpcState};
@ -277,7 +277,7 @@ pub(crate) fn new_rpc_handler(handler: &mut RpcHandler<RpcState>) { @@ -277,7 +277,7 @@ pub(crate) fn new_rpc_handler(handler: &mut RpcHandler<RpcState>) {
&gid,
InnerEvent::SessionRequestCreate(
true,
User::new(remote_gid, remote_addr, remote_name, vec![])?,
User::simple(remote_gid, remote_addr, remote_name, vec![]),
remark,
),
REQUEST_TABLE_PATH,

17
src/apps/wallet/rpc.rs

@ -16,19 +16,13 @@ use web3::{ @@ -16,19 +16,13 @@ use web3::{
Web3,
};
use crate::{
rpc::RpcState,
storage::wallet_db,
utils::crypto::{decrypt, encrypt},
};
use crate::{rpc::RpcState, storage::wallet_db};
use super::{
models::{Address, Balance, ChainToken, Network, Token},
ERC20_ABI, ERC721_ABI,
};
const WALLET_DEFAULT_PIN: &'static str = "walletissafe";
#[inline]
fn wallet_list(wallets: Vec<Address>) -> RpcParam {
let mut results = vec![];
@ -384,12 +378,13 @@ pub(crate) fn new_rpc_handler(handler: &mut RpcHandler<RpcState>) { @@ -384,12 +378,13 @@ pub(crate) fn new_rpc_handler(handler: &mut RpcHandler<RpcState>) {
|gid: GroupId, params: Vec<RpcParam>, state: Arc<RpcState>| async move {
let chain = ChainToken::from_i64(params[0].as_i64().ok_or(RpcError::ParseError)?);
let secret = params[1].as_str().ok_or(RpcError::ParseError)?;
let lock = params[2].as_str().ok_or(RpcError::ParseError)?;
let sk: SecretKey = secret.parse().or(Err(RpcError::ParseError))?;
let addr = format!("{:?}", (&sk).address());
let group_lock = state.group.read().await;
let cbytes = encrypt(&group_lock.secret(), WALLET_DEFAULT_PIN, sk.as_ref())?;
let cbytes = group_lock.encrypt(&gid, lock, sk.as_ref())?;
let db = wallet_db(group_lock.base(), &gid)?;
drop(group_lock);
@ -479,11 +474,7 @@ pub(crate) fn new_rpc_handler(handler: &mut RpcHandler<RpcState>) { @@ -479,11 +474,7 @@ pub(crate) fn new_rpc_handler(handler: &mut RpcHandler<RpcState>) {
let (mnemonic, pbytes) = if address.is_gen() {
(group_lock.mnemonic(&gid, lock)?, vec![])
} else {
let pbytes = decrypt(
&group_lock.secret(),
WALLET_DEFAULT_PIN,
address.secret.as_ref(),
)?;
let pbytes = group_lock.decrypt(&gid, lock, address.secret.as_ref())?;
(String::new(), pbytes)
};
let account = group_lock.account(&gid)?;

3
src/event.rs

@ -8,11 +8,10 @@ use tdn::types::{ @@ -8,11 +8,10 @@ use tdn::types::{
message::{SendMessage, SendType},
primitive::{HandleResult, PeerId, Result},
};
use tdn_did::user::User;
use tdn_storage::local::DStorage;
use tokio::sync::{mpsc::Sender, RwLock};
use crate::account::Account;
use crate::account::{Account, User};
use crate::apps::chat::LayerEvent;
use crate::consensus::Event;
use crate::group::{Group, GroupEvent};

25
src/group.rs

@ -7,10 +7,10 @@ use tdn::types::{ @@ -7,10 +7,10 @@ use tdn::types::{
message::{RecvType, SendMessage, SendType},
primitive::{HandleResult, Peer, PeerId, Result},
};
use tdn_did::{user::User, Proof};
use tdn_did::Proof;
use tokio::sync::{mpsc::Sender, RwLock};
use crate::account::Account;
use crate::account::{Account, User};
use crate::apps::device::rpc as device_rpc;
use crate::apps::device::Device;
use crate::consensus::Event;
@ -18,6 +18,7 @@ use crate::event::{InnerEvent, StatusEvent, SyncEvent}; @@ -18,6 +18,7 @@ use crate::event::{InnerEvent, StatusEvent, SyncEvent};
use crate::layer::Layer;
use crate::rpc;
use crate::storage::{account_db, account_init, consensus_db, write_avatar};
use crate::utils::crypto::{decrypt, encrypt};
use crate::utils::device_status::{device_info, device_status as local_device_status};
pub(crate) mod running;
@ -266,10 +267,6 @@ impl Group { @@ -266,10 +267,6 @@ impl Group {
&self.addr
}
pub fn secret(&self) -> &[u8] {
&self.secret
}
pub fn base(&self) -> &PathBuf {
&self.base
}
@ -430,7 +427,12 @@ impl Group { @@ -430,7 +427,12 @@ impl Group {
pub fn clone_user(&self, gid: &GroupId) -> Result<User> {
if let Some(u) = self.accounts.get(gid) {
User::new(u.gid, self.addr, u.name.clone(), u.avatar.clone())
Ok(User::simple(
u.gid,
self.addr,
u.name.clone(),
u.avatar.clone(),
))
} else {
Err(anyhow!("user missing."))
}
@ -521,6 +523,15 @@ impl Group { @@ -521,6 +523,15 @@ impl Group {
}
}
pub fn encrypt(&self, gid: &GroupId, lock: &str, bytes: &[u8]) -> Result<Vec<u8>> {
let ckey = &self.account(gid)?.encrypt;
encrypt(&self.secret, lock, ckey, bytes)
}
pub fn decrypt(&self, gid: &GroupId, lock: &str, bytes: &[u8]) -> Result<Vec<u8>> {
let ckey = &self.account(gid)?.encrypt;
decrypt(&self.secret, lock, ckey, bytes)
}
pub fn create_message(&self, gid: &GroupId, addr: Peer) -> Result<SendType> {
let user = self.clone_user(gid)?;
let account = self.account(gid)?;

1
src/migrate/account.rs

@ -9,6 +9,7 @@ pub(super) const ACCOUNT_VERSIONS: [&str; 11] = [ @@ -9,6 +9,7 @@ pub(super) const ACCOUNT_VERSIONS: [&str; 11] = [
name TEXT NOT NULL,
lock TEXT NOT NULL,
secret TEXT NOT NULL,
encrypt TEXT NOT NULL,
mnemonic TEXT NOT NULL,
avatar TEXT NOT NULL,
height INTEGER NOT NULL,

2
src/migrate/chat.rs

@ -5,8 +5,8 @@ pub(super) const CHAT_VERSIONS: [&str; 3] = [ @@ -5,8 +5,8 @@ pub(super) const CHAT_VERSIONS: [&str; 3] = [
gid TEXT NOT NULL,
addr TEXT NOT NULL,
name TEXT NOT NULL,
wallet TEXT,
remark TEXT,
eth TEXT,
is_closed INTEGER NOT NULL,
datetime INTEGER NOT NULL,
is_deleted INTEGER NOT NULL);",

142
src/utils/crypto.rs

@ -36,35 +36,96 @@ fn build_cipher(salt: &[u8], pin: &str) -> Aes256Gcm { @@ -36,35 +36,96 @@ fn build_cipher(salt: &[u8], pin: &str) -> Aes256Gcm {
Aes256Gcm::new(GenericArray::from_slice(hash_key.as_bytes())) // 256-bit key.
}
/// encrypted bytes.
pub fn encrypt(salt: &[u8], pin: &str, ptext: &[u8]) -> anyhow::Result<Vec<u8>> {
fn build_keycipher(key: &[u8]) -> Aes256Gcm {
let mut hasher = blake3::Hasher::new();
hasher.update(key);
let hash_key = hasher.finalize();
Aes256Gcm::new(GenericArray::from_slice(hash_key.as_bytes())) // 256-bit key.
}
/// encrypted key bytes.
pub fn encrypt_key(salt: &[u8], pin: &str, ptext: &[u8]) -> anyhow::Result<Vec<u8>> {
let cipher = build_cipher(salt, pin);
let mut nonce = Sha256::new();
nonce.update(pin.as_bytes());
nonce.update(&FIX_PADDING);
let res = nonce.finalize();
let mut hasher = Sha256::new();
hasher.update(pin.as_bytes());
hasher.update(&FIX_PADDING);
let res = hasher.finalize();
let nonce = GenericArray::from_slice(&res[0..12]); // 96-bit key.
cipher
.encrypt(nonce, ptext)
.or(Err(anyhow!("encrypt data failure.")))
}
pub fn encrypt_multiple(salt: &[u8], pin: &str, ptext: Vec<&[u8]>) -> anyhow::Result<Vec<Vec<u8>>> {
/// decrypted key bytes.
pub fn decrypt_key(salt: &[u8], pin: &str, ctext: &[u8]) -> anyhow::Result<Vec<u8>> {
let cipher = build_cipher(salt, pin);
let mut nonce = Sha256::new();
nonce.update(pin.as_bytes());
nonce.update(&FIX_PADDING);
let res = nonce.finalize();
let mut hasher = Sha256::new();
hasher.update(pin.as_bytes());
hasher.update(&FIX_PADDING);
let res = hasher.finalize();
let nonce = GenericArray::from_slice(&res[0..12]); // 96-bit key.
cipher
.decrypt(nonce, ctext)
.or(Err(anyhow!("decrypt data failure.")))
}
/// encrypted bytes.
pub fn encrypt(salt: &[u8], pin: &str, ckey: &[u8], ptext: &[u8]) -> anyhow::Result<Vec<u8>> {
let cipher = build_cipher(salt, pin);
let mut hasher = Sha256::new();
hasher.update(pin.as_bytes());
hasher.update(&FIX_PADDING);
let res = hasher.finalize();
let nonce = GenericArray::from_slice(&res[0..12]); // 96-bit key.
let key = cipher
.decrypt(nonce, ckey)
.or(Err(anyhow!("decrypt data failure.")))?;
let c_cipher = build_keycipher(&key);
let mut c_hasher = Sha256::new();
c_hasher.update(salt);
c_hasher.update(&FIX_PADDING);
let c_res = c_hasher.finalize();
let c_nonce = GenericArray::from_slice(&c_res[0..12]); // 96-bit key.
c_cipher
.encrypt(c_nonce, ptext)
.or(Err(anyhow!("encrypt data failure.")))
}
pub fn encrypt_multiple(
salt: &[u8],
pin: &str,
ckey: &[u8],
ptext: Vec<&[u8]>,
) -> anyhow::Result<Vec<Vec<u8>>> {
let cipher = build_cipher(salt, pin);
let mut hasher = Sha256::new();
hasher.update(pin);
hasher.update(&FIX_PADDING);
let res = hasher.finalize();
let nonce = GenericArray::from_slice(&res[0..12]); // 96-bit key.
let key = cipher
.decrypt(nonce, ckey)
.or(Err(anyhow!("decrypt data failure.")))?;
let c_cipher = build_keycipher(&key);
let mut c_hasher = Sha256::new();
c_hasher.update(salt);
c_hasher.update(&FIX_PADDING);
let c_res = c_hasher.finalize();
let c_nonce = GenericArray::from_slice(&c_res[0..12]); // 96-bit key.
let mut ebytes = vec![];
for p in ptext {
ebytes.push(
cipher
.encrypt(nonce, p)
c_cipher
.encrypt(c_nonce, p)
.or(Err(anyhow!("encrypt data failure.")))?,
);
}
@ -72,34 +133,59 @@ pub fn encrypt_multiple(salt: &[u8], pin: &str, ptext: Vec<&[u8]>) -> anyhow::Re @@ -72,34 +133,59 @@ pub fn encrypt_multiple(salt: &[u8], pin: &str, ptext: Vec<&[u8]>) -> anyhow::Re
}
/// decrypted bytes.
pub fn decrypt(salt: &[u8], pin: &str, ctext: &[u8]) -> anyhow::Result<Vec<u8>> {
pub fn decrypt(salt: &[u8], pin: &str, ckey: &[u8], ctext: &[u8]) -> anyhow::Result<Vec<u8>> {
let cipher = build_cipher(salt, pin);
let mut nonce = Sha256::new();
nonce.update(pin.as_bytes());
nonce.update(&FIX_PADDING);
let res = nonce.finalize();
let mut hasher = Sha256::new();
hasher.update(pin.as_bytes());
hasher.update(&FIX_PADDING);
let res = hasher.finalize();
let nonce = GenericArray::from_slice(&res[0..12]); // 96-bit key.
cipher
.decrypt(nonce, ctext)
let key = cipher
.decrypt(nonce, ckey)
.or(Err(anyhow!("decrypt data failure.")))?;
let c_cipher = build_keycipher(&key);
let mut c_hasher = Sha256::new();
c_hasher.update(salt);
c_hasher.update(&FIX_PADDING);
let c_res = c_hasher.finalize();
let c_nonce = GenericArray::from_slice(&c_res[0..12]); // 96-bit key.
c_cipher
.decrypt(c_nonce, ctext)
.or(Err(anyhow!("decrypt data failure.")))
}
pub fn decrypt_multiple(salt: &[u8], pin: &str, ctext: Vec<&[u8]>) -> anyhow::Result<Vec<Vec<u8>>> {
pub fn _decrypt_multiple(
salt: &[u8],
pin: &str,
ckey: &[u8],
ctext: Vec<&[u8]>,
) -> anyhow::Result<Vec<Vec<u8>>> {
let cipher = build_cipher(salt, pin);
let mut nonce = Sha256::new();
nonce.update(pin.as_bytes());
nonce.update(&FIX_PADDING);
let res = nonce.finalize();
let mut hasher = Sha256::new();
hasher.update(pin.as_bytes());
hasher.update(&FIX_PADDING);
let res = hasher.finalize();
let nonce = GenericArray::from_slice(&res[0..12]); // 96-bit key.
let key = cipher
.decrypt(nonce, ckey)
.or(Err(anyhow!("decrypt data failure.")))?;
let c_cipher = build_keycipher(&key);
let mut c_hasher = Sha256::new();
c_hasher.update(salt);
c_hasher.update(&FIX_PADDING);
let c_res = c_hasher.finalize();
let c_nonce = GenericArray::from_slice(&c_res[0..12]); // 96-bit key.
let mut pbytes = vec![];
for c in ctext {
pbytes.push(
cipher
.decrypt(nonce, c)
c_cipher
.decrypt(c_nonce, c)
.or(Err(anyhow!("decrypt data failure.")))?,
);
}

Loading…
Cancel
Save