using LTEMvcApp.Models; using Newtonsoft.Json; using System.Text.RegularExpressions; namespace LTEMvcApp.Services; /// /// LTE日志解析服务 - 实现JavaScript _logListParse方法的核心功能 /// public class LogParserService { #region 常量定义 /// /// HFN回绕阈值 /// private const int HFN_WRAP_THRESHOLD = 512; /// /// 最大日志数量 /// private const int LOGS_MAX = 2000000; #endregion #region 正则表达式 /// /// PHY层日志正则表达式 /// private static readonly Regex RegExpPhy = new(@"^([a-f0-9\-]+)\s+([a-f0-9\-]+)\s+([\d\.\-]+) (\w+): (.+)", RegexOptions.Compiled); /// /// 信息1正则表达式 /// private static readonly Regex RegExpInfo1 = new(@"^([\w\-]+): (.+)", RegexOptions.Compiled); /// /// 信息2正则表达式 /// private static readonly Regex RegExpInfo2 = new(@"^([\w]+) (.+)", RegexOptions.Compiled); /// /// IP日志正则表达式 /// private static readonly Regex RegExpIP = new(@"^(len=\d+)\s+(\S+)\s+(.*)", RegexOptions.Compiled); /// /// IPsec日志正则表达式 /// private static readonly Regex RegExpIPsec = new(@"^len=(\d+)\s+(.*)", RegexOptions.Compiled); /// /// SDU长度正则表达式 /// private static readonly Regex RegExpSDULen = new(@"SDU_len=(\d+)", RegexOptions.Compiled); /// /// SIP日志正则表达式 /// private static readonly Regex RegExpSIP = new(@"^([:\.\[\]\da-f]+)\s+(\S+) (.+)", RegexOptions.Compiled); /// /// 媒体请求正则表达式 /// private static readonly Regex RegExpMediaReq = new(@"^(\S+) (.+)", RegexOptions.Compiled); /// /// 信号记录正则表达式 /// private static readonly Regex RegExpSignalRecord = new(@"Link:\s([\w\d]+)@(\d+)", RegexOptions.Compiled); /// /// 小区ID正则表达式 /// private static readonly Regex RegExpCellID = new(@"^([a-f0-9\-]+) (.+)", RegexOptions.Compiled); /// /// RRC UE ID正则表达式 /// private static readonly Regex RegExpRRC_UE_ID = new(@"Changing UE_ID to 0x(\d+)", RegexOptions.Compiled); /// /// RRC TMSI正则表达式 /// private static readonly Regex RegExpRRC_TMSI = new(@"(5G|m)-TMSI '([\dA-F]+)'H", RegexOptions.Compiled); /// /// RRC新ID正则表达式 /// private static readonly Regex RegExpRRC_NEW_ID = new(@"newUE-Identity (['\dA-FH]+)", RegexOptions.Compiled); /// /// RRC CRNTI正则表达式 /// private static readonly Regex RegExpRRC_CRNTI = new(@"c-RNTI '([\dA-F]+)'H", RegexOptions.Compiled); /// /// NAS TMSI正则表达式 /// private static readonly Regex RegExpNAS_TMSI = new(@"m-TMSI = 0x([\da-f]+)", RegexOptions.Compiled); /// /// NAS 5G TMSI正则表达式 /// private static readonly Regex RegExpNAS_5GTMSI = new(@"5G-TMSI = 0x([\da-f]+)", RegexOptions.Compiled); /// /// RRC频段组合正则表达式 /// private static readonly Regex RegExpRRC_BC = new(@"(EUTRA|MRDC|NR|NRDC) band combinations", RegexOptions.Compiled); /// /// PDCCH正则表达式 /// private static readonly Regex RegExpPDCCH = new(@"^\s*(.+)=(\d+)$", RegexOptions.Compiled); /// /// S1/NGAP正则表达式 /// private static readonly Regex RegExpS1NGAP = new(@"^([\da-f\-]+)\s+([\da-f\-]+) (([^\s]+) .+)", RegexOptions.Compiled); /// /// 十六进制转储正则表达式 /// private static readonly Regex RegExpHexDump = new(@"^[\da-f]+:(\s+[\da-f]{2}){1,16}\s+.{1,16}$", RegexOptions.Compiled); #endregion #region 私有字段 /// /// 字符串到ID的映射缓存 /// private readonly Dictionary _stringToIdCache = new(); /// /// ID到字符串的映射缓存 /// private readonly Dictionary _idToStringCache = new(); /// /// 字符串ID计数器 /// private int _stringIdCounter = 0; #endregion #region 公共方法 /// /// 解析日志列表 - 对应JavaScript的_logListParse方法 /// /// LTE客户端 /// 日志列表 /// 是否为WebSocket消息 public void ParseLogList(LTEClient client, List logs, bool isWebSocket = false) { var length = logs.Count; for (int i = 0; i < length; i++) { var log = logs[i]; log.Message = log.Message + ""; client.AddLog(log); // 解析消息 switch (log.Layer) { case "PHY": if (!ParsePhyLog(client, log, log.Message, isWebSocket)) continue; ProcessPhyLog(client, log); break; case "RRC": ParseCellId(client, log, isWebSocket); var rrcUeIdMatch = RegExpRRC_UE_ID.Match(log.Message); if (rrcUeIdMatch.Success) { SetSameUe(client, log, int.Parse(rrcUeIdMatch.Groups[1].Value, System.Globalization.NumberStyles.HexNumber)); continue; } var infoMatch = RegExpInfo1.Match(log.Message); if (infoMatch.Success) { if (!SetLogInfo(client, log, infoMatch.Groups[1].Value)) continue; log.Message = infoMatch.Groups[2].Value; ProcessRrcLog(client, log); } var bcMatch = RegExpRRC_BC.Match(log.Message); if (bcMatch.Success) { try { var data = log.GetDataString(); var jsonData = JsonConvert.DeserializeObject(data); // 处理频段组合信息 } catch { // 忽略JSON解析错误 } } break; case "NAS": ParseCellId(client, log, isWebSocket); var nasTmsiMatch = RegExpNAS_TMSI.Match(log.Message); if (nasTmsiMatch.Success) { SetTmsi(client, log, nasTmsiMatch.Groups[1].Value); continue; } var nas5gTmsiMatch = RegExpNAS_5GTMSI.Match(log.Message); if (nas5gTmsiMatch.Success) { SetTmsi(client, log, nas5gTmsiMatch.Groups[1].Value); continue; } var nasInfoMatch = RegExpInfo2.Match(log.Message); if (nasInfoMatch.Success) { if (!SetLogInfo(client, log, nasInfoMatch.Groups[1].Value)) continue; log.Message = nasInfoMatch.Groups[2].Value; ProcessNasLog(client, log); } break; case "MAC": ParseCellId(client, log, isWebSocket); ParseMacLog(client, log); break; case "IP": var ipMatch = RegExpIP.Match(log.Message); if (ipMatch.Success) { var lenPart = ipMatch.Groups[1].Value; log.IpLen = int.Parse(lenPart.Split('=')[1]); if (!SetLogInfo(client, log, ipMatch.Groups[2].Value)) continue; log.Message = ipMatch.Groups[3].Value; client.HasData = true; } break; case "GTPU": var sduLenMatch = RegExpSDULen.Match(log.Message); if (sduLenMatch.Success) { log.SduLen = int.Parse(sduLenMatch.Groups[1].Value); client.HasData = true; } break; case "S1AP": case "NGAP": var s1ngMatch = RegExpS1NGAP.Match(log.Message); if (s1ngMatch.Success) { log.Message = s1ngMatch.Groups[3].Value; var coreId = int.TryParse(s1ngMatch.Groups[1].Value, System.Globalization.NumberStyles.HexNumber, null, out var core) ? core : (int?)null; var ranId = int.TryParse(s1ngMatch.Groups[2].Value, System.Globalization.NumberStyles.HexNumber, null, out var ran) ? ran : (int?)null; log.LinkIds = new LinkIds { Core = coreId, Ran = ranId }; } break; case "SIP": var sipMatch = RegExpSIP.Match(log.Message); if (sipMatch.Success) { if (!SetLogInfo(client, log, sipMatch.Groups[2].Value)) continue; log.Message = sipMatch.Groups[3].Value; } break; case "MEDIA": var mediaMatch = RegExpMediaReq.Match(log.Message); if (mediaMatch.Success) { if (!SetLogInfo(client, log, mediaMatch.Groups[1].Value)) continue; log.Message = mediaMatch.Groups[2].Value; } break; case "IPsec": var ipsecMatch = RegExpIPsec.Match(log.Message); if (ipsecMatch.Success) { log.IpLen = int.Parse(ipsecMatch.Groups[1].Value); log.Message = ipsecMatch.Groups[2].Value; } break; default: break; } // 处理提示信息 ProcessHints(client, log); } // 限制日志数量 if (client.LogCount > LOGS_MAX) { var excess = client.LogCount - LOGS_MAX; client.Logs.RemoveRange(0, excess); } } #endregion #region 私有方法 /// /// 解析PHY日志 /// private bool ParsePhyLog(LTEClient client, LTELog log, string message, bool isWebSocket) { if (!isWebSocket) { var phyMatch = RegExpPhy.Match(message); if (!phyMatch.Success) { Console.WriteLine($"Bad PHY log: {log}"); return false; } log.Cell = int.Parse(phyMatch.Groups[1].Value, System.Globalization.NumberStyles.HexNumber); log.Rnti = int.Parse(phyMatch.Groups[2].Value, System.Globalization.NumberStyles.HexNumber); log.Channel = phyMatch.Groups[4].Value; log.Message = phyMatch.Groups[5].Value; } client.HasPhy = true; client.HasCell = true; client.HasRnti = true; // 解析PHY参数 var lines = log.GetData(); foreach (var line in lines) { var parts = line.Split('='); if (parts.Length == 2) { ParsePhyParameter(client, log, parts[0].Trim(), parts[1].Trim()); } } return true; } /// /// 解析PHY参数 /// private void ParsePhyParameter(LTEClient client, LTELog log, string param, string value) { switch (param.ToLower()) { case "frame": if (int.TryParse(value, out var frame)) { log.Frame = frame; client.HasPhy = true; } break; case "subframe": if (int.TryParse(value, out var subframe)) { log.Subframe = subframe; } break; case "slot": if (int.TryParse(value, out var slot)) { log.Slot = slot; } break; case "symbol": if (int.TryParse(value, out var symbol)) { log.Symbol = symbol; } break; case "ant": if (int.TryParse(value, out var ant)) { log.AntennaPort = ant; } break; case "rb_start": if (int.TryParse(value, out var rbStart)) { log.RbStart = rbStart; client.HasRb = true; } break; case "rb_count": if (int.TryParse(value, out var rbCount)) { log.RbCount = rbCount; client.HasRb = true; } break; case "mcs": if (int.TryParse(value, out var mcs)) { log.Mcs = mcs; } break; case "tbs": if (int.TryParse(value, out var tbs)) { log.Tbs = tbs; } break; case "harq_id": if (int.TryParse(value, out var harqId)) { log.HarqId = harqId; } break; case "harq_ndi": if (bool.TryParse(value, out var harqNdi)) { log.HarqNdi = harqNdi; } break; case "harq_rv": if (int.TryParse(value, out var harqRv)) { log.HarqRedundancyVersion = harqRv; } break; } } /// /// 处理PHY日志 /// private void ProcessPhyLog(LTEClient client, LTELog log) { // 处理HARQ信息 if (log.HarqId.HasValue) { ProcessHarqLog(client, log, log.HarqId.Value); } // 处理帧信息 if (log.Frame.HasValue) { var frame = log.Frame.Value; var hfn = frame >> 10; var sfn = frame & 0x3FF; if (client.Frame.Timestamp == -1) { client.Frame.Timestamp = log.Timestamp; client.Frame.Hfn = hfn; client.Frame.Last = sfn; } else { var diff = sfn - client.Frame.Last; if (diff < -HFN_WRAP_THRESHOLD) { client.Frame.Hfn++; } else if (diff > HFN_WRAP_THRESHOLD) { client.Frame.Hfn--; } client.Frame.Last = sfn; } } } /// /// 处理HARQ日志 /// private void ProcessHarqLog(LTEClient client, LTELog log, int harq) { if (log.Cell.HasValue && log.UeId.HasValue) { var cell = log.Cell.Value; var ueId = log.UeId.Value; var key = ueId * 32 + harq; if (!client.LastHarq.ContainsKey(cell)) { client.LastHarq[cell] = new Dictionary(); } client.LastHarq[cell][key] = log; } } /// /// 解析小区ID /// private void ParseCellId(LTEClient client, LTELog log, bool isWebSocket) { if (!isWebSocket) { var cellMatch = RegExpCellID.Match(log.Message); if (cellMatch.Success) { log.Cell = int.Parse(cellMatch.Groups[1].Value, System.Globalization.NumberStyles.HexNumber); log.Message = cellMatch.Groups[2].Value; client.HasCell = true; } } } /// /// 设置日志信息 /// private bool SetLogInfo(LTEClient client, LTELog log, string info) { if (string.IsNullOrEmpty(info) || info == "?") return false; log.Info = StringToId(info); return true; } /// /// 字符串转ID /// private int StringToId(string str) { if (_stringToIdCache.TryGetValue(str, out var id)) { return id; } id = ++_stringIdCounter; _stringToIdCache[str] = id; _idToStringCache[id] = str; return id; } /// /// ID转字符串 /// private string IdToString(int id) { return _idToStringCache.TryGetValue(id, out var str) ? str : ""; } /// /// 设置相同UE /// private void SetSameUe(LTEClient client, LTELog log, int ueId) { log.UeId = ueId; if (!client.UeList.ContainsKey(ueId)) { client.UeList[ueId] = new UEInfo { UeId = ueId }; } } /// /// 处理RRC日志 /// private void ProcessRrcLog(LTEClient client, LTELog log) { // 处理RRC TMSI var tmsiMatch = RegExpRRC_TMSI.Match(log.Message); if (tmsiMatch.Success) { SetTmsi(client, log, tmsiMatch.Groups[2].Value); } // 处理RRC CRNTI var crntiMatch = RegExpRRC_CRNTI.Match(log.Message); if (crntiMatch.Success) { SetRnti(client, log, crntiMatch.Groups[1].Value); } } /// /// 处理NAS日志 /// private void ProcessNasLog(LTEClient client, LTELog log) { // NAS日志处理逻辑 } /// /// 解析MAC日志 /// private void ParseMacLog(LTEClient client, LTELog log) { // MAC日志解析逻辑 } /// /// 处理提示信息 /// private void ProcessHints(LTEClient client, LTELog log, Dictionary? config = null) { // 处理提示信息逻辑 } /// /// 设置TMSI /// private void SetTmsi(LTEClient client, LTELog log, string tmsi) { var tmsiId = int.Parse(tmsi, System.Globalization.NumberStyles.HexNumber); if (log.UeId.HasValue) { client.TmsiToUeId[tmsiId] = log.UeId.Value; } } /// /// 设置RNTI /// private void SetRnti(LTEClient client, LTELog log, string rnti) { var rntiId = int.Parse(rnti, System.Globalization.NumberStyles.HexNumber); if (log.UeId.HasValue) { client.RntiToUeId[rntiId] = log.UeId.Value; } } #endregion }