/src/suricata7/rust/src/rdp/log.rs
Line | Count | Source |
1 | | /* Copyright (C) 2019 Open Information Security Foundation |
2 | | * |
3 | | * You can copy, redistribute or modify this Program under the terms of |
4 | | * the GNU General Public License version 2 as published by the Free |
5 | | * Software Foundation. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * version 2 along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
15 | | * 02110-1301, USA. |
16 | | */ |
17 | | |
18 | | // Author: Zach Kelly <zach.kelly@lmco.com> |
19 | | |
20 | | use super::rdp::{RdpTransaction, RdpTransactionItem}; |
21 | | use crate::jsonbuilder::{JsonBuilder, JsonError}; |
22 | | use crate::rdp::parser::*; |
23 | | use crate::rdp::windows; |
24 | | use x509_parser::prelude::{X509Certificate, FromDer}; |
25 | | |
26 | | #[no_mangle] |
27 | 164 | pub extern "C" fn rs_rdp_to_json(tx: &mut RdpTransaction, js: &mut JsonBuilder) -> bool { |
28 | 164 | log(tx, js).is_ok() |
29 | 164 | } |
30 | | |
31 | | /// populate a json object with transactional information, for logging |
32 | 164 | fn log(tx: &RdpTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> { |
33 | 164 | js.open_object("rdp")?; |
34 | 164 | js.set_uint("tx_id", tx.id)?; |
35 | | |
36 | 164 | match &tx.item { |
37 | 75 | RdpTransactionItem::X224ConnectionRequest(ref x224) => x224_req_to_json(x224, js)?, |
38 | 54 | RdpTransactionItem::X224ConnectionConfirm(ref x224) => x224_conf_to_json(x224, js)?, |
39 | | |
40 | 24 | RdpTransactionItem::McsConnectRequest(ref mcs) => { |
41 | 24 | mcs_req_to_json(mcs, js)?; |
42 | | } |
43 | | |
44 | | RdpTransactionItem::McsConnectResponse(_) => { |
45 | | // no additional JSON data beyond `event_type` |
46 | 10 | js.set_string("event_type", "connect_response")?; |
47 | | } |
48 | | |
49 | 1 | RdpTransactionItem::TlsCertificateChain(chain) => { |
50 | 1 | js.set_string("event_type", "tls_handshake")?; |
51 | 1 | js.open_array("x509_serials")?; |
52 | 31 | for blob in chain { |
53 | 30 | if let Ok((_, cert)) = X509Certificate::from_der(&blob.data) { |
54 | 0 | js.append_string(&cert.tbs_certificate.serial.to_str_radix(16))?; |
55 | 30 | } |
56 | | } |
57 | 1 | js.close()?; |
58 | | } |
59 | | } |
60 | | |
61 | 164 | js.close()?; |
62 | 164 | Ok(()) |
63 | 164 | } |
64 | | |
65 | | /// json helper for X224ConnectionRequest |
66 | 75 | fn x224_req_to_json(x224: &X224ConnectionRequest, js: &mut JsonBuilder) -> Result<(), JsonError> { |
67 | | use crate::rdp::parser::NegotiationRequestFlags as Flags; |
68 | | |
69 | 75 | js.set_string("event_type", "initial_request")?; |
70 | 75 | if let Some(ref cookie) = x224.cookie { |
71 | 60 | js.set_string("cookie", &cookie.mstshash)?; |
72 | 15 | } |
73 | 75 | if let Some(ref req) = x224.negotiation_request { |
74 | 0 | if !req.flags.is_empty() { |
75 | 0 | js.open_array("flags")?; |
76 | 0 | if req.flags.contains(Flags::RESTRICTED_ADMIN_MODE_REQUIRED) { |
77 | 0 | js.append_string("restricted_admin_mode_required")?; |
78 | 0 | } |
79 | 0 | if req |
80 | 0 | .flags |
81 | 0 | .contains(Flags::REDIRECTED_AUTHENTICATION_MODE_REQUIRED) |
82 | | { |
83 | 0 | js.append_string("redirected_authentication_mode_required")?; |
84 | 0 | } |
85 | 0 | if req.flags.contains(Flags::CORRELATION_INFO_PRESENT) { |
86 | 0 | js.append_string("correlation_info_present")?; |
87 | 0 | } |
88 | 0 | js.close()?; |
89 | 0 | } |
90 | 75 | } |
91 | | |
92 | 75 | Ok(()) |
93 | 75 | } |
94 | | |
95 | | /// json helper for X224ConnectionConfirm |
96 | 54 | fn x224_conf_to_json(x224: &X224ConnectionConfirm, js: &mut JsonBuilder) -> Result<(), JsonError> { |
97 | | use crate::rdp::parser::NegotiationResponseFlags as Flags; |
98 | | |
99 | 54 | js.set_string("event_type", "initial_response")?; |
100 | 54 | if let Some(ref from_server) = x224.negotiation_from_server { |
101 | 0 | match &from_server { |
102 | 0 | NegotiationFromServer::Response(ref resp) => { |
103 | 0 | if !resp.flags.is_empty() { |
104 | 0 | js.open_array("server_supports")?; |
105 | 0 | if resp.flags.contains(Flags::EXTENDED_CLIENT_DATA_SUPPORTED) { |
106 | 0 | js.append_string("extended_client_data")?; |
107 | 0 | } |
108 | 0 | if resp.flags.contains(Flags::DYNVC_GFX_PROTOCOL_SUPPORTED) { |
109 | 0 | js.append_string("dynvc_gfx")?; |
110 | 0 | } |
111 | | |
112 | | // NEGRSP_FLAG_RESERVED not logged |
113 | | |
114 | 0 | if resp.flags.contains(Flags::RESTRICTED_ADMIN_MODE_SUPPORTED) { |
115 | 0 | js.append_string("restricted_admin")?; |
116 | 0 | } |
117 | 0 | if resp |
118 | 0 | .flags |
119 | 0 | .contains(Flags::REDIRECTED_AUTHENTICATION_MODE_SUPPORTED) |
120 | | { |
121 | 0 | js.append_string("redirected_authentication")?; |
122 | 0 | } |
123 | 0 | js.close()?; |
124 | 0 | } |
125 | | |
126 | 0 | let protocol = match resp.protocol { |
127 | 0 | Protocol::ProtocolRdp => "rdp", |
128 | 0 | Protocol::ProtocolSsl => "ssl", |
129 | 0 | Protocol::ProtocolHybrid => "hybrid", |
130 | 0 | Protocol::ProtocolRdsTls => "rds_tls", |
131 | 0 | Protocol::ProtocolHybridEx => "hybrid_ex", |
132 | | }; |
133 | 0 | js.set_string("protocol", protocol)?; |
134 | | } |
135 | | |
136 | 0 | NegotiationFromServer::Failure(ref fail) => match fail.code { |
137 | | NegotiationFailureCode::SslRequiredByServer => { |
138 | 0 | js.set_uint( |
139 | 0 | "error_code", |
140 | 0 | NegotiationFailureCode::SslRequiredByServer as u64, |
141 | 0 | )?; |
142 | 0 | js.set_string("reason", "ssl required by server")?; |
143 | | } |
144 | | NegotiationFailureCode::SslNotAllowedByServer => { |
145 | 0 | js.set_uint( |
146 | 0 | "error_code", |
147 | 0 | NegotiationFailureCode::SslNotAllowedByServer as u64, |
148 | 0 | )?; |
149 | 0 | js.set_string("reason", "ssl not allowed by server")?; |
150 | | } |
151 | | NegotiationFailureCode::SslCertNotOnServer => { |
152 | 0 | js.set_uint( |
153 | 0 | "error_code", |
154 | 0 | NegotiationFailureCode::SslCertNotOnServer as u64, |
155 | 0 | )?; |
156 | 0 | js.set_string("reason", "ssl cert not on server")?; |
157 | | } |
158 | | NegotiationFailureCode::InconsistentFlags => { |
159 | 0 | js.set_uint( |
160 | 0 | "error_code", |
161 | 0 | NegotiationFailureCode::InconsistentFlags as u64, |
162 | 0 | )?; |
163 | 0 | js.set_string("reason", "inconsistent flags")?; |
164 | | } |
165 | | NegotiationFailureCode::HybridRequiredByServer => { |
166 | 0 | js.set_uint( |
167 | 0 | "error_code", |
168 | 0 | NegotiationFailureCode::HybridRequiredByServer as u64, |
169 | 0 | )?; |
170 | 0 | js.set_string("reason", "hybrid required by server")?; |
171 | | } |
172 | | NegotiationFailureCode::SslWithUserAuthRequiredByServer => { |
173 | 0 | js.set_uint( |
174 | 0 | "error_code", |
175 | 0 | NegotiationFailureCode::SslWithUserAuthRequiredByServer as u64, |
176 | 0 | )?; |
177 | 0 | js.set_string("reason", "ssl with user auth required by server")?; |
178 | | } |
179 | | }, |
180 | | } |
181 | 54 | } |
182 | | |
183 | 54 | Ok(()) |
184 | 54 | } |
185 | | |
186 | | /// json helper for McsConnectRequest |
187 | 24 | fn mcs_req_to_json(mcs: &McsConnectRequest, js: &mut JsonBuilder) -> Result<(), JsonError> { |
188 | | // placeholder string value. We do not simply omit "unknown" values so that they can |
189 | | // help indicate that a given enum may be out of date (new Windows version, etc.) |
190 | 24 | let unknown = String::from("unknown"); |
191 | | |
192 | 24 | js.set_string("event_type", "connect_request")?; |
193 | 83 | for child in &mcs.children { |
194 | 59 | match child { |
195 | 24 | McsConnectRequestChild::CsClientCore(ref client) => { |
196 | 24 | js.open_object("client")?; |
197 | | |
198 | 24 | match client.version { |
199 | 24 | Some(ref ver) => { |
200 | 24 | js.set_string("version", &version_to_string(ver, "v"))?; |
201 | | } |
202 | | None => { |
203 | 0 | js.set_string("version", &unknown)?; |
204 | | } |
205 | | } |
206 | | |
207 | 24 | js.set_uint("desktop_width", client.desktop_width as u64)?; |
208 | 24 | js.set_uint("desktop_height", client.desktop_height as u64)?; |
209 | | |
210 | 24 | if let Some(depth) = get_color_depth(client) { |
211 | 20 | js.set_uint("color_depth", depth)?; |
212 | 4 | } |
213 | | |
214 | | // sas_sequence not logged |
215 | | |
216 | 24 | js.set_string( |
217 | 24 | "keyboard_layout", |
218 | 24 | &windows::lcid_to_string(client.keyboard_layout, &unknown), |
219 | 0 | )?; |
220 | | |
221 | 24 | js.set_string( |
222 | 24 | "build", |
223 | 24 | &windows::os_to_string(&client.client_build, &unknown), |
224 | 0 | )?; |
225 | | |
226 | 24 | if !client.client_name.is_empty() { |
227 | 24 | js.set_string("client_name", &client.client_name)?; |
228 | 0 | } |
229 | | |
230 | 24 | if let Some(ref kb) = client.keyboard_type { |
231 | 14 | js.set_string("keyboard_type", &keyboard_to_string(kb))?; |
232 | 10 | } |
233 | | |
234 | 24 | if client.keyboard_subtype != 0 { |
235 | 8 | js.set_uint("keyboard_subtype", client.keyboard_subtype as u64)?; |
236 | 16 | } |
237 | | |
238 | 24 | if client.keyboard_function_key != 0 { |
239 | 23 | js.set_uint("function_keys", client.keyboard_function_key as u64)?; |
240 | 1 | } |
241 | | |
242 | 24 | if !client.ime_file_name.is_empty() { |
243 | 8 | js.set_string("ime", &client.ime_file_name)?; |
244 | 16 | } |
245 | | |
246 | | // |
247 | | // optional fields |
248 | | // |
249 | | |
250 | 24 | if let Some(id) = client.client_product_id { |
251 | 21 | js.set_uint("product_id", id as u64)?; |
252 | 3 | } |
253 | | |
254 | 24 | if let Some(serial) = client.serial_number { |
255 | 21 | if serial != 0 { |
256 | 3 | js.set_uint("serial_number", serial as u64)?; |
257 | 18 | } |
258 | 3 | } |
259 | | |
260 | | // supported_color_depth not logged |
261 | | |
262 | 24 | if let Some(ref early_capability_flags) = client.early_capability_flags { |
263 | | use crate::rdp::parser::EarlyCapabilityFlags as Flags; |
264 | | |
265 | 18 | if !early_capability_flags.is_empty() { |
266 | 16 | js.open_array("capabilities")?; |
267 | 16 | if early_capability_flags.contains(Flags::RNS_UD_CS_SUPPORT_ERRINFO_PDF) { |
268 | 16 | js.append_string("support_errinfo_pdf")?; |
269 | 0 | } |
270 | 16 | if early_capability_flags.contains(Flags::RNS_UD_CS_WANT_32BPP_SESSION) { |
271 | 2 | js.append_string("want_32bpp_session")?; |
272 | 14 | } |
273 | 16 | if early_capability_flags.contains(Flags::RNS_UD_CS_SUPPORT_STATUSINFO_PDU) |
274 | | { |
275 | 2 | js.append_string("support_statusinfo_pdu")?; |
276 | 14 | } |
277 | 16 | if early_capability_flags.contains(Flags::RNS_UD_CS_STRONG_ASYMMETRIC_KEYS) |
278 | | { |
279 | 0 | js.append_string("strong_asymmetric_keys")?; |
280 | 16 | } |
281 | | |
282 | | // RNS_UD_CS_UNUSED not logged |
283 | | |
284 | 16 | if early_capability_flags.contains(Flags::RNS_UD_CS_VALID_CONNECTION_TYPE) { |
285 | 0 | js.append_string("valid_connection_type")?; |
286 | 16 | } |
287 | 16 | if early_capability_flags |
288 | 16 | .contains(Flags::RNS_UD_CS_SUPPORT_MONITOR_LAYOUT_PDU) |
289 | | { |
290 | 0 | js.append_string("support_monitor_layout_pdu")?; |
291 | 16 | } |
292 | 16 | if early_capability_flags |
293 | 16 | .contains(Flags::RNS_UD_CS_SUPPORT_NETCHAR_AUTODETECT) |
294 | | { |
295 | 0 | js.append_string("support_netchar_autodetect")?; |
296 | 16 | } |
297 | 16 | if early_capability_flags |
298 | 16 | .contains(Flags::RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL) |
299 | | { |
300 | 0 | js.append_string("support_dynvc_gfx_protocol")?; |
301 | 16 | } |
302 | 16 | if early_capability_flags |
303 | 16 | .contains(Flags::RNS_UD_CS_SUPPORT_DYNAMIC_TIME_ZONE) |
304 | | { |
305 | 0 | js.append_string("support_dynamic_time_zone")?; |
306 | 16 | } |
307 | 16 | if early_capability_flags.contains(Flags::RNS_UD_CS_SUPPORT_HEARTBEAT_PDU) { |
308 | 0 | js.append_string("support_heartbeat_pdu")?; |
309 | 16 | } |
310 | 16 | js.close()?; |
311 | 2 | } |
312 | 6 | } |
313 | | |
314 | 24 | if let Some(ref id) = client.client_dig_product_id { |
315 | 16 | if !id.is_empty() { |
316 | 14 | js.set_string("id", id)?; |
317 | 2 | } |
318 | 8 | } |
319 | | |
320 | 24 | if let Some(ref hint) = client.connection_hint { |
321 | 16 | let s = match hint { |
322 | 1 | ConnectionHint::ConnectionHintModem => "modem", |
323 | 1 | ConnectionHint::ConnectionHintBroadbandLow => "low_broadband", |
324 | 0 | ConnectionHint::ConnectionHintSatellite => "satellite", |
325 | 0 | ConnectionHint::ConnectionHintBroadbandHigh => "high_broadband", |
326 | 0 | ConnectionHint::ConnectionHintWan => "wan", |
327 | 0 | ConnectionHint::ConnectionHintLan => "lan", |
328 | 0 | ConnectionHint::ConnectionHintAutoDetect => "autodetect", |
329 | 14 | ConnectionHint::ConnectionHintNotProvided => "", |
330 | | }; |
331 | 16 | if *hint != ConnectionHint::ConnectionHintNotProvided { |
332 | 2 | js.set_string("connection_hint", s)?; |
333 | 14 | } |
334 | 8 | } |
335 | | |
336 | | // server_selected_protocol not logged |
337 | | |
338 | 24 | if let Some(width) = client.desktop_physical_width { |
339 | 0 | js.set_uint("physical_width", width as u64)?; |
340 | 24 | } |
341 | | |
342 | 24 | if let Some(height) = client.desktop_physical_height { |
343 | 0 | js.set_uint("physical_height", height as u64)?; |
344 | 24 | } |
345 | | |
346 | 24 | if let Some(orientation) = client.desktop_orientation { |
347 | 0 | js.set_uint("desktop_orientation", orientation as u64)?; |
348 | 24 | } |
349 | | |
350 | 24 | if let Some(scale) = client.desktop_scale_factor { |
351 | 0 | js.set_uint("scale_factor", scale as u64)?; |
352 | 24 | } |
353 | | |
354 | 24 | if let Some(scale) = client.device_scale_factor { |
355 | 0 | js.set_uint("device_scale_factor", scale as u64)?; |
356 | 24 | } |
357 | 24 | js.close()?; |
358 | | } |
359 | | |
360 | 11 | McsConnectRequestChild::CsNet(ref net) => { |
361 | 11 | if !net.channels.is_empty() { |
362 | 11 | js.open_array("channels")?; |
363 | 44 | for channel in &net.channels { |
364 | 33 | js.append_string(channel)?; |
365 | | } |
366 | 11 | js.close()?; |
367 | 0 | } |
368 | | } |
369 | | |
370 | 24 | McsConnectRequestChild::CsUnknown(_) => {} |
371 | | } |
372 | | } |
373 | | |
374 | 24 | Ok(()) |
375 | 24 | } |
376 | | |
377 | | /// converts RdpClientVersion to a string, using the provided prefix |
378 | 24 | fn version_to_string(ver: &RdpClientVersion, prefix: &str) -> String { |
379 | 24 | let mut result = String::from(prefix); |
380 | 24 | match ver { |
381 | 0 | RdpClientVersion::V4 => result.push('4'), |
382 | 24 | RdpClientVersion::V5_V8_1 => result.push('5'), |
383 | 0 | RdpClientVersion::V10_0 => result.push_str("10.0"), |
384 | 0 | RdpClientVersion::V10_1 => result.push_str("10.1"), |
385 | 0 | RdpClientVersion::V10_2 => result.push_str("10.2"), |
386 | 0 | RdpClientVersion::V10_3 => result.push_str("10.3"), |
387 | 0 | RdpClientVersion::V10_4 => result.push_str("10.4"), |
388 | 0 | RdpClientVersion::V10_5 => result.push_str("10.5"), |
389 | 0 | RdpClientVersion::V10_6 => result.push_str("10.6"), |
390 | 0 | RdpClientVersion::V10_7 => result.push_str("10.7"), |
391 | | }; |
392 | 24 | result |
393 | 24 | } |
394 | | |
395 | | /// checks multiple client info fields to determine color depth |
396 | 24 | fn get_color_depth(client: &CsClientCoreData) -> Option<u64> { |
397 | | // first check high_color_depth |
398 | 19 | match client.high_color_depth { |
399 | 1 | Some(HighColorDepth::HighColor4Bpp) => return Some(4), |
400 | 0 | Some(HighColorDepth::HighColor8Bpp) => return Some(8), |
401 | 14 | Some(HighColorDepth::HighColor15Bpp) => return Some(15), |
402 | 0 | Some(HighColorDepth::HighColor16Bpp) => return Some(16), |
403 | 0 | Some(HighColorDepth::HighColor24Bpp) => return Some(24), |
404 | 9 | _ => (), |
405 | | }; |
406 | | |
407 | | // if not present, try post_beta2_color_depth |
408 | 6 | match client.post_beta2_color_depth { |
409 | 0 | Some(PostBeta2ColorDepth::RnsUdColor4Bpp) => return Some(4), |
410 | 0 | Some(PostBeta2ColorDepth::RnsUdColor8Bpp) => return Some(8), |
411 | 0 | Some(PostBeta2ColorDepth::RnsUdColor16Bpp555) => return Some(15), |
412 | 0 | Some(PostBeta2ColorDepth::RnsUdColor16Bpp565) => return Some(16), |
413 | 0 | Some(PostBeta2ColorDepth::RnsUdColor24Bpp) => return Some(24), |
414 | 9 | _ => (), |
415 | | }; |
416 | | |
417 | | // if not present, try color_depth |
418 | 5 | match client.color_depth { |
419 | 0 | Some(ColorDepth::RnsUdColor4Bpp) => return Some(4), |
420 | 5 | Some(ColorDepth::RnsUdColor8Bpp) => return Some(8), |
421 | 4 | _ => return None, |
422 | | } |
423 | 24 | } |
424 | | |
425 | 14 | fn keyboard_to_string(kb: &KeyboardType) -> String { |
426 | 14 | let s = match kb { |
427 | 0 | KeyboardType::KbXt => "xt", |
428 | 0 | KeyboardType::KbIco => "ico", |
429 | 0 | KeyboardType::KbAt => "at", |
430 | 14 | KeyboardType::KbEnhanced => "enhanced", |
431 | 0 | KeyboardType::Kb1050 => "1050", |
432 | 0 | KeyboardType::Kb9140 => "9140", |
433 | 0 | KeyboardType::KbJapanese => "jp", |
434 | | }; |
435 | 14 | String::from(s) |
436 | 14 | } |
437 | | |
438 | | #[cfg(test)] |
439 | | mod tests { |
440 | | use super::*; |
441 | | |
442 | | // for now, testing of JsonBuilder output is done by suricata-verify |
443 | | |
444 | | #[test] |
445 | | fn test_version_string() { |
446 | | assert_eq!("v10.7", version_to_string(&RdpClientVersion::V10_7, "v")); |
447 | | } |
448 | | |
449 | | #[test] |
450 | | fn test_color_depth_high() { |
451 | | let core_data = CsClientCoreData { |
452 | | version: None, |
453 | | desktop_width: 1280, |
454 | | desktop_height: 768, |
455 | | color_depth: Some(ColorDepth::RnsUdColor4Bpp), |
456 | | sas_sequence: None, |
457 | | keyboard_layout: 0x409, |
458 | | client_build: windows::OperatingSystem { |
459 | | build: windows::Build::Win10_17763, |
460 | | suffix: windows::Suffix::Rs5, |
461 | | }, |
462 | | client_name: String::from("SERVER-XYZ"), |
463 | | keyboard_type: None, |
464 | | keyboard_subtype: 0, |
465 | | keyboard_function_key: 12, |
466 | | ime_file_name: String::from(""), |
467 | | post_beta2_color_depth: Some(PostBeta2ColorDepth::RnsUdColor8Bpp), |
468 | | client_product_id: None, |
469 | | serial_number: None, |
470 | | high_color_depth: Some(HighColorDepth::HighColor24Bpp), |
471 | | supported_color_depth: None, |
472 | | early_capability_flags: None, |
473 | | client_dig_product_id: None, |
474 | | connection_hint: None, |
475 | | server_selected_protocol: None, |
476 | | desktop_physical_width: None, |
477 | | desktop_physical_height: None, |
478 | | desktop_orientation: None, |
479 | | desktop_scale_factor: None, |
480 | | device_scale_factor: None, |
481 | | }; |
482 | | assert_eq!(Some(24), get_color_depth(&core_data)); |
483 | | } |
484 | | |
485 | | #[test] |
486 | | fn test_color_depth_post_beta2() { |
487 | | let core_data = CsClientCoreData { |
488 | | version: None, |
489 | | desktop_width: 1280, |
490 | | desktop_height: 768, |
491 | | color_depth: Some(ColorDepth::RnsUdColor4Bpp), |
492 | | sas_sequence: None, |
493 | | keyboard_layout: 0x409, |
494 | | client_build: windows::OperatingSystem { |
495 | | build: windows::Build::Win10_17763, |
496 | | suffix: windows::Suffix::Rs5, |
497 | | }, |
498 | | client_name: String::from("SERVER-XYZ"), |
499 | | keyboard_type: None, |
500 | | keyboard_subtype: 0, |
501 | | keyboard_function_key: 12, |
502 | | ime_file_name: String::from(""), |
503 | | post_beta2_color_depth: Some(PostBeta2ColorDepth::RnsUdColor8Bpp), |
504 | | client_product_id: None, |
505 | | serial_number: None, |
506 | | high_color_depth: None, |
507 | | supported_color_depth: None, |
508 | | early_capability_flags: None, |
509 | | client_dig_product_id: None, |
510 | | connection_hint: None, |
511 | | server_selected_protocol: None, |
512 | | desktop_physical_width: None, |
513 | | desktop_physical_height: None, |
514 | | desktop_orientation: None, |
515 | | desktop_scale_factor: None, |
516 | | device_scale_factor: None, |
517 | | }; |
518 | | assert_eq!(Some(8), get_color_depth(&core_data)); |
519 | | } |
520 | | |
521 | | #[test] |
522 | | fn test_color_depth_basic() { |
523 | | let core_data = CsClientCoreData { |
524 | | version: None, |
525 | | desktop_width: 1280, |
526 | | desktop_height: 768, |
527 | | color_depth: Some(ColorDepth::RnsUdColor4Bpp), |
528 | | sas_sequence: None, |
529 | | keyboard_layout: 0x409, |
530 | | client_build: windows::OperatingSystem { |
531 | | build: windows::Build::Win10_17763, |
532 | | suffix: windows::Suffix::Rs5, |
533 | | }, |
534 | | client_name: String::from("SERVER-XYZ"), |
535 | | keyboard_type: None, |
536 | | keyboard_subtype: 0, |
537 | | keyboard_function_key: 12, |
538 | | ime_file_name: String::from(""), |
539 | | post_beta2_color_depth: None, |
540 | | client_product_id: None, |
541 | | serial_number: None, |
542 | | high_color_depth: None, |
543 | | supported_color_depth: None, |
544 | | early_capability_flags: None, |
545 | | client_dig_product_id: None, |
546 | | connection_hint: None, |
547 | | server_selected_protocol: None, |
548 | | desktop_physical_width: None, |
549 | | desktop_physical_height: None, |
550 | | desktop_orientation: None, |
551 | | desktop_scale_factor: None, |
552 | | device_scale_factor: None, |
553 | | }; |
554 | | assert_eq!(Some(4), get_color_depth(&core_data)); |
555 | | } |
556 | | |
557 | | #[test] |
558 | | fn test_color_depth_missing() { |
559 | | let core_data = CsClientCoreData { |
560 | | version: None, |
561 | | desktop_width: 1280, |
562 | | desktop_height: 768, |
563 | | color_depth: None, |
564 | | sas_sequence: None, |
565 | | keyboard_layout: 0x409, |
566 | | client_build: windows::OperatingSystem { |
567 | | build: windows::Build::Win10_17763, |
568 | | suffix: windows::Suffix::Rs5, |
569 | | }, |
570 | | client_name: String::from("SERVER-XYZ"), |
571 | | keyboard_type: None, |
572 | | keyboard_subtype: 0, |
573 | | keyboard_function_key: 12, |
574 | | ime_file_name: String::from(""), |
575 | | post_beta2_color_depth: None, |
576 | | client_product_id: None, |
577 | | serial_number: None, |
578 | | high_color_depth: None, |
579 | | supported_color_depth: None, |
580 | | early_capability_flags: None, |
581 | | client_dig_product_id: None, |
582 | | connection_hint: None, |
583 | | server_selected_protocol: None, |
584 | | desktop_physical_width: None, |
585 | | desktop_physical_height: None, |
586 | | desktop_orientation: None, |
587 | | desktop_scale_factor: None, |
588 | | device_scale_factor: None, |
589 | | }; |
590 | | assert!(get_color_depth(&core_data).is_none()); |
591 | | } |
592 | | |
593 | | #[test] |
594 | | fn test_keyboard_string() { |
595 | | assert_eq!("enhanced", keyboard_to_string(&KeyboardType::KbEnhanced)); |
596 | | } |
597 | | } |