samloader_fus/
fusclient.rs1use crate::{auth, xml};
16use aes::cipher::KeyInit;
17use reqwest::blocking::{Client, Response};
18use reqwest::header::{AUTHORIZATION, HeaderMap, HeaderValue, RANGE, USER_AGENT};
19use xml::BinaryInform;
20
21pub type Aes128EcbDec = ecb::Decryptor<aes::Aes128>;
22
23pub struct FusClient {
24 client: Client,
25 auth: String,
26 nonce: String,
27 encnonce: String,
28 pub info: BinaryInform,
29}
30
31impl FusClient {
32 pub fn new() -> reqwest::Result<Self> {
33 let client = Client::builder().cookie_store(true).build()?;
34 let mut fus = FusClient {
35 client,
36 auth: Default::default(),
37 nonce: Default::default(),
38 encnonce: Default::default(),
39 info: Default::default(),
40 };
41
42 fus.make_req("NF_SmartDownloadGenerateNonce.do", "")?;
44
45 Ok(fus)
46 }
47
48 pub fn fetch_binary_info(&mut self, model: &str, region: &str) {
49 let version_url = format!(
51 "https://fota-cloud-dn.ospserver.net:443/firmware/{}/{}/version.xml",
52 region, model
53 );
54 let version_xml = self
55 .client
56 .get(&version_url)
57 .header(USER_AGENT, "Kies2.0_FUS")
58 .send()
59 .expect("Failed to fetch version.xml")
60 .text()
61 .expect("Failed to read version.xml text");
62
63 let latest_fw = xml::parse_version_xml(&version_xml).expect("Failed to parse version.xml");
64
65 let req_xml = xml::binary_inform_req_xml(model, region, &latest_fw, &self.nonce);
67
68 let xml = self
69 .make_req("NF_SmartDownloadBinaryInform.do", &req_xml)
70 .and_then(Response::text)
71 .expect("Info request failed");
72
73 self.info = BinaryInform::parse(&xml).expect("Info request invalid");
74 }
75
76 fn make_headers(&self) -> HeaderMap {
77 let auth_val = format!(
78 "FUS nonce=\"{}\", signature=\"{}\", nc=\"\", type=\"\", realm=\"\", newauth=\"1\"",
79 self.encnonce, self.auth
80 );
81
82 let mut headers = HeaderMap::new();
83 headers.insert(AUTHORIZATION, HeaderValue::from_str(&auth_val).unwrap());
84 headers.insert(USER_AGENT, HeaderValue::from_static("SMART 2.0"));
85 headers
86 }
87
88 fn make_req(&mut self, path: &str, data: &str) -> reqwest::Result<Response> {
89 let url = format!("https://neofussvr.sslcs.cdngc.net/{}", path);
90 let resp = self
91 .client
92 .post(&url)
93 .headers(self.make_headers())
94 .body(data.to_string())
95 .send()?
96 .error_for_status()?;
97
98 if let Some(nonce) = resp
99 .headers()
100 .get("NONCE")
101 .or_else(|| resp.headers().get("nonce"))
102 .and_then(|n| n.to_str().ok())
103 {
104 let nonce_str = nonce.to_string();
105 if !nonce_str.is_empty() && nonce_str != self.encnonce {
106 self.encnonce = nonce_str;
107 self.nonce = self.encnonce.clone();
108 self.auth = auth::decrypt_nonce(&self.encnonce);
109 }
110 }
111
112 Ok(resp)
113 }
114
115 pub fn init_download(&mut self) {
116 let init_xml = xml::binary_init_req_xml(
117 &self.info.filename,
118 &self.nonce,
119 &self.info.version,
120 &self.info.model_type,
121 &self.info.region,
122 );
123 self.make_req("NF_SmartDownloadBinaryInitForMass.do", &init_xml)
124 .expect("Download init failed");
125 }
126
127 pub fn download_file(&self, start: Option<u64>, end: Option<u64>) -> reqwest::Result<Response> {
128 let mut headers = self.make_headers();
129 match (start, end) {
130 (Some(s), Some(e)) => headers.insert(
131 RANGE,
132 HeaderValue::from_str(&format!("bytes={}-{}", s, e)).unwrap(),
133 ),
134 (None, Some(e)) => headers.insert(
135 RANGE,
136 HeaderValue::from_str(&format!("bytes=0-{}", e)).unwrap(),
137 ),
138 (Some(s), None) => headers.insert(
139 RANGE,
140 HeaderValue::from_str(&format!("bytes={}-", s)).unwrap(),
141 ),
142 _ => None,
143 };
144
145 let url = format!(
146 "http://cloud-neofussvr.samsungmobile.com/NF_SmartDownloadBinaryForMass.do?file={}{}",
147 self.info.path, self.info.filename
148 );
149 self.client
150 .get(url)
151 .headers(headers)
152 .send()?
153 .error_for_status()
154 }
155
156 pub fn get_decryptor(&self) -> Aes128EcbDec {
157 Aes128EcbDec::new_from_slice(self.info.key.as_slice()).unwrap()
158 }
159}