diff --git a/HanvonHttpDiagnostics.cs b/HanvonHttpDiagnostics.cs new file mode 100644 index 0000000..6bbe465 --- /dev/null +++ b/HanvonHttpDiagnostics.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace HanvonF710XAttendanceService +{ + internal sealed class HttpExchangeResult + { + public string Url { get; set; } + public string Method { get; set; } = "POST"; + public string RequestContentType { get; set; } + public string RequestBodySanitized { get; set; } + public int StatusCode { get; set; } + public string ResponseHeaders { get; set; } + public string ResponseBody { get; set; } + public Dictionary ParsedResponse { get; set; } + public string Error { get; set; } + + public bool IsHttpSuccess => StatusCode >= 200 && StatusCode < 300; + } + + internal static class HanvonHttpDiagnostics + { + public static string SanitizeJsonBody(string json) + { + if (string.IsNullOrEmpty(json)) return json; + string sanitized = Regex.Replace(json, "(\"password\"\\s*:\\s*\")([^\"]*)(\")", "$1***$3", RegexOptions.IgnoreCase); + sanitized = Regex.Replace(sanitized, "(\"record\"\\s*:\\s*\")([^\"]{80})[^\"]*(\")", "$1$2...[truncated]$3", RegexOptions.IgnoreCase); + sanitized = Regex.Replace(sanitized, "(\"face\"\\s*:\\s*\")([^\"]{80})[^\"]*(\")", "$1$2...[truncated]$3", RegexOptions.IgnoreCase); + return sanitized; + } + + public static string DescribeTemplatePayload(string payload, string pushMode) + { + if (string.IsNullOrWhiteSpace(payload)) return "empty"; + string mode = pushMode ?? "unknown"; + int len = payload.Length; + string prefix = payload.Length > 24 ? payload.Substring(0, 24) : payload; + try + { + byte[] decoded = Convert.FromBase64String(payload.Length > 256 ? payload.Substring(0, 256) : payload); + if (decoded.Length >= 3) + { + return $"mode={mode} base64_len={len} decoded_prefix=0x{decoded[0]:X2}{decoded[1]:X2}{decoded[2]:X2} jpeg={(decoded[0] == 0xFF && decoded[1] == 0xD8)}"; + } + } + catch + { + return $"mode={mode} base64_len={len} prefix={prefix} (not standard base64 JPEG header)"; + } + return $"mode={mode} base64_len={len} prefix={prefix}"; + } + + public static void WriteExchange(TextWriter writer, string phase, string empId, HttpExchangeResult exchange) + { + writer.WriteLine($"[DB_TO_DEVICE] emp={empId} {phase}"); + writer.WriteLine($"URL={exchange?.Url ?? "(null)"}"); + writer.WriteLine($"HTTP={exchange?.StatusCode ?? 0}"); + writer.WriteLine($"Method={exchange?.Method ?? "POST"}"); + writer.WriteLine($"Content-Type={exchange?.RequestContentType ?? "(null)"}"); + writer.WriteLine($"request={exchange?.RequestBodySanitized ?? "(null)"}"); + if (!string.IsNullOrWhiteSpace(exchange?.ResponseHeaders)) + { + writer.WriteLine($"response_headers={exchange.ResponseHeaders}"); + } + writer.WriteLine($"response={exchange?.ResponseBody ?? exchange?.Error ?? "(null)"}"); + } + + public static void WriteResultSummary( + TextWriter writer, + string empId, + bool userExistsInList, + string listId, + string listName, + string listFaceField, + bool profileOk, + string profileFaceFlag, + string profilePhotoUrl, + bool recordOk, + int recordLen, + string templateFormat) + { + writer.WriteLine($"[DB_TO_DEVICE] emp={empId} RESULT"); + writer.WriteLine($"user_exists={(userExistsInList ? "true" : "false")} list_id={listId ?? ""} list_name={listName ?? ""} list_face={listFaceField ?? ""}"); + writer.WriteLine($"profile_getuserinfo_ok={profileOk} faceflag={profileFaceFlag ?? ""} photourl={profilePhotoUrl ?? ""}"); + writer.WriteLine($"face_exists={(recordOk ? "true" : "false")} backupnum_record_len={recordLen}"); + writer.WriteLine($"template_format={templateFormat ?? ""}"); + } + } +}