use std::path::{Path, PathBuf};
use ant_protocol::pqc::api::{ml_dsa_65, MlDsaPublicKey, MlDsaSignature, MlDsaVariant};
use futures_util::StreamExt;
use serde::{Deserialize, Serialize};
use crate::error::{Error, Result};
use crate::node::binary::{extract_tar_gz, extract_zip, ProgressReporter};
const GITHUB_REPO: &str = "WithAutonomi/ant-client";
const CLI_BINARY_NAME: &str = "ant";
const TAG_PREFIX: &str = "ant-cli-v";
const SIGNING_CONTEXT: &[u8] = b"ant-release-v1";
const SIGNATURE_SIZE: usize = 3309;
const RELEASE_SIGNING_KEY: &[u8] = &[
0xb4, 0xcf, 0x2a, 0x24, 0x31, 0xd9, 0xb2, 0x3a, 0xab, 0xe9, 0x5e, 0xfc, 0xbc, 0xf3, 0xb1, 0x1f,
0x4e, 0x50, 0x0a, 0x46, 0xeb, 0x83, 0xfc, 0x6f, 0x0f, 0x89, 0x41, 0x00, 0x1b, 0x52, 0xde, 0xdc,
0xb5, 0xc9, 0x07, 0xed, 0x72, 0x3a, 0xe1, 0xa9, 0x82, 0xe1, 0xfc, 0xff, 0xab, 0xce, 0x9f, 0x7d,
0xab, 0xe2, 0x57, 0x92, 0xe0, 0xf2, 0xac, 0xa1, 0x41, 0xd3, 0x74, 0x95, 0x41, 0xd1, 0xac, 0x22,
0xb1, 0xbb, 0x5c, 0xf4, 0x02, 0x0b, 0x73, 0x85, 0xfd, 0x56, 0x75, 0x0d, 0x5c, 0x38, 0xe1, 0x2d,
0xe0, 0x15, 0x4f, 0xbf, 0x40, 0xeb, 0xf1, 0x0d, 0x8f, 0x39, 0x32, 0xeb, 0x80, 0xa7, 0x2e, 0x77,
0x3e, 0x54, 0xe3, 0x3d, 0x62, 0xae, 0xe7, 0x09, 0x5e, 0xfb, 0xdc, 0xaa, 0x07, 0xdc, 0xe1, 0x08,
0x96, 0xbb, 0x4b, 0xa0, 0x2e, 0x05, 0x2d, 0xee, 0xeb, 0x9a, 0x1a, 0xae, 0xde, 0xe9, 0x2c, 0xf8,
0x2f, 0x43, 0x6c, 0x78, 0x4b, 0xde, 0xef, 0x91, 0x8b, 0x94, 0x9d, 0x4f, 0x81, 0x05, 0xcc, 0xf0,
0x98, 0xce, 0xce, 0x67, 0x54, 0xac, 0xde, 0xcd, 0x26, 0x9e, 0x84, 0xf4, 0x88, 0xb2, 0x1a, 0x3e,
0x93, 0x2e, 0xff, 0xa8, 0x45, 0x95, 0xd1, 0xd0, 0xb1, 0x6c, 0x3c, 0x1e, 0xef, 0x3d, 0xe3, 0xf2,
0x73, 0xe2, 0xf6, 0xb7, 0xf9, 0x3f, 0x10, 0x0d, 0x3c, 0xde, 0x28, 0x94, 0x07, 0xef, 0x24, 0x70,
0xc4, 0x5a, 0x0a, 0x67, 0xbb, 0x0f, 0x4f, 0x5c, 0x2b, 0xd8, 0x02, 0x05, 0xa5, 0x98, 0x03, 0x5d,
0x8f, 0xc0, 0x4a, 0x84, 0xe9, 0xea, 0xac, 0x13, 0xdf, 0x69, 0xfc, 0x1e, 0xcf, 0xb6, 0x88, 0xba,
0x99, 0x30, 0xbc, 0x7a, 0xb8, 0x9d, 0x3d, 0x62, 0x3b, 0x33, 0x19, 0xbb, 0x3a, 0x2c, 0x2b, 0xa0,
0x5a, 0xb0, 0x8f, 0x9e, 0x10, 0x81, 0xb9, 0x12, 0x54, 0x81, 0xf8, 0xe2, 0x91, 0xb2, 0xe7, 0xe6,
0x9c, 0x11, 0xeb, 0x49, 0x64, 0x3a, 0x25, 0xd6, 0x53, 0x2e, 0xdf, 0xfc, 0x14, 0x32, 0x65, 0xcc,
0x87, 0xdb, 0xfd, 0xbb, 0x81, 0xa8, 0x50, 0xcc, 0xb4, 0x31, 0x0c, 0x70, 0xf3, 0xb6, 0x15, 0x8a,
0x50, 0x80, 0xad, 0xb1, 0xb0, 0x10, 0x2b, 0x67, 0x33, 0xf5, 0xf6, 0x36, 0x35, 0x5f, 0xa7, 0xd2,
0x81, 0xd6, 0x75, 0xa1, 0x18, 0x15, 0xbe, 0x1d, 0x5e, 0x33, 0x8e, 0x98, 0xdd, 0x45, 0x0f, 0x0c,
0x0f, 0x0d, 0x8b, 0x3f, 0x97, 0x11, 0x21, 0x2e, 0xa0, 0x5e, 0xfe, 0x70, 0x09, 0xa7, 0x14, 0x30,
0xa3, 0x01, 0x2d, 0x18, 0x2b, 0x8f, 0x19, 0x75, 0x54, 0x1f, 0xd8, 0xee, 0x66, 0x06, 0x7b, 0x9d,
0x7d, 0xb2, 0xae, 0x14, 0xe6, 0x51, 0x19, 0xc2, 0x45, 0x2e, 0x7e, 0x11, 0xd9, 0x7b, 0x16, 0x8e,
0xae, 0x17, 0xdb, 0x1b, 0x24, 0x90, 0xcd, 0xed, 0x94, 0xf9, 0xf7, 0xba, 0x9f, 0x4c, 0x12, 0xae,
0x31, 0x7b, 0xd4, 0x7c, 0x04, 0x42, 0x2c, 0x32, 0x16, 0xc1, 0x70, 0x6d, 0x11, 0x6f, 0x3b, 0x44,
0x62, 0xba, 0xbd, 0xc5, 0x7a, 0xec, 0x55, 0x1b, 0xcd, 0xdb, 0xb6, 0x55, 0x08, 0x86, 0x13, 0x7f,
0x4e, 0xa9, 0x63, 0xe1, 0x87, 0xa0, 0x7e, 0x49, 0xfb, 0xf4, 0xa3, 0x46, 0xcf, 0x1d, 0xec, 0xf5,
0xc6, 0x2f, 0xe1, 0x43, 0x02, 0xd0, 0xe4, 0x5f, 0x1b, 0x20, 0x1a, 0xa7, 0x81, 0xbd, 0x31, 0x19,
0x6a, 0x74, 0xd7, 0x9a, 0x6d, 0x3d, 0xf8, 0xac, 0x4d, 0xbb, 0x01, 0x63, 0xa4, 0x9d, 0x3c, 0xc9,
0x6c, 0x8a, 0x4f, 0x61, 0xd6, 0x98, 0xf5, 0x40, 0x22, 0xa9, 0x5e, 0x93, 0x5e, 0x13, 0xd3, 0xe0,
0xdb, 0x54, 0xab, 0x0d, 0xe3, 0x88, 0x85, 0x80, 0x7a, 0x5e, 0x38, 0x64, 0x97, 0xc4, 0xe9, 0xb0,
0x5d, 0xf7, 0x40, 0x5f, 0x6e, 0x3f, 0xbe, 0x14, 0x9a, 0x7c, 0xa2, 0x7c, 0x74, 0xa5, 0x32, 0x22,
0x61, 0x31, 0xa5, 0x0d, 0xa5, 0xcc, 0x93, 0xe4, 0xfd, 0xed, 0xbc, 0xe7, 0xf2, 0xe5, 0xdb, 0xc6,
0x0c, 0xe1, 0xc8, 0x4e, 0xee, 0xe6, 0x76, 0x1c, 0x10, 0x1b, 0xd8, 0x53, 0xd4, 0xe8, 0x07, 0xed,
0xea, 0x91, 0xd4, 0x1b, 0x91, 0x5c, 0x28, 0x05, 0xca, 0xe2, 0x9c, 0xd1, 0x99, 0x43, 0xed, 0xd8,
0x6a, 0x2b, 0xd2, 0x64, 0x9b, 0xe1, 0x0c, 0x88, 0x6c, 0x0d, 0xb2, 0x6b, 0x73, 0x85, 0x9d, 0xbf,
0x79, 0x78, 0xaa, 0x7b, 0x5e, 0xf8, 0xa4, 0x26, 0xdf, 0xb3, 0x9b, 0x24, 0x5a, 0xc8, 0x19, 0x22,
0xa5, 0xc6, 0xca, 0x00, 0x59, 0x3b, 0xad, 0x45, 0xdf, 0x71, 0x1d, 0x60, 0x60, 0x24, 0x0d, 0xa4,
0x3d, 0x42, 0x23, 0xb8, 0xfe, 0xac, 0x86, 0x94, 0x79, 0x87, 0x05, 0xae, 0xb8, 0x4d, 0x7e, 0x11,
0x5b, 0x22, 0x44, 0x15, 0x3d, 0x7f, 0x82, 0x98, 0x65, 0x0a, 0x3c, 0xd3, 0xef, 0x80, 0x0d, 0x75,
0x03, 0x92, 0xf6, 0x3a, 0x8b, 0xa4, 0xb0, 0x61, 0x2d, 0x2c, 0xcc, 0x1f, 0x01, 0x8e, 0x7a, 0x46,
0x36, 0x2a, 0x83, 0x21, 0x88, 0x98, 0x13, 0x0a, 0xd5, 0xa1, 0x54, 0x4b, 0x63, 0xe0, 0xe3, 0x1c,
0x07, 0x5e, 0x32, 0x8c, 0xa4, 0x6b, 0x62, 0xc3, 0x28, 0x95, 0xb8, 0x0a, 0xb9, 0x4f, 0xaf, 0x7f,
0x49, 0xeb, 0xff, 0xd7, 0xa1, 0x41, 0x43, 0x9a, 0x92, 0x9a, 0x5f, 0xee, 0xbd, 0xb9, 0xbe, 0xb3,
0x4b, 0x9d, 0x0b, 0xcb, 0x9b, 0x2c, 0x26, 0x8f, 0x0f, 0xb1, 0xfa, 0xc0, 0xe3, 0x3a, 0x7f, 0x2b,
0x51, 0x79, 0x75, 0x25, 0x5b, 0x23, 0x22, 0xb1, 0x01, 0x27, 0x4e, 0x43, 0xdd, 0x66, 0x7a, 0x33,
0x4d, 0x32, 0x96, 0x83, 0x59, 0x52, 0xd7, 0x3c, 0xb0, 0xe3, 0x03, 0xd6, 0xb0, 0xc7, 0x99, 0x68,
0xc6, 0xa4, 0x2d, 0x35, 0x3d, 0xa4, 0x6a, 0x17, 0xd9, 0xf4, 0x0c, 0x26, 0x11, 0xe4, 0xbc, 0x03,
0x87, 0x25, 0x62, 0xad, 0xa9, 0x7e, 0x96, 0x4d, 0x39, 0x9b, 0x8f, 0x09, 0xdc, 0xd1, 0x28, 0x5e,
0xf4, 0xe3, 0x94, 0xfd, 0x94, 0x46, 0x30, 0xe2, 0x24, 0x46, 0x30, 0x7f, 0xf4, 0x4c, 0xaa, 0x51,
0x7e, 0x04, 0x5c, 0xa4, 0x8c, 0xba, 0x4a, 0xb8, 0x61, 0x5e, 0x75, 0x1c, 0xa8, 0x0c, 0xbc, 0x7f,
0x36, 0x16, 0xa1, 0x72, 0x98, 0x6a, 0x44, 0x39, 0x42, 0x67, 0xb5, 0x4a, 0xac, 0x14, 0x35, 0x8f,
0xcd, 0x87, 0x3f, 0x9e, 0x2e, 0xa1, 0x53, 0xf1, 0x45, 0x68, 0x26, 0xcb, 0x35, 0x96, 0x57, 0xd5,
0x3a, 0x24, 0x74, 0xe2, 0xff, 0xe0, 0x70, 0xb1, 0xbd, 0xec, 0x0c, 0xd2, 0x97, 0x9a, 0xe5, 0x9f,
0xa9, 0xfe, 0x6a, 0x63, 0x17, 0x35, 0xad, 0x64, 0x2f, 0xd9, 0x2e, 0xdb, 0x47, 0xdc, 0x62, 0xdc,
0xcc, 0xee, 0x7e, 0x23, 0xa6, 0x67, 0x61, 0x7c, 0xd1, 0x03, 0xbd, 0x78, 0xe9, 0x34, 0x05, 0xed,
0x05, 0x87, 0xef, 0x59, 0xf4, 0x16, 0xd6, 0x8d, 0x85, 0x46, 0x65, 0x2a, 0x08, 0xac, 0x4a, 0x5d,
0xe6, 0x27, 0x5f, 0x43, 0xdd, 0x51, 0x4e, 0x95, 0x9b, 0xf5, 0x0c, 0x81, 0x24, 0x73, 0x39, 0x77,
0xe9, 0xc8, 0x35, 0x4a, 0xe2, 0xb8, 0x35, 0x92, 0xde, 0x5c, 0x31, 0x12, 0x36, 0x5c, 0xc7, 0x69,
0xcd, 0x79, 0xa9, 0xf9, 0xcf, 0x13, 0xa9, 0x12, 0x29, 0x25, 0x5c, 0x6a, 0x34, 0xa4, 0xbf, 0xc5,
0xb6, 0x2a, 0xc1, 0xba, 0x6a, 0xd3, 0x98, 0x8c, 0x9b, 0x6d, 0x9f, 0xb9, 0x25, 0xa6, 0xd1, 0x97,
0x80, 0x38, 0x11, 0xdc, 0x73, 0x5c, 0xe7, 0x3a, 0x1f, 0xd2, 0x16, 0xcd, 0x63, 0xfb, 0x41, 0xb0,
0xba, 0xb0, 0x38, 0x67, 0x48, 0xd2, 0x8a, 0x94, 0x2f, 0x11, 0x81, 0xbf, 0x66, 0x38, 0x68, 0xff,
0xfe, 0xd1, 0x7c, 0xcd, 0xa3, 0xac, 0xe4, 0xf7, 0x58, 0x19, 0xcd, 0x2a, 0xe3, 0xfa, 0x4d, 0xb0,
0xbe, 0xac, 0x05, 0x1c, 0xd9, 0x8d, 0xf7, 0x5c, 0xc0, 0xfc, 0xa6, 0xb5, 0x99, 0xb8, 0x8e, 0x2b,
0x72, 0xf8, 0x19, 0xfc, 0x17, 0x11, 0xf6, 0x2b, 0x08, 0xe4, 0x6e, 0xb0, 0x65, 0xab, 0x78, 0x8a,
0xfc, 0x7c, 0x09, 0xca, 0x73, 0xcd, 0x35, 0x5d, 0x6c, 0x7a, 0x36, 0xc0, 0x24, 0xba, 0x3f, 0x08,
0xea, 0x17, 0x09, 0xe1, 0x9d, 0x5d, 0x18, 0x59, 0x8a, 0xd8, 0x6a, 0x6d, 0x85, 0x6a, 0x9e, 0xa9,
0xe5, 0x4b, 0x45, 0xb2, 0x35, 0x6e, 0x62, 0x24, 0x08, 0x00, 0x1c, 0x06, 0x73, 0x27, 0x5d, 0x11,
0x4a, 0xc8, 0x51, 0xbd, 0x59, 0xd6, 0x94, 0xce, 0x16, 0x15, 0x17, 0x58, 0x7f, 0x39, 0x9d, 0x4e,
0x69, 0x1a, 0x64, 0xbb, 0xd4, 0x51, 0xb9, 0xe4, 0x7d, 0x51, 0x3a, 0xff, 0xe5, 0x1f, 0x29, 0xea,
0x7e, 0xa5, 0x62, 0x63, 0xff, 0x10, 0xf7, 0x54, 0x35, 0xd1, 0xf3, 0x73, 0x1e, 0xab, 0xca, 0x52,
0x14, 0xc6, 0x7e, 0x51, 0xc2, 0x48, 0x13, 0xcb, 0x30, 0xb2, 0x1a, 0x84, 0x72, 0xe5, 0x44, 0x83,
0xc9, 0x90, 0xa5, 0x8c, 0xf9, 0xeb, 0x3c, 0x5c, 0xc6, 0xcc, 0x8a, 0x95, 0x8a, 0xfa, 0xeb, 0x37,
0x9c, 0xde, 0xa2, 0xb1, 0x72, 0x4d, 0xd9, 0x3d, 0xab, 0xfd, 0x0e, 0xbd, 0x32, 0x9d, 0x23, 0xe9,
0x6f, 0x85, 0x4e, 0xfe, 0xcd, 0x91, 0xfb, 0x82, 0x94, 0xee, 0x8b, 0xdf, 0x6a, 0xd9, 0x01, 0xa1,
0xc6, 0x22, 0x18, 0x01, 0x8d, 0x10, 0xd5, 0x87, 0x42, 0xd0, 0xbd, 0x23, 0x75, 0x44, 0x53, 0x46,
0xa5, 0xae, 0x00, 0x4c, 0x0e, 0x88, 0x4a, 0xa8, 0x3d, 0x4a, 0x30, 0xe0, 0x1a, 0xa4, 0xe5, 0x40,
0xb8, 0xe0, 0x12, 0x9c, 0x44, 0x03, 0xfb, 0x2e, 0x4e, 0xf5, 0x29, 0xdb, 0x09, 0x84, 0x55, 0xc7,
0x6c, 0xc6, 0x1f, 0xf9, 0xee, 0x0b, 0xa4, 0x91, 0x7d, 0x79, 0x27, 0x59, 0x75, 0x97, 0xec, 0x6a,
0xa8, 0xf8, 0x55, 0xa8, 0x45, 0xd4, 0xd7, 0xa6, 0xc1, 0xc4, 0x27, 0x35, 0xe8, 0x4f, 0x39, 0x89,
0x7d, 0x41, 0xf3, 0xf6, 0xd0, 0xb6, 0xf9, 0x91, 0xeb, 0x94, 0xf1, 0xbb, 0x17, 0x46, 0x9c, 0xd5,
0x5a, 0x53, 0x04, 0x2d, 0x12, 0x7c, 0x17, 0x6a, 0x36, 0xb5, 0xea, 0xf3, 0x5b, 0x96, 0x1b, 0xee,
0xce, 0xc4, 0xc0, 0x11, 0x5a, 0xbc, 0x0c, 0x29, 0xd0, 0x42, 0x1d, 0x16, 0x63, 0xea, 0x1e, 0x04,
0x2f, 0xe3, 0x17, 0xed, 0x33, 0xac, 0x56, 0x80, 0x34, 0x41, 0x41, 0x1e, 0x77, 0x80, 0x06, 0x9f,
0xbc, 0x2e, 0x78, 0xa1, 0x04, 0x00, 0x06, 0x6f, 0x36, 0x2f, 0xb7, 0xa5, 0x95, 0x37, 0x82, 0x9d,
0xef, 0x41, 0x08, 0x85, 0x3d, 0x53, 0xa7, 0xfb, 0xfe, 0xba, 0x8c, 0xb9, 0xae, 0xc7, 0x89, 0x11,
0x69, 0x4f, 0x62, 0xe6, 0xb6, 0x08, 0x6b, 0x35, 0x1c, 0x96, 0xb3, 0x7b, 0x40, 0x2d, 0xee, 0x07,
0x40, 0x52, 0x4f, 0x68, 0x60, 0xf4, 0xb9, 0xc3, 0x54, 0x9f, 0x22, 0x50, 0x88, 0x48, 0x6a, 0x28,
0x93, 0x46, 0x00, 0xe2, 0x4a, 0x85, 0x41, 0x78, 0x0e, 0x87, 0xc5, 0xeb, 0xfc, 0xd3, 0x5f, 0x4d,
0x24, 0xe4, 0x9d, 0xeb, 0x1d, 0x00, 0x73, 0x85, 0x25, 0x47, 0x9e, 0x8c, 0x5b, 0x88, 0xf4, 0x3b,
0x33, 0xf0, 0x3d, 0x3a, 0xa1, 0x28, 0xd3, 0x06, 0xb4, 0x7a, 0x4e, 0x5d, 0x31, 0x1b, 0xca, 0xf4,
0x3f, 0x70, 0x30, 0x49, 0x44, 0x29, 0x24, 0x14, 0x5e, 0x35, 0xc2, 0x6c, 0x92, 0x7e, 0xf8, 0x97,
0x0c, 0x51, 0x9d, 0x67, 0xc0, 0x10, 0xa9, 0x35, 0x48, 0x59, 0x6a, 0x33, 0xef, 0x40, 0x4e, 0x53,
0x10, 0x14, 0x2a, 0x12, 0x38, 0xe6, 0xc4, 0x63, 0x9c, 0x84, 0x85, 0x06, 0xaf, 0x3d, 0x3a, 0x84,
0x06, 0x60, 0x88, 0x32, 0xda, 0x2c, 0xe5, 0xc6, 0x59, 0xf1, 0xe0, 0x10, 0xe2, 0x3c, 0xe6, 0xbf,
0x32, 0x7d, 0x32, 0x39, 0x6d, 0xe4, 0xd9, 0xca, 0xe7, 0xf5, 0xf4, 0xa6, 0x5f, 0xb2, 0x33, 0x05,
0xb5, 0xad, 0x5f, 0xcb, 0x0b, 0x14, 0xaf, 0xeb, 0xc0, 0xec, 0x87, 0x85, 0x9b, 0x13, 0xb5, 0x8a,
0x98, 0xa9, 0x92, 0x13, 0x1b, 0x74, 0xec, 0xfd, 0xe1, 0xc1, 0x22, 0x06, 0x5d, 0x4f, 0x06, 0xc7,
0xdd, 0xc6, 0xf0, 0xc4, 0x01, 0x04, 0xad, 0x7f, 0x71, 0xbc, 0x74, 0x4d, 0xfd, 0x18, 0xa3, 0x56,
0x2c, 0x45, 0x28, 0x2b, 0x2f, 0xbc, 0x9b, 0xb8, 0x4b, 0xe6, 0x51, 0x75, 0x28, 0x0c, 0x27, 0x0e,
0xf7, 0x92, 0x8c, 0xc9, 0xde, 0x33, 0x1a, 0x65, 0x28, 0xc7, 0x01, 0x32, 0xa2, 0x36, 0x88, 0xb6,
0x64, 0x10, 0x03, 0xd6, 0xb7, 0x9f, 0x9d, 0x73, 0xe1, 0xa9, 0xc7, 0xdf, 0xe1, 0x0b, 0x39, 0x31,
0x77, 0xbc, 0x91, 0xf1, 0x45, 0x9a, 0xc5, 0x97, 0x28, 0xc0, 0x61, 0xc5, 0x23, 0x54, 0xad, 0xe3,
0x23, 0x18, 0x69, 0xf7, 0x27, 0xd0, 0x5b, 0xf2, 0x44, 0x62, 0xdc, 0x97, 0xce, 0x4e, 0x40, 0x76,
0x00, 0xde, 0xc2, 0xf9, 0x3a, 0x42, 0xfd, 0xd4, 0xd7, 0xe1, 0x85, 0xd8, 0xc9, 0x38, 0x91, 0xc1,
0x79, 0x87, 0x58, 0xf1, 0x26, 0x1a, 0x29, 0x02, 0xe3, 0x54, 0xde, 0x58, 0x64, 0x9d, 0xe6, 0x8e,
0x33, 0x70, 0x53, 0x43, 0x47, 0x90, 0xee, 0x6e, 0x0f, 0x8c, 0xb3, 0x9e, 0x47, 0x45, 0xfc, 0xa8,
0xe3, 0x52, 0x62, 0x74, 0x6d, 0xa2, 0xaf, 0x28, 0x9d, 0xdf, 0x1e, 0x69, 0x1f, 0x56, 0xbc, 0x49,
0xc1, 0xe5, 0xd6, 0xc4, 0xb5, 0x5c, 0x4d, 0x39, 0x49, 0x4b, 0xb4, 0xec, 0x56, 0x54, 0x9a, 0x15,
0x94, 0x0a, 0xcb, 0xa9, 0x10, 0x46, 0x03, 0x5c, 0x23, 0x2f, 0x29, 0xed, 0x72, 0xa1, 0x57, 0xfa,
0x58, 0xef, 0x21, 0x7e, 0xf2, 0x8b, 0xa7, 0x04, 0x51, 0xb4, 0x03, 0x5d, 0xd8, 0x48, 0xc0, 0xe5,
0x83, 0xb6, 0x7a, 0x6b, 0xcd, 0xfb, 0xda, 0x47, 0xe8, 0xa1, 0xae, 0x57, 0x74, 0x49, 0xc0, 0xf9,
0x4a, 0x6b, 0x3c, 0xb5, 0xd8, 0x27, 0x3d, 0x1d, 0x96, 0x39, 0x09, 0x65, 0x95, 0xdb, 0x01, 0xa3,
0x8b, 0x78, 0x8b, 0x07, 0x6d, 0x1c, 0x8b, 0x4b, 0x1d, 0x9d, 0x4a, 0x4f, 0xcb, 0xb8, 0xf6, 0x22,
0x73, 0x8a, 0x7b, 0xc8, 0xf2, 0x0a, 0xef, 0x03, 0x1e, 0xb7, 0x4d, 0x8f, 0xc0, 0xdf, 0x87, 0x88,
0x05, 0xe1, 0x0a, 0x30, 0xea, 0xde, 0xf3, 0xc2, 0xb6, 0x00, 0x3c, 0xd6, 0xff, 0x3b, 0xb5, 0x01,
0xfb, 0xd8, 0xb2, 0x65, 0x26, 0x5d, 0xa0, 0x5a, 0x7c, 0xef, 0x1d, 0x85, 0xbe, 0x51, 0xc2, 0x57,
0x0b, 0x27, 0x37, 0x71, 0x99, 0xf5, 0x87, 0x83, 0x68, 0x0b, 0x88, 0xed, 0x66, 0x9e, 0x37, 0x59,
0x84, 0x23, 0x72, 0xc3, 0x80, 0xac, 0xfe, 0x45, 0x5f, 0xdf, 0x31, 0xc4, 0x84, 0x07, 0x5a, 0x17,
0x28, 0xcd, 0x64, 0xb4, 0xe2, 0xa3, 0x0e, 0x2c, 0x15, 0x60, 0x77, 0xdc, 0x08, 0x45, 0x36, 0x37,
0x68, 0x50, 0xba, 0x03, 0x85, 0xb7, 0xed, 0xd0, 0x7b, 0xb2, 0xa1, 0x62, 0xbc, 0x70, 0x00, 0x9e,
];
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateCheck {
pub current_version: String,
pub latest_version: String,
pub update_available: bool,
pub download_url: Option<String>,
}
impl UpdateCheck {
pub fn force(&mut self) -> Result<()> {
self.update_available = true;
self.download_url = Some(build_download_url(&self.latest_version)?);
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateResult {
pub previous_version: String,
pub new_version: String,
}
pub async fn check_for_update(current_version: &str) -> Result<UpdateCheck> {
let latest = fetch_latest_cli_version().await?;
let current = parse_version(current_version)?;
let latest_parsed = parse_version(&latest)?;
let update_available = latest_parsed > current;
let download_url = if update_available {
Some(build_download_url(&latest)?)
} else {
None
};
Ok(UpdateCheck {
current_version: current_version.to_string(),
latest_version: latest,
update_available,
download_url,
})
}
pub async fn perform_update(
check: &UpdateCheck,
progress: &dyn ProgressReporter,
) -> Result<UpdateResult> {
let download_url = check.download_url.as_deref().ok_or_else(|| {
Error::UpdateFailed("no download URL — are you already on the latest version?".to_string())
})?;
let tmp_dir = tempfile::tempdir()
.map_err(|e| Error::UpdateFailed(format!("failed to create temp directory: {e}")))?;
let (archive_path, archive_bytes) =
download_archive(download_url, tmp_dir.path(), progress).await?;
let sig_url = format!("{download_url}.sig");
progress.report_started("Downloading signature...");
let sig_bytes = download_bytes(&sig_url).await?;
progress.report_started("Verifying ML-DSA signature...");
verify_signature(&archive_bytes, &sig_bytes)?;
progress.report_complete("Signature verified");
progress.report_started("Extracting archive...");
let extracted = if download_url.ends_with(".zip") {
extract_zip(&archive_bytes, tmp_dir.path(), CLI_BINARY_NAME)?
} else {
extract_tar_gz(&archive_bytes, tmp_dir.path(), CLI_BINARY_NAME)?
};
let binary_path = extracted.binary_path;
let actual_version = extract_version(&binary_path).await;
if let Ok(ref v) = actual_version {
if v != &check.latest_version {
return Err(Error::UpdateFailed(format!(
"version mismatch: expected {}, binary reports {v}",
check.latest_version
)));
}
}
replace_binary(&binary_path)?;
let _ = std::fs::remove_file(&archive_path);
Ok(UpdateResult {
previous_version: check.current_version.clone(),
new_version: check.latest_version.clone(),
})
}
async fn fetch_latest_cli_version() -> Result<String> {
let url = format!("https://api.github.com/repos/{GITHUB_REPO}/releases?per_page=100");
let client = reqwest::Client::new();
let resp = client
.get(&url)
.header("User-Agent", "ant-cli")
.header("Accept", "application/vnd.github+json")
.send()
.await
.map_err(|e| Error::UpdateFailed(format!("failed to fetch releases: {e}")))?;
if !resp.status().is_success() {
return Err(Error::UpdateFailed(format!(
"GitHub API returned status {} when fetching releases",
resp.status()
)));
}
let releases: Vec<serde_json::Value> = resp
.json()
.await
.map_err(|e| Error::UpdateFailed(format!("failed to parse releases JSON: {e}")))?;
let mut best: Option<semver::Version> = None;
for release in &releases {
if release["draft"].as_bool().unwrap_or(false)
|| release["prerelease"].as_bool().unwrap_or(false)
{
continue;
}
let tag = release["tag_name"].as_str().unwrap_or_default();
if let Some(version_str) = tag.strip_prefix(TAG_PREFIX) {
if let Ok(v) = semver::Version::parse(version_str) {
if best.as_ref().is_none_or(|b| v > *b) {
best = Some(v);
}
}
}
}
best.map(|v| v.to_string())
.ok_or_else(|| Error::UpdateFailed("no ant-cli release found on GitHub".to_string()))
}
async fn download_archive(
url: &str,
tmp_dir: &Path,
progress: &dyn ProgressReporter,
) -> Result<(PathBuf, Vec<u8>)> {
progress.report_started(&format!("Downloading {CLI_BINARY_NAME} from {url}"));
let client = reqwest::Client::new();
let resp = client
.get(url)
.header("User-Agent", "ant-cli")
.send()
.await
.map_err(|e| Error::UpdateFailed(format!("download request failed: {e}")))?;
if !resp.status().is_success() {
return Err(Error::UpdateFailed(format!(
"download returned status {}",
resp.status()
)));
}
let total_size = resp.content_length().unwrap_or(0);
let mut downloaded: u64 = 0;
let tmp_archive = tmp_dir.join(".download.tmp");
let mut tmp_file = std::fs::File::create(&tmp_archive)
.map_err(|e| Error::UpdateFailed(format!("failed to create temp file: {e}")))?;
let mut stream = resp.bytes_stream();
while let Some(chunk) = stream.next().await {
let chunk =
chunk.map_err(|e| Error::UpdateFailed(format!("download stream error: {e}")))?;
downloaded += chunk.len() as u64;
std::io::Write::write_all(&mut tmp_file, &chunk)
.map_err(|e| Error::UpdateFailed(format!("failed to write temp file: {e}")))?;
progress.report_progress(downloaded, total_size);
}
drop(tmp_file);
progress.report_complete("Download complete");
let bytes = std::fs::read(&tmp_archive)
.map_err(|e| Error::UpdateFailed(format!("failed to read temp file: {e}")))?;
Ok((tmp_archive, bytes))
}
async fn download_bytes(url: &str) -> Result<Vec<u8>> {
let client = reqwest::Client::new();
let resp = client
.get(url)
.header("User-Agent", "ant-cli")
.send()
.await
.map_err(|e| Error::UpdateFailed(format!("download request failed: {e}")))?;
if !resp.status().is_success() {
return Err(Error::UpdateFailed(format!(
"download returned status {} for {url}",
resp.status()
)));
}
resp.bytes()
.await
.map(|b| b.to_vec())
.map_err(|e| Error::UpdateFailed(format!("failed to read response body: {e}")))
}
fn verify_signature(archive_bytes: &[u8], signature_bytes: &[u8]) -> Result<()> {
if signature_bytes.len() != SIGNATURE_SIZE {
return Err(Error::UpdateFailed(format!(
"invalid signature size: expected {SIGNATURE_SIZE}, got {}",
signature_bytes.len()
)));
}
let public_key = MlDsaPublicKey::from_bytes(MlDsaVariant::MlDsa65, RELEASE_SIGNING_KEY)
.map_err(|e| Error::UpdateFailed(format!("invalid embedded release key: {e}")))?;
let sig = MlDsaSignature::from_bytes(MlDsaVariant::MlDsa65, signature_bytes)
.map_err(|e| Error::UpdateFailed(format!("invalid signature format: {e}")))?;
let dsa = ml_dsa_65();
let valid = dsa
.verify_with_context(&public_key, archive_bytes, &sig, SIGNING_CONTEXT)
.map_err(|e| Error::UpdateFailed(format!("signature verification error: {e}")))?;
if valid {
Ok(())
} else {
Err(Error::UpdateFailed(
"signature verification failed: archive may be corrupted or tampered".to_string(),
))
}
}
async fn extract_version(binary_path: &Path) -> Result<String> {
let output = tokio::process::Command::new(binary_path)
.arg("--version")
.output()
.await
.map_err(|e| {
Error::UpdateFailed(format!(
"failed to run {} --version: {e}",
binary_path.display()
))
})?;
if !output.status.success() {
return Err(Error::UpdateFailed(format!(
"{} --version exited with status {}",
binary_path.display(),
output.status
)));
}
let stdout = String::from_utf8_lossy(&output.stdout);
Ok(parse_version_from_stdout(&stdout).to_string())
}
fn parse_version_from_stdout(stdout: &str) -> &str {
stdout
.lines()
.next()
.unwrap_or_default()
.split_whitespace()
.last()
.unwrap_or("unknown")
}
fn replace_binary(new_binary: &Path) -> Result<()> {
self_replace::self_replace(new_binary)
.map_err(|e| Error::UpdateFailed(format!("failed to replace binary: {e}")))?;
Ok(())
}
pub fn build_download_url(version: &str) -> Result<String> {
let asset_name = cli_platform_asset_name(version)?;
Ok(format!(
"https://github.com/{GITHUB_REPO}/releases/download/{TAG_PREFIX}{version}/{asset_name}"
))
}
fn cli_platform_asset_name(version: &str) -> Result<String> {
let target_triple = if cfg!(all(target_os = "linux", target_arch = "x86_64")) {
"x86_64-unknown-linux-musl"
} else if cfg!(all(target_os = "linux", target_arch = "aarch64")) {
"aarch64-unknown-linux-musl"
} else if cfg!(all(target_os = "macos", target_arch = "x86_64")) {
"x86_64-apple-darwin"
} else if cfg!(all(target_os = "macos", target_arch = "aarch64")) {
"aarch64-apple-darwin"
} else if cfg!(all(target_os = "windows", target_arch = "x86_64")) {
"x86_64-pc-windows-msvc"
} else {
return Err(Error::UpdateFailed(format!(
"unsupported platform: {}-{}",
std::env::consts::OS,
std::env::consts::ARCH
)));
};
let ext = if cfg!(target_os = "windows") {
"zip"
} else {
"tar.gz"
};
Ok(format!("ant-{version}-{target_triple}.{ext}"))
}
fn parse_version(version: &str) -> Result<semver::Version> {
let cleaned = version.strip_prefix('v').unwrap_or(version);
semver::Version::parse(cleaned)
.map_err(|e| Error::UpdateFailed(format!("invalid version '{version}': {e}")))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_version_from_stdout_single_line() {
assert_eq!(parse_version_from_stdout("ant 0.1.4"), "0.1.4");
assert_eq!(parse_version_from_stdout("ant 0.1.4\n"), "0.1.4");
}
#[test]
fn parse_version_from_stdout_multi_line_license_trailer() {
let stdout = "ant 0.1.4\n\
Autonomi network client\n\
\n\
Repository: https://github.com/WithAutonomi/ant-client\n\
License: MIT or Apache-2.0\n";
assert_eq!(parse_version_from_stdout(stdout), "0.1.4");
}
#[test]
fn parse_version_from_stdout_empty() {
assert_eq!(parse_version_from_stdout(""), "unknown");
}
#[test]
fn parse_version_from_stdout_blank_first_line() {
assert_eq!(parse_version_from_stdout("\nant 0.1.4\n"), "unknown");
}
#[test]
fn parse_version_from_stdout_leading_whitespace() {
assert_eq!(parse_version_from_stdout(" ant 0.1.4\n"), "0.1.4");
}
#[test]
fn parse_version_valid() {
assert!(parse_version("1.2.3").is_ok());
assert!(parse_version("v1.2.3").is_ok());
assert!(parse_version("0.1.0").is_ok());
}
#[test]
fn parse_version_invalid() {
assert!(parse_version("not-a-version").is_err());
assert!(parse_version("").is_err());
}
#[test]
fn version_comparison() {
let v1 = parse_version("0.1.0").unwrap();
let v2 = parse_version("0.2.0").unwrap();
assert!(v2 > v1);
let v3 = parse_version("1.0.0").unwrap();
assert!(v3 > v2);
let same = parse_version("0.1.0").unwrap();
assert_eq!(v1, same);
}
#[test]
fn check_result_no_update() {
let check = UpdateCheck {
current_version: "1.0.0".to_string(),
latest_version: "1.0.0".to_string(),
update_available: false,
download_url: None,
};
assert!(!check.update_available);
assert!(check.download_url.is_none());
}
#[test]
fn check_result_with_update() {
let check = UpdateCheck {
current_version: "0.1.0".to_string(),
latest_version: "0.2.0".to_string(),
update_available: true,
download_url: Some("https://example.com/ant.tar.gz".to_string()),
};
assert!(check.update_available);
assert!(check.download_url.is_some());
}
#[test]
fn force_populates_download_url() {
let mut check = UpdateCheck {
current_version: "1.0.0".to_string(),
latest_version: "1.0.0".to_string(),
update_available: false,
download_url: None,
};
check.force().unwrap();
assert!(check.update_available);
assert!(check.download_url.is_some());
}
#[test]
fn platform_asset_name_format() {
let name = cli_platform_asset_name("1.2.3").unwrap();
assert!(name.starts_with("ant-1.2.3-"));
assert!(
name.ends_with(".tar.gz") || name.ends_with(".zip"),
"unexpected extension: {name}"
);
}
#[test]
fn build_download_url_format() {
let url = build_download_url("1.2.3").unwrap();
assert!(url.starts_with(
"https://github.com/WithAutonomi/ant-client/releases/download/ant-cli-v1.2.3/ant-1.2.3-"
));
assert!(url.ends_with(".tar.gz") || url.ends_with(".zip"));
}
#[test]
fn update_check_serializes() {
let check = UpdateCheck {
current_version: "0.1.0".to_string(),
latest_version: "0.2.0".to_string(),
update_available: true,
download_url: Some("https://example.com/ant.tar.gz".to_string()),
};
let json = serde_json::to_string(&check).unwrap();
let deserialized: UpdateCheck = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.current_version, "0.1.0");
assert!(deserialized.update_available);
}
#[test]
fn verify_signature_rejects_wrong_size() {
let result = verify_signature(b"some archive data", &[0u8; 100]);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("invalid signature size"), "got: {err}");
}
#[test]
fn verify_signature_rejects_invalid_signature() {
let invalid_sig = vec![0u8; SIGNATURE_SIZE];
let result = verify_signature(b"some archive data", &invalid_sig);
assert!(result.is_err());
}
#[test]
fn verify_signature_valid_roundtrip() {
let dsa = ml_dsa_65();
let (public_key, secret_key) = dsa.generate_keypair().unwrap();
let archive = b"fake archive content for testing";
let sig = dsa
.sign_with_context(&secret_key, archive, SIGNING_CONTEXT)
.unwrap();
let parsed_sig =
MlDsaSignature::from_bytes(MlDsaVariant::MlDsa65, &sig.to_bytes()).unwrap();
let valid = dsa
.verify_with_context(&public_key, archive, &parsed_sig, SIGNING_CONTEXT)
.unwrap();
assert!(valid);
}
}