You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

638 lines
19 KiB

using LTEMvcApp.Models;
using Newtonsoft.Json;
using System.Text.RegularExpressions;
namespace LTEMvcApp.Services;
/// <summary>
/// LTE日志解析服务 - 实现JavaScript _logListParse方法的核心功能
/// </summary>
public class LogParserService
{
#region 常量定义
/// <summary>
/// HFN回绕阈值
/// </summary>
private const int HFN_WRAP_THRESHOLD = 512;
/// <summary>
/// 最大日志数量
/// </summary>
private const int LOGS_MAX = 2000000;
#endregion
#region 正则表达式
/// <summary>
/// PHY层日志正则表达式
/// </summary>
private static readonly Regex RegExpPhy = new(@"^([a-f0-9\-]+)\s+([a-f0-9\-]+)\s+([\d\.\-]+) (\w+): (.+)", RegexOptions.Compiled);
/// <summary>
/// 信息1正则表达式
/// </summary>
private static readonly Regex RegExpInfo1 = new(@"^([\w\-]+): (.+)", RegexOptions.Compiled);
/// <summary>
/// 信息2正则表达式
/// </summary>
private static readonly Regex RegExpInfo2 = new(@"^([\w]+) (.+)", RegexOptions.Compiled);
/// <summary>
/// IP日志正则表达式
/// </summary>
private static readonly Regex RegExpIP = new(@"^(len=\d+)\s+(\S+)\s+(.*)", RegexOptions.Compiled);
/// <summary>
/// IPsec日志正则表达式
/// </summary>
private static readonly Regex RegExpIPsec = new(@"^len=(\d+)\s+(.*)", RegexOptions.Compiled);
/// <summary>
/// SDU长度正则表达式
/// </summary>
private static readonly Regex RegExpSDULen = new(@"SDU_len=(\d+)", RegexOptions.Compiled);
/// <summary>
/// SIP日志正则表达式
/// </summary>
private static readonly Regex RegExpSIP = new(@"^([:\.\[\]\da-f]+)\s+(\S+) (.+)", RegexOptions.Compiled);
/// <summary>
/// 媒体请求正则表达式
/// </summary>
private static readonly Regex RegExpMediaReq = new(@"^(\S+) (.+)", RegexOptions.Compiled);
/// <summary>
/// 信号记录正则表达式
/// </summary>
private static readonly Regex RegExpSignalRecord = new(@"Link:\s([\w\d]+)@(\d+)", RegexOptions.Compiled);
/// <summary>
/// 小区ID正则表达式
/// </summary>
private static readonly Regex RegExpCellID = new(@"^([a-f0-9\-]+) (.+)", RegexOptions.Compiled);
/// <summary>
/// RRC UE ID正则表达式
/// </summary>
private static readonly Regex RegExpRRC_UE_ID = new(@"Changing UE_ID to 0x(\d+)", RegexOptions.Compiled);
/// <summary>
/// RRC TMSI正则表达式
/// </summary>
private static readonly Regex RegExpRRC_TMSI = new(@"(5G|m)-TMSI '([\dA-F]+)'H", RegexOptions.Compiled);
/// <summary>
/// RRC新ID正则表达式
/// </summary>
private static readonly Regex RegExpRRC_NEW_ID = new(@"newUE-Identity (['\dA-FH]+)", RegexOptions.Compiled);
/// <summary>
/// RRC CRNTI正则表达式
/// </summary>
private static readonly Regex RegExpRRC_CRNTI = new(@"c-RNTI '([\dA-F]+)'H", RegexOptions.Compiled);
/// <summary>
/// NAS TMSI正则表达式
/// </summary>
private static readonly Regex RegExpNAS_TMSI = new(@"m-TMSI = 0x([\da-f]+)", RegexOptions.Compiled);
/// <summary>
/// NAS 5G TMSI正则表达式
/// </summary>
private static readonly Regex RegExpNAS_5GTMSI = new(@"5G-TMSI = 0x([\da-f]+)", RegexOptions.Compiled);
/// <summary>
/// RRC频段组合正则表达式
/// </summary>
private static readonly Regex RegExpRRC_BC = new(@"(EUTRA|MRDC|NR|NRDC) band combinations", RegexOptions.Compiled);
/// <summary>
/// PDCCH正则表达式
/// </summary>
private static readonly Regex RegExpPDCCH = new(@"^\s*(.+)=(\d+)$", RegexOptions.Compiled);
/// <summary>
/// S1/NGAP正则表达式
/// </summary>
private static readonly Regex RegExpS1NGAP = new(@"^([\da-f\-]+)\s+([\da-f\-]+) (([^\s]+) .+)", RegexOptions.Compiled);
/// <summary>
/// 十六进制转储正则表达式
/// </summary>
private static readonly Regex RegExpHexDump = new(@"^[\da-f]+:(\s+[\da-f]{2}){1,16}\s+.{1,16}$", RegexOptions.Compiled);
#endregion
#region 私有字段
/// <summary>
/// 字符串到ID的映射缓存
/// </summary>
private readonly Dictionary<string, int> _stringToIdCache = new();
/// <summary>
/// ID到字符串的映射缓存
/// </summary>
private readonly Dictionary<int, string> _idToStringCache = new();
/// <summary>
/// 字符串ID计数器
/// </summary>
private int _stringIdCounter = 0;
#endregion
#region 公共方法
/// <summary>
/// 解析日志列表 - 对应JavaScript的_logListParse方法
/// </summary>
/// <param name="client">LTE客户端</param>
/// <param name="logs">日志列表</param>
/// <param name="isWebSocket">是否为WebSocket消息</param>
public void ParseLogList(LTEClient client, List<LTELog> 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<object>(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 私有方法
/// <summary>
/// 解析PHY日志
/// </summary>
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;
}
/// <summary>
/// 解析PHY参数
/// </summary>
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;
}
}
/// <summary>
/// 处理PHY日志
/// </summary>
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;
}
}
}
/// <summary>
/// 处理HARQ日志
/// </summary>
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<int, LTELog>();
}
client.LastHarq[cell][key] = log;
}
}
/// <summary>
/// 解析小区ID
/// </summary>
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;
}
}
}
/// <summary>
/// 设置日志信息
/// </summary>
private bool SetLogInfo(LTEClient client, LTELog log, string info)
{
if (string.IsNullOrEmpty(info) || info == "?")
return false;
log.Info = StringToId(info);
return true;
}
/// <summary>
/// 字符串转ID
/// </summary>
private int StringToId(string str)
{
if (_stringToIdCache.TryGetValue(str, out var id))
{
return id;
}
id = ++_stringIdCounter;
_stringToIdCache[str] = id;
_idToStringCache[id] = str;
return id;
}
/// <summary>
/// ID转字符串
/// </summary>
private string IdToString(int id)
{
return _idToStringCache.TryGetValue(id, out var str) ? str : "";
}
/// <summary>
/// 设置相同UE
/// </summary>
private void SetSameUe(LTEClient client, LTELog log, int ueId)
{
log.UeId = ueId;
if (!client.UeList.ContainsKey(ueId))
{
client.UeList[ueId] = new UEInfo { UeId = ueId };
}
}
/// <summary>
/// 处理RRC日志
/// </summary>
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);
}
}
/// <summary>
/// 处理NAS日志
/// </summary>
private void ProcessNasLog(LTEClient client, LTELog log)
{
// NAS日志处理逻辑
}
/// <summary>
/// 解析MAC日志
/// </summary>
private void ParseMacLog(LTEClient client, LTELog log)
{
// MAC日志解析逻辑
}
/// <summary>
/// 处理提示信息
/// </summary>
private void ProcessHints(LTEClient client, LTELog log, Dictionary<string, object>? config = null)
{
// 处理提示信息逻辑
}
/// <summary>
/// 设置TMSI
/// </summary>
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;
}
}
/// <summary>
/// 设置RNTI
/// </summary>
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
}