diff --git a/rpc/src/endpoint/tonapi/error.rs b/rpc/src/endpoint/error.rs similarity index 100% rename from rpc/src/endpoint/tonapi/error.rs rename to rpc/src/endpoint/error.rs diff --git a/rpc/src/endpoint/mod.rs b/rpc/src/endpoint/mod.rs index 5764990b7..8c334218c 100644 --- a/rpc/src/endpoint/mod.rs +++ b/rpc/src/endpoint/mod.rs @@ -11,9 +11,12 @@ pub use self::jrpc::JrpcEndpointCache; pub use self::proto::ProtoEndpointCache; use crate::state::RpcState; +mod error; mod jrpc; mod proto; mod tonapi; +mod toncenter; +mod utils; pub struct RpcEndpoint { listener: TcpListener, @@ -47,6 +50,7 @@ impl RpcEndpoint { .route("/rpc", post(common_route)) .route("/proto", post(common_route)) .nest("/v2", tonapi::router()) + .nest("/api/v2", toncenter::router()) .layer(service) .with_state(self.state); diff --git a/rpc/src/endpoint/tonapi/accounts.rs b/rpc/src/endpoint/tonapi/accounts.rs index b1d01de2b..f56b70be7 100644 --- a/rpc/src/endpoint/tonapi/accounts.rs +++ b/rpc/src/endpoint/tonapi/accounts.rs @@ -3,9 +3,8 @@ use axum::routing::get; use axum::Json; use everscale_types::models::StdAddr; -use super::error::Error; use super::responses::{status_to_string, GetAccountResponse}; -use crate::endpoint::tonapi::error::Result; +use crate::endpoint::error::{Error, Result}; use crate::state::LoadedAccountState; use crate::RpcState; diff --git a/rpc/src/endpoint/tonapi/blockchain.rs b/rpc/src/endpoint/tonapi/blockchain.rs index 53cac4d88..8d951cc0a 100644 --- a/rpc/src/endpoint/tonapi/blockchain.rs +++ b/rpc/src/endpoint/tonapi/blockchain.rs @@ -12,7 +12,6 @@ use everscale_types::models::{ use num_bigint::BigInt; use tycho_vm::{GasParams, NaN, OwnedCellSlice, RcStackValue, SmcInfoBase, VmState}; -use super::error::Error; use super::requests::{ExecMethodArgs, Pagination, SendMessageRequest}; use super::responses::{ bounce_phase_to_string, status_to_string, AccountResponse, ExecGetMethodResponse, @@ -20,7 +19,7 @@ use super::responses::{ TvmStackRecord, }; use super::utils::crc_16; -use crate::endpoint::tonapi::error::Result; +use crate::endpoint::error::{Error, Result}; use crate::state::LoadedAccountState; use crate::RpcState; diff --git a/rpc/src/endpoint/tonapi/mod.rs b/rpc/src/endpoint/tonapi/mod.rs index 5e70df887..7ca92360e 100644 --- a/rpc/src/endpoint/tonapi/mod.rs +++ b/rpc/src/endpoint/tonapi/mod.rs @@ -2,7 +2,6 @@ use crate::RpcState; mod accounts; mod blockchain; -mod error; mod requests; mod responses; mod utils; diff --git a/rpc/src/endpoint/toncenter/mod.rs b/rpc/src/endpoint/toncenter/mod.rs new file mode 100644 index 000000000..0b1d72569 --- /dev/null +++ b/rpc/src/endpoint/toncenter/mod.rs @@ -0,0 +1,325 @@ +use std::str::FromStr; +use std::vec; + +use axum::extract::{Path, Query, State}; +use axum::routing::{get, post}; +use axum::Json; +use everscale_types::boc::Boc; +use everscale_types::cell::CellBuilder; +use everscale_types::models::{ + AccountState, IntAddr, MsgInfo, OwnedMessage, StateInit, StdAddr, Transaction, +}; +use num_bigint::BigInt; +use requests::{AddressInformationQuery, ExecMethodArgs, GetTransactionsQuery, SendMessageRequest}; +use responses::{ + status_to_string, AddressInformation, AddressResponse, BlockId, TonCenterResponse, + TransactionId, TransactionResponse, TransactionsResponse, TvmStackRecord, +}; +use tycho_vm::{GasParams, NaN, OwnedCellSlice, RcStackValue, SmcInfoBase, VmState}; + +use super::utils::crc_16; +use crate::endpoint::error::{Error, Result}; +use crate::state::LoadedAccountState; +use crate::RpcState; + +mod requests; +mod responses; + +pub fn router() -> axum::Router { + axum::Router::new() + .route("/getAddressInformation", get(get_address_information)) + .route("/getTransactions", get(get_blockchain_account_transactions)) + .route("/runGetMethod", get(exec_get_method_for_blockchain_account)) + .route("/sendBoc", post(send_blockchain_message)) +} + +async fn get_address_information( + Query(address): Query, + State(state): State, +) -> Result>> { + let Ok(item) = state.get_account_state(&address.address) else { + return Ok(Json(TonCenterResponse { + ok: false, + result: None, + error: Some("account not found".to_string()), + code: None, + })); + }; + + match &item { + &LoadedAccountState::NotFound { .. } => Err(Error::NotFound("account not found")), + LoadedAccountState::Found { state, .. } => { + let Ok(account) = state.load_account() else { + return Ok(Json(TonCenterResponse { + ok: false, + result: None, + error: Some("account not found".to_string()), + code: None, + })); + }; + match account { + Some(loaded) => { + let status = loaded.state.status(); + let (code, data, frozen_hash) = match loaded.state { + AccountState::Active(StateInit { code, data, .. }) => { + (code.map(Boc::encode_hex), data.map(Boc::encode_hex), None) + } + AccountState::Uninit => (None, None, None), + AccountState::Frozen(hash_bytes) => { + (None, None, Some(hash_bytes.to_string())) + } + }; + + Ok(Json(TonCenterResponse { + ok: true, + result: Some(AddressInformation { + type_field: "raw.fullAccountState".to_string(), + balance: loaded.balance.tokens.into_inner() as u64, + code, + data, + last_transaction_id: TransactionId { + type_field: "internal.transactionId".to_string(), + lt: loaded.last_trans_lt.to_string(), + hash: Some(state.last_trans_hash.to_string()), + }, + block_id: BlockId { + type_field: "ton.blockIdExt".to_string(), + workchain: 0, // TODO: fix workchain + shard: "-9223372036854775808".to_string(), /* TODO: fix block_id shard */ + seqno: 44906584, // TODO: fix block_id seqno + root_hash: "tgbt6ZqC1bYk4m9yMYQDHLUeNrmWRNtb5r/mhHoB9RA=" + .to_string(), // TODO: fix block_id root_hash + file_hash: "4vkWnBWGIHiDy1Z7W2m+zcd/1HvZkGB1lCthVs07woM=" + .to_string(), // TODO: fix block_id file_hash + }, + frozen_hash, + sync_utime: 0, // TODO: fix sync utime + extra: "1739453311.547493:11:0.8618085632029536".to_string(), /* TODO: fix extra */ + state: status_to_string(status), + }), + error: None, + code: None, + })) + } + None => Ok(Json(TonCenterResponse { + ok: false, + result: None, + error: Some("account not found".to_string()), + code: None, + })), + } + } + } +} + +async fn get_blockchain_account_transactions( + Path(address): Path, + Query(pagination): Query, + State(state): State, +) -> Result>>> { + let limit = pagination + .limit + .unwrap_or(TransactionsResponse::DEFAULT_LIMIT); + + if limit == 0 { + return Ok(Json(TonCenterResponse { + ok: true, + result: Some(vec![]), + error: None, + code: None, + })); + } + + if limit > TransactionsResponse::MAX_LIMIT { + return Err(Error::BadRequest("limit is too large")); + } + + let list = state.get_transactions(&address, pagination.lt)?; + let transactions = list + .map(|item| { + let root = Boc::decode(item).map_err(|e| anyhow::format_err!(e.to_string()))?; + let hash = *root.repr_hash(); + let t = root.parse::()?; + + let in_msg = if let Some(in_msg) = &t.in_msg { + let hash = *in_msg.repr_hash(); + let in_msg = in_msg.parse::()?; + Some((in_msg.info, Some(in_msg.body.0), hash).into()) + } else { + None + }; + + let mut out_msgs = vec![]; + + for out_msg_cell in t.out_msgs.values() { + let out_msg_cell = out_msg_cell?; + let out_msg_hash = *out_msg_cell.repr_hash(); + let out_msg_info = out_msg_cell.parse::()?; + out_msgs.push((out_msg_info, None, out_msg_hash).into()); + } + + Ok(TransactionResponse { + type_field: "raw.transaction".to_string(), + address: AddressResponse { + type_field: "accountAddress".to_string(), + account_address: pagination.address.to_string(), + }, + data: Boc::encode_hex(root), + transaction_id: TransactionId { + type_field: "internal.transactionId".to_string(), + lt: t.lt.to_string(), + hash: Some(hash.to_string()), + }, + fee: (t.total_fees.tokens.into_inner() as u64).to_string(), // CHECK + storage_fee: 0.to_string(), // TODO: fill with correct storage_fee + other_fee: 0.to_string(), // TODO: fill with correct other_fee + utime: t.now, + in_msg, + out_msgs, + }) as Result + }) + .take(limit as _) + .collect::, _>>()?; + + Ok(Json(TonCenterResponse { + ok: true, + result: Some(transactions), + error: None, + code: None, + })) +} + +async fn exec_get_method_for_blockchain_account( + Query(args): Query, + State(state): State, +) -> Result>> { + let item = state.get_account_state(&args.address)?; + + match &item { + &LoadedAccountState::NotFound { .. } => Ok(Json(TonCenterResponse { + ok: false, + result: None, + error: Some("account not found".to_string()), + code: None, + })), + LoadedAccountState::Found { state, .. } => { + let account = state.load_account()?; + match account { + Some(loaded) => { + match loaded.state { + AccountState::Active(StateInit { code, data, .. }) => { + let smc_info = SmcInfoBase::new() + // .with_now(1733142533) // TODO: check if needed? + // .with_block_lt(50899537000013) // TODO: check if needed? + // .with_tx_lt(50899537000013) // TODO: check if needed? + .with_account_balance(loaded.balance) + .with_account_addr(IntAddr::Std(args.address.clone())) + .require_ton_v4(); + + let crc = crc_16(args.method.as_bytes()); + let method_id = crc as u32 | 0x10000; + + let mut stack = vec![RcStackValue::new_dyn_value( + OwnedCellSlice::from(CellBuilder::build_from(&args.address)?), + )]; + + let mut stack_response = vec![TvmStackRecord::Slice { + slice: args.address.to_string(), + }]; + + for args_stack in args.stack { + for arg in args_stack { + // TODO: check args + if arg == "NaN" { + stack.push(RcStackValue::new_dyn_value(NaN)); + stack_response.push(TvmStackRecord::Nan); + continue; + } + + if arg == "Null" { + stack.push(RcStackValue::new_dyn_value(())); + stack_response.push(TvmStackRecord::Null); + continue; + } + + if let Ok(v) = BigInt::from_str(&arg) { + stack.push(RcStackValue::new_dyn_value(v)); + stack_response.push(TvmStackRecord::Num { num: arg }); + continue; + } + + if let Ok(v) = hex::decode(&arg) { + if let Some(v) = BigInt::parse_bytes(&v, 16) { + stack.push(RcStackValue::new_dyn_value(v)); + stack_response.push(TvmStackRecord::Num { num: arg }); + continue; + } + } + + if let Ok(cell) = Boc::decode_base64(&arg) { + stack.push(RcStackValue::new_dyn_value( + OwnedCellSlice::from(cell), + )); + stack_response.push(TvmStackRecord::Cell { cell: arg }); + continue; + } + if let Ok(cell) = Boc::decode(&arg) { + stack.push(RcStackValue::new_dyn_value( + OwnedCellSlice::from(cell), + )); + stack_response.push(TvmStackRecord::Slice { slice: arg }); + continue; + } + if let Ok(address) = IntAddr::from_str(&arg) { + stack.push(RcStackValue::new_dyn_value( + OwnedCellSlice::from(CellBuilder::build_from( + &address, + )?), + )); + stack_response.push(TvmStackRecord::Slice { slice: arg }); + continue; + } + } + } + + stack.push(RcStackValue::new_dyn_value(BigInt::from(method_id))); + stack_response.push(TvmStackRecord::Num { + num: method_id.to_string(), + }); + + let mut vm_state = VmState::builder() + .with_smc_info(smc_info) + .with_stack(stack) + .with_code(code.unwrap_or_default()) + .with_data(data.unwrap_or_default()) + .with_gas(GasParams::getter()) + .build(); + + let exit_code = vm_state.run(); + let success = exit_code == 0; + + Ok(Json(TonCenterResponse { + ok: success, + result: None, // TODO: fill with correct result + error: None, + code: None, + })) + } + _ => Err(Error::BadRequest("account has wrong state")), + } + } + None => Err(Error::NotFound("account not found")), + } + } + } +} + +async fn send_blockchain_message( + State(state): State, + Json(input): Json, +) -> Result> { + let data = + hex::decode(input.boc).map_err(|_e| Error::BadRequest("can not parse boc from hex"))?; + state.broadcast_external_message(&data).await; + Ok(Json(())) +} diff --git a/rpc/src/endpoint/toncenter/requests/mod.rs b/rpc/src/endpoint/toncenter/requests/mod.rs new file mode 100644 index 000000000..6e9e332ca --- /dev/null +++ b/rpc/src/endpoint/toncenter/requests/mod.rs @@ -0,0 +1,42 @@ +use everscale_types::models::StdAddr; +use serde::Deserialize; + +#[derive(Deserialize)] +pub(crate) struct GetTransactionsQuery { + pub(crate) address: StdAddr, + pub(crate) limit: Option, + pub(crate) lt: Option, + #[allow(unused)] + pub(crate) to_lt: Option, + #[allow(unused)] + pub(crate) hash: Option, + #[allow(unused)] + pub(crate) archival: Option, +} + +#[derive(Deserialize)] +pub(crate) struct AddressInformationQuery { + pub address: StdAddr, +} + +#[derive(Debug, Copy, Clone, Deserialize, Eq, PartialEq, Hash)] +pub(crate) enum Direction { + #[serde(rename = "asc")] + Ascending, + #[serde(rename = "desc")] + Descending, +} + +#[derive(Default, Debug, Clone, PartialEq, Deserialize)] +pub struct SendMessageRequest { + pub boc: String, +} + +#[derive(Deserialize)] +pub(crate) struct ExecMethodArgs { + pub address: StdAddr, + pub method: String, + pub stack: Vec>, + #[allow(dead_code)] + pub seqno: i64, +} diff --git a/rpc/src/endpoint/toncenter/responses/mod.rs b/rpc/src/endpoint/toncenter/responses/mod.rs new file mode 100644 index 000000000..f671481ef --- /dev/null +++ b/rpc/src/endpoint/toncenter/responses/mod.rs @@ -0,0 +1,291 @@ +use std::collections::HashMap; + +use everscale_types::boc::Boc; +use everscale_types::cell::{Cell, HashBytes}; +use everscale_types::models::{AccountStatus, MsgInfo, StorageInfo}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct TonCenterResponse { + pub ok: bool, + pub result: Option, + pub error: Option, + pub code: Option, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct AddressInformation { + #[serde(rename = "@type")] + pub type_field: String, + pub balance: u64, + pub code: Option, + pub data: Option, + pub last_transaction_id: TransactionId, + pub block_id: BlockId, + pub frozen_hash: Option, + pub sync_utime: i64, + #[serde(rename = "@extra")] + pub extra: String, + pub state: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct BlockId { + #[serde(rename = "@type")] + pub type_field: String, + pub workchain: i64, + pub shard: String, + pub seqno: i64, + pub root_hash: String, + pub file_hash: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct GetAccountResponse { + pub address: String, + pub balance: u64, + pub extra_balance: Option>, + pub currencies_balance: Option>, + pub last_activity: u64, + pub status: String, + pub interfaces: Option>, + pub name: Option, + pub is_scam: Option, + pub icon: Option, + pub memo_required: Option, + pub get_methods: Vec, + pub is_suspended: Option, + pub is_wallet: bool, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct AccountResponse { + pub address: String, + pub balance: u64, + pub extra_balance: Option>, + pub code: Option, + pub data: Option, + pub last_transaction_lt: u64, + pub last_transaction_hash: Option, + pub frozen_hash: Option, + pub status: String, + pub storage: StorageResponse, + pub libraries: Option>, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct StorageResponse { + pub used_cells: u64, + pub used_bits: u64, + pub used_public_cells: u64, + pub last_paid: u32, + pub due_payment: Option, +} + +impl From for StorageResponse { + fn from(info: StorageInfo) -> Self { + Self { + used_cells: info.used.cells.into(), + used_bits: info.used.bits.into(), + used_public_cells: info.used.public_cells.into(), + last_paid: info.last_paid, + due_payment: info.due_payment.map(|v| v.into_inner() as u64), + } + } +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct LibraryResponse { + pub public: bool, + pub root: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct TransactionsResponse { + pub transactions: Vec, +} + +impl TransactionsResponse { + pub const MAX_LIMIT: u8 = 100; + pub const DEFAULT_LIMIT: u8 = 10; +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct TransactionResponse { + #[serde(rename = "@type")] + pub type_field: String, + pub address: AddressResponse, + pub utime: u32, + pub data: String, + pub transaction_id: TransactionId, + pub fee: String, + pub storage_fee: String, + pub other_fee: String, + pub in_msg: Option, + pub out_msgs: Vec, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct AddressResponse { + #[serde(rename = "@type")] + pub type_field: String, + pub account_address: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct TransactionId { + #[serde(rename = "@type")] + pub type_field: String, + pub lt: String, + pub hash: Option, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct MsgResponse { + #[serde(rename = "@type")] + pub type_field: String, + pub hash: String, + pub source: Option, + pub destination: Option, + pub value: String, + pub extra_currencies: Vec, + pub fwd_fee: String, + pub ihr_fee: String, + pub created_lt: String, + pub body_hash: String, + pub msg_data: MsgDataResponse, + pub message: String, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(tag = "@type")] +pub enum MsgDataResponse { + #[serde(rename = "msg.dataText")] + Text { text: String }, + #[serde(rename = "msg.dataRaw")] + Body { + body: Option, + init_state: String, + }, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct TransactionAccountResponse { + pub address: String, + pub name: Option, + pub is_scam: bool, + pub icon: Option, + pub is_wallet: bool, +} + +impl From<(MsgInfo, Option, HashBytes)> for MsgResponse { + fn from(msg: (MsgInfo, Option, HashBytes)) -> Self { + let (msg, body, hash) = msg; + match msg { + MsgInfo::Int(int_msg_info) => { + MsgResponse { + type_field: "raw.message".to_string(), + hash: hash.to_string(), + source: Some(int_msg_info.src.to_string()), + destination: Some(int_msg_info.dst.to_string()), + created_lt: int_msg_info.created_lt.to_string(), + value: (int_msg_info.value.tokens.into_inner() as u64).to_string(), + fwd_fee: (int_msg_info.fwd_fee.into_inner() as u64).to_string(), + ihr_fee: (int_msg_info.ihr_fee.into_inner() as u64).to_string(), + extra_currencies: vec![], + body_hash: body.clone().unwrap_or_default().repr_hash().to_string(), + msg_data: MsgDataResponse::Body { + body: body.clone().map(Boc::encode_hex), + init_state: "".to_string(), + }, + message: body.map(Boc::encode_hex).unwrap_or_default(), /* TODO: fill with correct values */ + } + } + MsgInfo::ExtIn(ext_in_msg) => { + MsgResponse { + type_field: "raw.message".to_string(), + hash: hash.to_string(), + source: None, + destination: Some(ext_in_msg.dst.to_string()), + created_lt: "0".to_string(), + value: "0".to_string(), + fwd_fee: "0".to_string(), + ihr_fee: "0".to_string(), + extra_currencies: vec![], + body_hash: body.clone().unwrap_or_default().repr_hash().to_string(), + msg_data: MsgDataResponse::Body { + body: body.clone().map(Boc::encode_hex), + init_state: "".to_string(), + }, + message: body.map(Boc::encode_hex).unwrap_or_default(), /* TODO: fill with correct values */ + } + } + MsgInfo::ExtOut(ext_out_msg) => MsgResponse { + type_field: "raw.message".to_string(), + hash: hash.to_string(), + source: Some(ext_out_msg.src.to_string()), + destination: None, + created_lt: "0".to_string(), + value: "0".to_string(), + fwd_fee: "0".to_string(), + ihr_fee: "0".to_string(), + extra_currencies: vec![], + body_hash: body.clone().unwrap_or_default().repr_hash().to_string(), + msg_data: MsgDataResponse::Body { + body: body.clone().map(Boc::encode_hex), + init_state: "".to_string(), + }, + message: body.map(Boc::encode_hex).unwrap_or_default(), /* TODO: fill with correct values */ + }, + } + } +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ValueExtraResponse { + pub amount: String, + pub preview: PreviewResponse, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct PreviewResponse { + pub id: u64, + pub symbol: String, + pub decimals: u64, + pub image: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct InitResponse { + pub boc: String, + pub interfaces: Vec, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ExecGetMethodResponse { + pub success: bool, + pub exit_code: i32, + pub stack: Vec, + pub decoded: Option, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(tag = "type")] +#[serde(rename_all = "lowercase")] +pub enum TvmStackRecord { + Cell { cell: String }, + Slice { slice: String }, + Num { num: String }, + Null, + Nan, +} + +pub fn status_to_string(account_status: AccountStatus) -> String { + match account_status { + AccountStatus::Active => "active".to_string(), + AccountStatus::Frozen => "frozen".to_string(), + AccountStatus::Uninit => "uninitialized".to_string(), + AccountStatus::NotExists => "not_exists".to_string(), + } +} diff --git a/rpc/src/endpoint/utils.rs b/rpc/src/endpoint/utils.rs new file mode 100644 index 000000000..13820afb1 --- /dev/null +++ b/rpc/src/endpoint/utils.rs @@ -0,0 +1,33 @@ +const CRC_TABLE: [u16; 256] = [ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, + 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, + 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, + 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, + 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, + 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, + 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, + 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, + 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, + 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, + 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, + 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0, +]; + +pub fn crc_16(data: &[u8]) -> u16 { + let mut crc: u32 = 0; + for c in data { + let t = c ^ ((crc >> 8) as u8); + crc = (CRC_TABLE[t as usize] ^ ((crc << 8) as u16)) as u32; + } + crc as u16 +}