Compare commits

..

6 Commits

Author SHA1 Message Date
9c5dd8b850
updated all packages with new dtpm 2025-04-17 17:32:24 +05:30
ac55653e64
fix: increase retry attempts while app deployment
improve ux while deploying with progress update
2025-04-15 23:53:06 +00:00
48226a3341
refactor: update proto
remove base 64 encoding
updated package with new dtpm
remove unused crate and error
2025-04-15 23:07:16 +00:00
20165a6e84
refactor: DTMP grpc connction and methods
updated Dtpm client connection
remove mr_signer from connetion
reuse dtpm client connection
pure function for grpc methods
some error handling
2025-04-11 01:58:00 +05:30
301f428500
feat: upload directory in launch config
stream file upload
dtpm grpc compression
new actix static server package
updated config with directory sample
2025-04-10 10:33:22 +00:00
60113bc538
fix println with eprintln for error 2025-04-03 13:22:41 +05:30
8 changed files with 40 additions and 205 deletions

151
Cargo.lock generated

@ -732,38 +732,6 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "camino"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3"
dependencies = [
"serde",
]
[[package]]
name = "cargo-platform"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea"
dependencies = [
"serde",
]
[[package]]
name = "cargo_metadata"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba"
dependencies = [
"camino",
"cargo-platform",
"semver 1.0.24",
"serde",
"serde_json",
"thiserror 2.0.11",
]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.11" version = "1.2.11"
@ -885,26 +853,6 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "const_format"
version = "0.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd"
dependencies = [
"const_format_proc_macros",
]
[[package]]
name = "const_format_proc_macros"
version = "0.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.4" version = "0.9.4"
@ -1133,7 +1081,6 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"serde_yaml", "serde_yaml",
"shadow-rs",
"tabled", "tabled",
"thiserror 2.0.11", "thiserror 2.0.11",
"tokio", "tokio",
@ -1607,19 +1554,6 @@ version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "git2"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5220b8ba44c68a9a7f7a7659e864dd73692e417ef0211bea133c7b74e031eeb9"
dependencies = [
"bitflags",
"libc",
"libgit2-sys",
"log",
"url",
]
[[package]] [[package]]
name = "glob" name = "glob"
version = "0.3.2" version = "0.3.2"
@ -2060,12 +1994,6 @@ version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
[[package]]
name = "is_debug"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fe266d2e243c931d8190177f20bf7f24eed45e96f39e87dc49a27b32d12d407"
[[package]] [[package]]
name = "is_terminal_polyfill" name = "is_terminal_polyfill"
version = "1.70.1" version = "1.70.1"
@ -2174,18 +2102,6 @@ version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "libgit2-sys"
version = "0.18.1+1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1dcb20f84ffcdd825c7a311ae347cce604a6f084a767dec4a4929829645290e"
dependencies = [
"cc",
"libc",
"libz-sys",
"pkg-config",
]
[[package]] [[package]]
name = "libloading" name = "libloading"
version = "0.8.6" version = "0.8.6"
@ -2213,18 +2129,6 @@ dependencies = [
"redox_syscall", "redox_syscall",
] ]
[[package]]
name = "libz-sys"
version = "1.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.14" version = "0.4.14"
@ -2361,15 +2265,6 @@ dependencies = [
"libm", "libm",
] ]
[[package]]
name = "num_threads"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "object" name = "object"
version = "0.36.7" version = "0.36.7"
@ -3216,9 +3111,6 @@ name = "semver"
version = "1.0.24" version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "semver-parser" name = "semver-parser"
@ -3329,21 +3221,6 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "shadow-rs"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d5625ed609cf66d7e505e7d487aca815626dc4ebb6c0dd07637ca61a44651a6"
dependencies = [
"cargo_metadata",
"const_format",
"git2",
"is_debug",
"serde_json",
"time",
"tzdb",
]
[[package]] [[package]]
name = "shlex" name = "shlex"
version = "1.3.0" version = "1.3.0"
@ -3614,9 +3491,7 @@ checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
dependencies = [ dependencies = [
"deranged", "deranged",
"itoa", "itoa",
"libc",
"num-conv", "num-conv",
"num_threads",
"powerfmt", "powerfmt",
"serde", "serde",
"time-core", "time-core",
@ -3916,32 +3791,6 @@ version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "tz-rs"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1450bf2b99397e72070e7935c89facaa80092ac812502200375f1f7d33c71a1"
[[package]]
name = "tzdb"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0be2ea5956f295449f47c0b825c5e109022ff1a6a53bb4f77682a87c2341fbf5"
dependencies = [
"iana-time-zone",
"tz-rs",
"tzdb_data",
]
[[package]]
name = "tzdb_data"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0604b35c1f390a774fdb138cac75a99981078895d24bcab175987440bbff803b"
dependencies = [
"tz-rs",
]
[[package]] [[package]]
name = "ucd-trie" name = "ucd-trie"
version = "0.1.7" version = "0.1.7"

@ -32,11 +32,9 @@ hyper-rustls = { version = "0.27.5", features = ["http2"] }
openssl = { version = "0.10.71", features = ["vendored"] } openssl = { version = "0.10.71", features = ["vendored"] }
tokio-retry = "0.3.0" tokio-retry = "0.3.0"
detee-sgx = { git = "ssh://git@gitea.detee.cloud/testnet/detee-sgx.git", branch = "hratls", features=["hratls", "qvl"] } detee-sgx = { git = "ssh://git@gitea.detee.cloud/testnet/detee-sgx.git", branch = "hratls", features=["hratls", "qvl"] }
shadow-rs = { version = "1.1.1", features = ["metadata"] }
detee-shared = { git = "ssh://git@gitea.detee.cloud/testnet/proto.git", branch = "feat_dir_support_dtpm_config" } detee-shared = { git = "ssh://git@gitea.detee.cloud/testnet/proto.git", branch = "feat_dir_support_dtpm_config" }
# detee-shared = { path = "../detee-shared" } # detee-shared = { path = "../detee-shared" }
[build-dependencies] [build-dependencies]
shadow-rs = "1.1.1"
tonic-build = "0.12" tonic-build = "0.12"

@ -1,5 +0,0 @@
use shadow_rs::ShadowBuilder;
fn main() {
ShadowBuilder::builder().deny_const(Default::default()).build().unwrap();
}

@ -56,14 +56,11 @@ snpguest --help > /dev/null \
} }
try_countdown=20; try_countdown=20;
echo -n Trying $server
while [[ $try_countdown -gt 0 ]]; do while [[ $try_countdown -gt 0 ]]; do
echo -n .
curl --max-time 1 -k "https://$server" > /dev/null 2>&1 && break curl --max-time 1 -k "https://$server" > /dev/null 2>&1 && break
sleep 1 sleep 1
((try_countdown--)) ((try_countdown--))
done done
echo
openssl s_client -connect "$server" </dev/null \ openssl s_client -connect "$server" </dev/null \
| sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' > "$server_crt" | sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' > "$server_crt"
@ -103,9 +100,9 @@ echo_blue "Verifying AMD signature in attestation report..."
chip_id_hash=$( snpguest display report "$server_report" \ chip_id_hash=$( snpguest display report "$server_report" \
| grep "Chip ID:" -A 4 | tail -3 | tr '\n' ' ' | sed 's/\s//g' \ | grep "Chip ID:" -A 4 | tail -3 | tr '\n' ' ' | sed 's/\s//g' \
| md5sum | awk '{ print $1 }') | md5sum | awk '{ print $1 }')
tcb_hash=$(grep -e "Committed TCB" -e "Reported TCB" -A 10 "$server_report" | microcode=$( snpguest display report "$server_report" |
md5sum | awk '{ print $1 }') grep "Launch TCB:" -A 6 | grep "Microcode:" | awk '{ print $2 }' )
vcek_path="${cert_dir}/${chip_id_hash}-${tcb_hash}.vcek.pem" vcek_path="${cert_dir}/${chip_id_hash}-${microcode}.vcek.pem"
amd_certs_dir="${cert_dir}/amd_certs_${server}" amd_certs_dir="${cert_dir}/amd_certs_${server}"
mkdir -p "$amd_certs_dir" mkdir -p "$amd_certs_dir"

@ -12,8 +12,6 @@ More information can be found at https://detee.ltd
Feel free to browser applications bundles or VM disks available for immediate deployment."#; Feel free to browser applications bundles or VM disks available for immediate deployment."#;
shadow_rs::shadow!(build);
fn main() { fn main() {
// TODO: figure if there is a more elegant way to solve this than calling default_provider in main // TODO: figure if there is a more elegant way to solve this than calling default_provider in main
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
@ -52,7 +50,7 @@ fn main() {
fn clap_cmd() -> Command { fn clap_cmd() -> Command {
Command::new("detee-cli") Command::new("detee-cli")
.version(build::CLAP_LONG_VERSION) .version("0.0.1")
.author("https://detee.ltd") .author("https://detee.ltd")
.about(ABOUT) .about(ABOUT)
.arg( .arg(
@ -284,6 +282,7 @@ fn clap_cmd() -> Command {
.required(true) .required(true)
) )
) )
/*
.subcommand(Command::new("report").about("report a node for poor performance") .subcommand(Command::new("report").about("report a node for poor performance")
.arg( .arg(
Arg::new("pubkey") Arg::new("pubkey")
@ -303,6 +302,7 @@ fn clap_cmd() -> Command {
.help("detail the performance issue you experienced") .help("detail the performance issue you experienced")
) )
) )
*/
) )
.subcommand(Command::new("vm") .subcommand(Command::new("vm")
.about("virtual machines that run on AMD SEV-SNP nodes") .about("virtual machines that run on AMD SEV-SNP nodes")

@ -13,8 +13,6 @@ It allows you to:
The admin pubkeys are hardcoded in the brain."#; The admin pubkeys are hardcoded in the brain."#;
shadow_rs::shadow!(build);
fn main() { fn main() {
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
let log_level = match std::env::var("LOG_LEVEL") { let log_level = match std::env::var("LOG_LEVEL") {
@ -28,7 +26,7 @@ fn main() {
env_logger::builder().filter_level(log_level).format_timestamp(None).init(); env_logger::builder().filter_level(log_level).format_timestamp(None).init();
let cmd = Command::new("super-detee-cli") let cmd = Command::new("super-detee-cli")
.version(build::CLAP_LONG_VERSION) .version("0.0.1")
.author("https://detee.ltd") .author("https://detee.ltd")
.about(ABOUT) .about(ABOUT)
.subcommand( .subcommand(

@ -39,13 +39,11 @@ pub fn handle_app_nodes(matches: &ArgMatches) {
let ip: String = subcom_args.get_one::<String>("ip").unwrap().clone(); let ip: String = subcom_args.get_one::<String>("ip").unwrap().clone();
cli_print(inspect_node(ip).map_err(Into::into)); cli_print(inspect_node(ip).map_err(Into::into));
} }
Some(("report", subcom_args)) => { Some(("report", _)) => {
let node_pubkey: String = subcom_args.get_one::<String>("pubkey").unwrap().clone(); // let node_pubkey: String = path_subcommand.get_one::<String>("pubkey").unwrap().clone();
let contract_uuid: String = subcom_args.get_one::<String>("contract").unwrap().clone(); // let contract_uuid: String = path_subcommand.get_one::<String>("contract").unwrap().clone();
let reason: String = subcom_args.get_one::<String>("reason").unwrap().clone(); // let reason: String = path_subcommand.get_one::<String>("reason").unwrap().clone();
cli_print( todo!()
crate::general::report_node(node_pubkey, contract_uuid, reason).map_err(Into::into),
)
} }
_ => { _ => {
eprintln!("Available commands are search, inspec and report. Use --help for more information.") eprintln!("Available commands are search, inspec and report. Use --help for more information.")

@ -398,32 +398,32 @@ pub fn calculate_nanolp(
lazy_static! { lazy_static! {
static ref DEFAULT_DTRFS: Dtrfs = Dtrfs { static ref DEFAULT_DTRFS: Dtrfs = Dtrfs {
name: "dtrfs-6.14.2-arch1-1".to_string(), name: "dtrfs-6.13.8-arch1-1".to_string(),
vendor: "ghe0".to_string(), vendor: "ghe0".to_string(),
dtrfs_url: "http://registry.detee.ltd/detee-archtop-6.14.2-arch1-1.cpio.gz".to_string(), dtrfs_url: "http://registry.detee.ltd/detee-archtop-6.13.8-arch1-1.cpio.gz".to_string(),
dtrfs_sha: "d207644ee60d54009b6ecdfb720e2ec251cde31774dd249fcc7435aca0377990".to_string(), dtrfs_sha: "b5f408d00e2b93dc594fed3a7f2466a9878802ff1c7ae502247471cd06728a45".to_string(),
kernel_url: "http://registry.detee.ltd/vmlinuz-linux-6.14.2-arch1-1".to_string(), kernel_url: "http://registry.detee.ltd/vmlinuz-linux-6.13.8-arch1-1".to_string(),
kernel_sha: "e765e56166ef321b53399b9638584d1279821dbe3d46191c1f66bbaa075e7919".to_string() kernel_sha: "e49c8587287b21df7600c04326fd7393524453918c14d67f73757dc769a13542".to_string()
}; };
static ref DEFAULT_ARCHLINUX: Distro = Distro { static ref DEFAULT_ARCHLINUX: Distro = Distro {
name: "archlinux_2025-04-03".to_string(), name: "archlinux_2025-02-21".to_string(),
vendor: "gheorghe".to_string(), vendor: "gheorghe".to_string(),
template_url: "http://registry.detee.ltd/detee_arch_2025-04-03.fsa".to_string(), template_url: "http://registry.detee.ltd/detee_arch_2025-02-21.fsa".to_string(),
template_sha: "7fdb19d9325c63d246140c984dc3764538f6ea329ed877e947993ea7bc8c2067" template_sha: "257edbf1e3b949b895c422befc8890c85dfae1ad3d35661010c9aaa173ba9fc4"
.to_string() .to_string()
}; };
static ref DEFAULT_UBUNTU: Distro = Distro { static ref DEFAULT_UBUNTU: Distro = Distro {
name: "ubuntu_2025-04-03".to_string(), name: "ubuntu_2025-02-28".to_string(),
vendor: "gheorghe".to_string(), vendor: "gheorghe".to_string(),
template_url: "http://registry.detee.ltd/detee_ubuntu_2025-04-03.fsa".to_string(), template_url: "http://registry.detee.ltd/detee_ubuntu_2025-02-28.fsa".to_string(),
template_sha: "324895a7a1788e43253cf9699aa446df1a5519fe072917cedcc4ed356546e34a" template_sha: "faa8bd38d02ca9b6ee69d7f5128ed9ccab42bdbfa69f688b9947e8e5c9e5d133"
.to_string() .to_string()
}; };
static ref DEFAULT_FEDORA: Distro = Distro { static ref DEFAULT_FEDORA: Distro = Distro {
name: "fedora_2025-04-03".to_string(), name: "fedora_2025-02-21".to_string(),
vendor: "gheorghe".to_string(), vendor: "gheorghe".to_string(),
template_url: "http://registry.detee.ltd/detee_fedora_2025-04-03.fsa".to_string(), template_url: "http://registry.detee.ltd/detee_fedora_2025-02-21.fsa".to_string(),
template_sha: "75a98c3744552bbf5f8e9c6a271cd0f382e1d9a846f5d577767b39293b8efda9" template_sha: "c0fdd08d465939077ef8ed746903005fc190af12cdf70917cc8c6f872da85777"
.to_string() .to_string()
}; };
static ref ALTERNATIVE_INIT: Vec<Dtrfs> = vec![ static ref ALTERNATIVE_INIT: Vec<Dtrfs> = vec![
@ -438,36 +438,36 @@ lazy_static! {
.to_string() .to_string()
}, },
Dtrfs { Dtrfs {
name: "dtrfs-6.13.8-arch1-1".to_string(), name: "dtrfs-6.13.6-arch1-1".to_string(),
vendor: "ghe0".to_string(), vendor: "ghe0".to_string(),
dtrfs_url: "http://registry.detee.ltd/detee-archtop-6.13.8-arch1-1.cpio.gz".to_string(), dtrfs_url: "http://registry.detee.ltd/detee-archtop-6.13.6-arch1-1.cpio.gz".to_string(),
dtrfs_sha: "b5f408d00e2b93dc594fed3a7f2466a9878802ff1c7ae502247471cd06728a45" dtrfs_sha: "de48048fb42fe4054611f14e51ce175ca90645734fe41349642f036b8bca8fcd"
.to_string(), .to_string(),
kernel_url: "http://registry.detee.ltd/vmlinuz-linux-6.13.8-arch1-1".to_string(), kernel_url: "http://registry.detee.ltd/vmlinuz-linux-6.13.6-arch1-1".to_string(),
kernel_sha: "e49c8587287b21df7600c04326fd7393524453918c14d67f73757dc769a13542" kernel_sha: "7efaca6c348cd4136afe3ece0beec346da713029347a0d4e71e12a0b91570de7"
.to_string() .to_string()
}, },
]; ];
static ref ALTERNATIVE_DISTROS: Vec<Distro> = vec![ static ref ALTERNATIVE_DISTROS: Vec<Distro> = vec![
Distro { Distro {
name: "archlinux_2025-02-21".to_string(), name: "archlinux_2025-01-27".to_string(),
vendor: "gheorghe".to_string(), vendor: "gheorghe".to_string(),
template_url: "http://registry.detee.ltd/detee_arch_2025-02-21.fsa".to_string(), template_url: "http://registry.detee.ltd/detee_arch_2025-01-27.fsa".to_string(),
template_sha: "257edbf1e3b949b895c422befc8890c85dfae1ad3d35661010c9aaa173ba9fc4" template_sha: "c8cc8ef611380c2d1fbab36e44ccfd8d666e344c7aaefe763f7dd6136b672c97"
.to_string() .to_string()
}, },
Distro { Distro {
name: "ubuntu_2025-02-28".to_string(), name: "ubuntu_2025-02-21".to_string(),
vendor: "gheorghe".to_string(), vendor: "gheorghe".to_string(),
template_url: "http://registry.detee.ltd/detee_ubuntu_2025-02-28.fsa".to_string(), template_url: "http://registry.detee.ltd/detee_ubuntu_2025-02-21.fsa".to_string(),
template_sha: "faa8bd38d02ca9b6ee69d7f5128ed9ccab42bdbfa69f688b9947e8e5c9e5d133" template_sha: "180e43c46494c8b5cf2b19067995755ade1bbd80396e1fd5e1c4b164ed2fe8cf"
.to_string() .to_string()
}, },
Distro { Distro {
name: "fedora_2025-02-21".to_string(), name: "fedora_2025-01-28".to_string(),
vendor: "gheorghe".to_string(), vendor: "gheorghe".to_string(),
template_url: "http://registry.detee.ltd/detee_fedora_2025-02-21.fsa".to_string(), template_url: "http://registry.detee.ltd/detee_fedora_2025-01-28.fsa".to_string(),
template_sha: "c0fdd08d465939077ef8ed746903005fc190af12cdf70917cc8c6f872da85777" template_sha: "68c5be46d668a12e8ff78692843a922315bd5cd9c2bb53accf2685ec3be1fa31"
.to_string() .to_string()
} }
]; ];