easyofd_core/crypto/
provider.rs1#[derive(Debug, Clone)]
17pub struct CryptoProvider {
18 name: String,
20 version: Option<String>,
22 company: Option<String>,
24 protocol_ver: Option<String>,
26 extend_data: Option<Vec<u8>>,
28}
29
30impl CryptoProvider {
31 #[must_use]
41 pub fn new(name: impl Into<String>) -> Self {
42 Self {
43 name: name.into(),
44 version: None,
45 company: None,
46 protocol_ver: None,
47 extend_data: None,
48 }
49 }
50
51 #[must_use]
55 pub fn name(&self) -> &str {
56 &self.name
57 }
58
59 #[must_use]
63 pub fn version(mut self, version: impl Into<String>) -> Self {
64 self.version = Some(version.into());
65 self
66 }
67
68 #[must_use]
70 pub fn version_ref(&self) -> Option<&str> {
71 self.version.as_deref()
72 }
73
74 #[must_use]
78 pub fn company(mut self, company: impl Into<String>) -> Self {
79 self.company = Some(company.into());
80 self
81 }
82
83 #[must_use]
85 pub fn company_ref(&self) -> Option<&str> {
86 self.company.as_deref()
87 }
88
89 #[must_use]
93 pub fn protocol_ver(mut self, ver: impl Into<String>) -> Self {
94 self.protocol_ver = Some(ver.into());
95 self
96 }
97
98 #[must_use]
100 pub fn protocol_ver_ref(&self) -> Option<&str> {
101 self.protocol_ver.as_deref()
102 }
103
104 #[must_use]
110 pub fn extend_data(mut self, data: Vec<u8>) -> Self {
111 self.extend_data = Some(data);
112 self
113 }
114
115 #[must_use]
117 pub fn extend_data_ref(&self) -> Option<&[u8]> {
118 self.extend_data.as_deref()
119 }
120
121 #[must_use]
126 pub fn to_xml_string(&self) -> String {
127 use std::fmt::Write;
128
129 let mut xml = format!(r#"<ofd:Provider Name="{}""#, self.name);
130 if let Some(ref v) = self.version {
131 let _ = write!(xml, r#" Version="{v}""#);
132 }
133 if let Some(ref c) = self.company {
134 let _ = write!(xml, r#" Company="{c}""#);
135 }
136 if let Some(ref p) = self.protocol_ver {
137 let _ = write!(xml, r#" ProtocolVer="{p}""#);
138 }
139 if let Some(ref data) = self.extend_data {
140 let encoded = base64_encode(data);
141 let _ = write!(
142 xml,
143 "><ofd:ExtendData>{encoded}</ofd:ExtendData></ofd:Provider>"
144 );
145 } else {
146 xml.push_str(" />");
147 }
148 xml
149 }
150}
151
152fn base64_encode(data: &[u8]) -> String {
154 const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
155 let mut out = String::with_capacity(data.len().div_ceil(3) * 4);
156 for chunk in data.chunks(3) {
157 let b0 = u32::from(chunk[0]);
158 let b1 = if chunk.len() > 1 {
159 u32::from(chunk[1])
160 } else {
161 0
162 };
163 let b2 = if chunk.len() > 2 {
164 u32::from(chunk[2])
165 } else {
166 0
167 };
168 let triple = (b0 << 16) | (b1 << 8) | b2;
169 out.push(CHARS[((triple >> 18) & 0x3F) as usize] as char);
170 out.push(CHARS[((triple >> 12) & 0x3F) as usize] as char);
171 if chunk.len() > 1 {
172 out.push(CHARS[((triple >> 6) & 0x3F) as usize] as char);
173 } else {
174 out.push('=');
175 }
176 if chunk.len() > 2 {
177 out.push(CHARS[(triple & 0x3F) as usize] as char);
178 } else {
179 out.push('=');
180 }
181 }
182 out
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn new_provider() {
191 let p = CryptoProvider::new("TestCrypto");
192 assert_eq!(p.name(), "TestCrypto");
193 assert!(p.version_ref().is_none());
194 assert!(p.company_ref().is_none());
195 assert!(p.protocol_ver_ref().is_none());
196 assert!(p.extend_data_ref().is_none());
197 }
198
199 #[test]
200 fn builder_chain() {
201 let p = CryptoProvider::new("SM4Provider")
202 .version("1.0")
203 .company("ACME")
204 .protocol_ver("2.0");
205 assert_eq!(p.name(), "SM4Provider");
206 assert_eq!(p.version_ref(), Some("1.0"));
207 assert_eq!(p.company_ref(), Some("ACME"));
208 assert_eq!(p.protocol_ver_ref(), Some("2.0"));
209 }
210
211 #[test]
212 fn xml_name_only() {
213 let p = CryptoProvider::new("TestProvider");
214 let xml = p.to_xml_string();
215 assert!(xml.contains(r#"Name="TestProvider""#));
216 assert!(xml.contains("<ofd:Provider"));
217 assert!(xml.ends_with("/>"));
218 }
219
220 #[test]
221 fn xml_all_attributes() {
222 let p = CryptoProvider::new("SM4")
223 .version("1.0")
224 .company("TestCo")
225 .protocol_ver("2.0");
226 let xml = p.to_xml_string();
227 assert!(xml.contains(r#"Name="SM4""#));
228 assert!(xml.contains(r#"Version="1.0""#));
229 assert!(xml.contains(r#"Company="TestCo""#));
230 assert!(xml.contains(r#"ProtocolVer="2.0""#));
231 }
232
233 #[test]
234 fn xml_with_extend_data() {
235 let p = CryptoProvider::new("Test").extend_data(vec![0x01, 0x02, 0x03]);
236 let xml = p.to_xml_string();
237 assert!(xml.contains("<ofd:ExtendData>"));
238 assert!(xml.contains("</ofd:Provider>"));
239 assert!(xml.contains("AQID"));
241 }
242
243 #[test]
244 fn base64_encode_basic() {
245 assert_eq!(base64_encode(b""), "");
246 assert_eq!(base64_encode(b"f"), "Zg==");
247 assert_eq!(base64_encode(b"fo"), "Zm8=");
248 assert_eq!(base64_encode(b"foo"), "Zm9v");
249 assert_eq!(base64_encode(b"foob"), "Zm9vYg==");
250 assert_eq!(base64_encode(b"fooba"), "Zm9vYmE=");
251 assert_eq!(base64_encode(b"foobar"), "Zm9vYmFy");
252 }
253
254 #[test]
255 fn clone_provider() {
256 let p = CryptoProvider::new("Test").version("1.0");
257 let cloned = p.clone();
258 assert_eq!(cloned.name(), "Test");
259 assert_eq!(cloned.version_ref(), Some("1.0"));
260 }
261
262 #[test]
263 fn extend_data_ref() {
264 let p = CryptoProvider::new("Test").extend_data(vec![10, 20, 30]);
265 assert_eq!(p.extend_data_ref(), Some(&[10u8, 20, 30][..]));
266 }
267}