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.

1459 lines
42 KiB

2 months ago
using System.Collections.Immutable;
using System.Text.RegularExpressions;
1 month ago
using Newtonsoft.Json;
2 months ago
namespace LTEMvcApp.Models;
/// <summary>
/// LTE客户端类 - 对应JavaScript中的lte.client对象
/// </summary>
public class LTEClient
{
1 month ago
#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
2 months ago
#region 基础属性
/// <summary>
/// 客户端ID
/// </summary>
public int ClientId { get; set; }
/// <summary>
/// 客户端名称
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 客户端配置
/// </summary>
public ClientConfig Config { get; set; } = new();
/// <summary>
/// 客户端状态
/// </summary>
public ClientState State { get; set; } = ClientState.Stop;
/// <summary>
/// 版本信息
/// </summary>
public string? Version { get; set; }
/// <summary>
/// 许可证信息
/// </summary>
public string? License { get; set; }
/// <summary>
/// 模型类型
/// </summary>
public string? Model { get; set; }
/// <summary>
/// 是否有信号记录
/// </summary>
public bool HasSignalRecord { get; set; }
2 months ago
#endregion
#region 日志相关属性
/// <summary>
/// 日志列表
/// </summary>
public List<LTELog> Logs { get; set; } = new();
/// <summary>
/// 日志数量
/// </summary>
public int LogCount => Logs.Count;
/// <summary>
/// 是否已过滤
/// </summary>
public bool Filtered { get; set; }
/// <summary>
/// 重置标志
/// </summary>
public bool ResetFlag { get; set; }
#endregion
#region 解析器状态
/// <summary>
/// 最后HARQ信息
/// </summary>
public Dictionary<int, Dictionary<int, LTELog>> LastHarq { get; set; } = new();
/// <summary>
/// 最后NB HARQ信息
/// </summary>
public Dictionary<int, Dictionary<int, List<LTELog>>> LastNbHarq { get; set; } = new();
/// <summary>
/// 帧信息
/// </summary>
public FrameInfo Frame { get; set; } = new();
/// <summary>
/// TMSI到UE ID的映射
/// </summary>
public Dictionary<int, int> TmsiToUeId { get; set; } = new();
/// <summary>
/// RNTI到UE ID的映射
/// </summary>
public Dictionary<int, int> RntiToUeId { get; set; } = new();
/// <summary>
/// UE列表
/// </summary>
public Dictionary<int, UEInfo> UeList { get; set; } = new();
/// <summary>
/// 最后时间戳
/// </summary>
public long LastTimestamp { get; set; }
/// <summary>
/// 时间戳偏移
/// </summary>
public long TimestampOffset { get; set; }
/// <summary>
/// 最后小区
/// </summary>
public int? LastCell { get; set; }
#endregion
#region 功能标志
/// <summary>
/// 是否有小区信息
/// </summary>
public bool HasCell { get; set; }
/// <summary>
/// 是否有物理层信息
/// </summary>
public bool HasPhy { get; set; }
/// <summary>
/// 是否有数据
/// </summary>
public bool HasData { get; set; }
/// <summary>
/// 是否有RNTI
/// </summary>
public bool HasRnti { get; set; }
/// <summary>
/// 是否有资源块
/// </summary>
public bool HasRb { get; set; }
#endregion
#region 参数和组件
/// <summary>
/// 参数信息
/// </summary>
public Dictionary<string, object> Parameters { get; set; } = new();
/// <summary>
/// 组件列表
/// </summary>
public Dictionary<string, object> Components { get; set; } = new();
#endregion
#region 依赖注入
1 month ago
/// <summary>
/// 日志管理器
1 month ago
/// </summary>
1 month ago
private readonly LogsManager _lteLogs;
1 month ago
/// <summary>
/// 获取日志管理器实例
1 month ago
/// </summary>
1 month ago
public LogsManager LogsManager => _lteLogs;
1 month ago
#endregion
2 months ago
#region 构造函数
public LTEClient(ClientConfig config, LogsManager logsManager)
2 months ago
{
Config = config;
Name = config.Name;
ClientId = GenerateClientId();
1 month ago
_lteLogs = logsManager;
2 months ago
ResetParserState();
}
/// <summary>
/// 使用名称创建客户端
/// </summary>
/// <param name="name">客户端名称</param>
/// <param name="logsManager">日志管理器</param>
public LTEClient(string name, LogsManager logsManager)
2 months ago
{
Config = new ClientConfig { Name = name, Enabled = true };
Name = name;
ClientId = GenerateClientId();
1 month ago
_lteLogs = logsManager;
2 months ago
ResetParserState();
}
#endregion
1 month ago
#region 客户端控制方法
2 months ago
/// <summary>
/// 启动客户端
/// </summary>
public void Start()
{
SetState(ClientState.Start);
}
/// <summary>
/// 停止客户端
/// </summary>
public void Stop()
{
SetState(ClientState.Stop);
}
/// <summary>
/// 销毁客户端
/// </summary>
public void Destroy()
{
SetState(ClientState.Destroy);
}
1 month ago
#endregion
#region 日志管理方法
2 months ago
/// <summary>
/// 重置日志
/// </summary>
public void ResetLogs()
{
if (LogCount > 0)
{
ResetFlag = true;
Logs.Clear();
ResetParserState();
HasCell = false;
HasPhy = false;
HasData = false;
HasRnti = false;
HasRb = false;
HasSignalRecord = false;
}
}
/// <summary>
/// 添加日志
/// </summary>
public void AddLog(LTELog log)
{
// 检查时间戳回绕
var timestamp = log.Timestamp + TimestampOffset;
if (timestamp < LastTimestamp - 100)
{
Console.WriteLine($"Log wrap by {LastTimestamp - timestamp}");
timestamp += 86400000; // 24小时
TimestampOffset += 86400000;
}
LastTimestamp = log.Timestamp = timestamp;
log.Client = this;
log.Id = GenerateLogId();
}
1 month ago
#endregion
#region 工具方法
2 months ago
/// <summary>
/// 设置头信息
/// </summary>
public void SetHeaders(string[] headers)
{
// 实现头信息设置逻辑
}
/// <summary>
/// 初始化模型猜测
/// </summary>
public void LogModelGuessInit()
{
1 month ago
if (string.IsNullOrEmpty(Config.Model))
{
// 尝试所有模型
var modelList = new List<string> { "RUE", "UE", "PROBE", "ENB", "N3IWF", "MBMSGW", "MME", "IMS", "LICENSE", "MONITOR" };
if (!TryModelHint(modelList))
{
SetModel("MME"); // 默认模型
}
// 初始化模型猜测字典
_modelGuess = new Dictionary<string, bool>();
var layerConfig = GetLayerConfig();
foreach (var layer in layerConfig.Keys)
{
_modelGuess[layer] = false;
}
}
}
/// <summary>
/// 模型猜测字典
/// </summary>
private Dictionary<string, bool>? _modelGuess;
/// <summary>
/// 获取层配置
/// </summary>
/// <returns>层配置字典</returns>
private Dictionary<string, object> GetLayerConfig()
{
// 这里应该从 LogsManager 获取层配置
// 暂时返回一个基本的层配置
return new Dictionary<string, object>
{
{ "PHY", new { Color = "#606070" } },
{ "MAC", new { Color = "#10A0FF" } },
{ "RLC", new { Color = "#FFFF80" } },
{ "PDCP", new { Color = "#B0D0B0" } },
{ "RRC", new { Color = "#00FF60" } },
{ "NAS", new { Color = "#90FFC0" } },
{ "GTPU", new { Color = "#FF80FF" } },
{ "GTPC", new { Color = "#FFC0FF" } },
{ "IP", new { Color = "#E0E0E0" } },
{ "S1AP", new { Color = "#80FF00" } },
{ "NGAP", new { Color = "#5DD122" } },
{ "X2AP", new { Color = "#FF8000" } },
{ "XnAP", new { Color = "#FFB020" } },
{ "M2AP", new { Color = "#7F675B" } },
{ "IMS", new { Color = "#C0FF80" } },
{ "CX", new { Color = "#F49542" } },
{ "RX", new { Color = "#D4A190" } },
{ "COM", new { Color = "#C000FF" } },
{ "SIP", new { Color = "#C080FF" } },
{ "MEDIA", new { Color = "#800040" } },
{ "RTP", new { Color = "#FF00C0" } },
{ "PROD", new { Color = "#80C0FF" } },
{ "S6", new { Color = "#F44B42" } },
{ "S13", new { Color = "#D6F953" } },
{ "SGsAP", new { Color = "#FF7DB8" } },
{ "SBcAP", new { Color = "#8FA00F" } },
{ "N8", new { Color = "#106020" } },
{ "N12", new { Color = "#602020" } },
{ "N13", new { Color = "#202060" } },
{ "N17", new { Color = "#D6F953" } },
{ "N50", new { Color = "#8FA00F" } },
{ "MMS", new { Color = "#B4D98F" } },
{ "HTTP2", new { Color = "#644824" } },
{ "LCSAP", new { Color = "#cfd50d" } },
{ "LPPa", new { Color = "#0dcfd5" } },
{ "NL1", new { Color = "#d040cf" } },
{ "NRPPa", new { Color = "#0dd5cf" } },
{ "IKEV2", new { Color = "#C0B732" } },
{ "SWU", new { Color = "#101080" } },
{ "NWU", new { Color = "#2080B8" } },
{ "IPSEC", new { Color = "#F04010" } },
{ "N3IWF", new { Color = "#C080C0" } },
{ "TRX", new { Color = "#42C0a0" } },
{ "MON", new { Color = "#C0C080" } },
{ "EVENT", new { Color = "#80C0FF" } },
{ "ALARM", new { Color = "#FF8040" } },
{ "LIC", new { Color = "#ff80c0" } },
{ "OTS", new { Color = "#8080FF" } },
{ "ERROR", new { Color = "#ff0000" } }
};
2 months ago
}
/// <summary>
/// 模型猜测
/// </summary>
1 month ago
/// <param name="logs">日志列表</param>
2 months ago
public void LogModelGuess(List<Dictionary<string, object>> logs)
{
1 month ago
var modelGuess = _modelGuess;
if (modelGuess == null)
return;
// 标记所有出现的层
foreach (var log in logs)
{
if (log.TryGetValue("layer", out var layer) && layer is string layerStr)
{
modelGuess[layerStr] = true;
}
}
// 尝试根据层配置确定模型
var modelList = new List<string> { "RUE", "UE", "PROBE", "ENB", "N3IWF", "MBMSGW", "MME", "IMS", "LICENSE", "MONITOR" };
var availableModels = new List<string>(modelList);
// 根据层配置过滤模型
foreach (var layer in modelGuess.Keys)
{
if (!modelGuess[layer]) continue;
var layerConfig = GetLayerConfig();
if (layerConfig.TryGetValue(layer, out var config))
{
// 这里应该检查层的方向配置来过滤模型
// 暂时简化处理
var layerDir = GetLayerDirection(layer);
if (layerDir != null)
{
// 移除不支持的模型
availableModels.RemoveAll(model => !layerDir.ContainsKey(model));
}
}
// 如果只剩下一个模型,直接设置
if (availableModels.Count <= 1)
break;
}
// 如果找到了合适的模型,设置它
if (availableModels.Count > 0 && availableModels.Count < modelList.Count)
{
SetModel(availableModels[0]);
_modelGuess = null; // 清除猜测字典
}
}
/// <summary>
/// 获取层方向配置
/// </summary>
/// <param name="layer">层名称</param>
/// <returns>方向配置字典</returns>
private Dictionary<string, int>? GetLayerDirection(string layer)
{
// 这里应该从 LogsManager 获取层的方向配置
// 暂时返回一个基本的配置
return layer switch
{
"PHY" => new Dictionary<string, int> { { "UE", 2 }, { "PROBE", 3 }, { "ENB", 1 } },
"MAC" => new Dictionary<string, int> { { "UE", 2 }, { "PROBE", 3 }, { "ENB", 1 } },
"RLC" => new Dictionary<string, int> { { "UE", 2 }, { "PROBE", 3 }, { "ENB", 1 } },
"PDCP" => new Dictionary<string, int> { { "UE", 2 }, { "PROBE", 3 }, { "ENB", 1 } },
"RRC" => new Dictionary<string, int> { { "UE", 2 }, { "PROBE", 3 }, { "ENB", 1 } },
"NAS" => new Dictionary<string, int> { { "UE", 2 }, { "PROBE", 3 }, { "ENB", 3 }, { "N3IWF", 3 }, { "MME", 1 } },
"GTPU" => new Dictionary<string, int> { { "ENB", 2 }, { "N3IWF", 2 }, { "MME", 1 }, { "MBMSGW", 1 }, { "UE", 1 }, { "RUE", 2 } },
"GTPC" => new Dictionary<string, int> { { "MME", 2 } },
"IP" => new Dictionary<string, int> { { "RUE", 2 }, { "UE", 2 }, { "N3IWF", 3 }, { "PROBE", 3 }, { "MME", 3 } },
"S1AP" => new Dictionary<string, int> { { "ENB", 2 }, { "MME", 1 } },
"NGAP" => new Dictionary<string, int> { { "ENB", 2 }, { "MME", 1 }, { "N3IWF", 2 } },
"X2AP" => new Dictionary<string, int> { { "ENB", 2 } },
"XnAP" => new Dictionary<string, int> { { "ENB", 2 } },
"M2AP" => new Dictionary<string, int> { { "ENB", 2 }, { "MBMSGW", 1 } },
"IMS" => new Dictionary<string, int> { { "MME", 2 }, { "IMS", 1 } },
"CX" => new Dictionary<string, int> { { "MME", 2 }, { "IMS", 1 } },
"RX" => new Dictionary<string, int> { { "MME", 2 }, { "IMS", 1 } },
"COM" => new Dictionary<string, int> { { "*", 2 } },
"SIP" => new Dictionary<string, int> { { "IMS", 1 } },
"MEDIA" => new Dictionary<string, int> { { "IMS", 1 } },
"RTP" => new Dictionary<string, int> { { "IMS", 1 } },
"PROD" => new Dictionary<string, int> { },
"S6" => new Dictionary<string, int> { { "MME", 2 } },
"S13" => new Dictionary<string, int> { { "MME", 2 } },
"SGsAP" => new Dictionary<string, int> { { "MME", 2 } },
"SBcAP" => new Dictionary<string, int> { { "MME", 2 } },
"N8" => new Dictionary<string, int> { { "MME", 2 } },
"N12" => new Dictionary<string, int> { { "MME", 2 } },
"N13" => new Dictionary<string, int> { { "MME", 2 } },
"N17" => new Dictionary<string, int> { { "MME", 2 } },
"N50" => new Dictionary<string, int> { { "MME", 2 } },
"MMS" => new Dictionary<string, int> { { "MME", 2 } },
"HTTP2" => new Dictionary<string, int> { { "MME", 2 } },
"LCSAP" => new Dictionary<string, int> { { "MME", 2 } },
"LPPa" => new Dictionary<string, int> { { "ENB", 2 }, { "MME", 3 } },
"NL1" => new Dictionary<string, int> { { "MME", 2 } },
"NRPPa" => new Dictionary<string, int> { { "ENB", 2 }, { "MME", 3 } },
"IKEV2" => new Dictionary<string, int> { { "UE", 2 }, { "MME", 1 }, { "N3IWF", 1 } },
"SWU" => new Dictionary<string, int> { { "UE", 2 }, { "MME", 1 } },
"NWU" => new Dictionary<string, int> { { "UE", 2 }, { "MME", 1 } },
"IPSEC" => new Dictionary<string, int> { { "UE", 2 }, { "IMS", 1 }, { "N3IWF", 1 }, { "MME", 1 } },
"N3IWF" => new Dictionary<string, int> { { "UE", 2 }, { "N3IWF", 1 } },
"TRX" => new Dictionary<string, int> { { "UE", 2 }, { "ENB", 1 } },
"MON" => new Dictionary<string, int> { },
"EVENT" => new Dictionary<string, int> { },
"ALARM" => new Dictionary<string, int> { },
"LIC" => new Dictionary<string, int> { { "LICENSE", 1 } },
"OTS" => new Dictionary<string, int> { },
"ERROR" => new Dictionary<string, int> { },
_ => null
};
2 months ago
}
/// <summary>
/// 方向转换
/// </summary>
public int DirConvert(LTELog log)
{
// 实现方向转换逻辑
return 0;
}
/// <summary>
/// 字符串转ID - 委托给LogsManager
2 months ago
/// </summary>
public int StringToId(string str)
{
1 month ago
return _lteLogs.String2Id(str);
1 month ago
}
/// <summary>
/// ID转字符串 - 委托给LogsManager
1 month ago
/// </summary>
public string IdToString(int id)
{
1 month ago
return _lteLogs.Id2String(id);
1 month ago
}
#endregion
#region 日志解析方法
/// <summary>
/// 解析日志列表 - 对应JavaScript的_logListParse方法
/// </summary>
/// <param name="logs">日志列表</param>
/// <param name="isWebSocket">是否为WebSocket消息</param>
public void ParseLogList(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 + "";
AddLog(log);
// 解析消息
switch (log.Layer)
{
case "PHY":
if (!ParsePhyLog(log, log.Message, isWebSocket)) continue;
ProcessPhyLog(log);
break;
case "RRC":
ParseCellId(log, isWebSocket);
var rrcUeIdMatch = RegExpRRC_UE_ID.Match(log.Message);
if (rrcUeIdMatch.Success)
{
SetSameUe(log, int.Parse(rrcUeIdMatch.Groups[1].Value, System.Globalization.NumberStyles.HexNumber));
continue;
}
var infoMatch = RegExpInfo1.Match(log.Message);
if (infoMatch.Success)
{
if (!SetLogInfo(log, infoMatch.Groups[1].Value)) continue;
log.Message = infoMatch.Groups[2].Value;
ProcessRrcLog(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(log, isWebSocket);
var nasTmsiMatch = RegExpNAS_TMSI.Match(log.Message);
if (nasTmsiMatch.Success)
{
SetTmsi(log, nasTmsiMatch.Groups[1].Value);
continue;
}
var nas5gTmsiMatch = RegExpNAS_5GTMSI.Match(log.Message);
if (nas5gTmsiMatch.Success)
{
SetTmsi(log, nas5gTmsiMatch.Groups[1].Value);
continue;
}
var nasInfoMatch = RegExpInfo2.Match(log.Message);
if (nasInfoMatch.Success)
{
if (!SetLogInfo(log, nasInfoMatch.Groups[1].Value)) continue;
log.Message = nasInfoMatch.Groups[2].Value;
ProcessNasLog(log);
}
break;
case "MAC":
ParseCellId(log, isWebSocket);
ParseMacLog(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(log, ipMatch.Groups[2].Value)) continue;
log.Message = ipMatch.Groups[3].Value;
HasData = true;
}
break;
case "GTPU":
var sduLenMatch = RegExpSDULen.Match(log.Message);
if (sduLenMatch.Success)
{
log.SduLen = int.Parse(sduLenMatch.Groups[1].Value);
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(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(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(log);
}
// 限制日志数量
if (LogCount > LOGS_MAX)
{
var excess = LogCount - LOGS_MAX;
Logs.RemoveRange(0, excess);
}
}
#endregion
#region PHY层解析方法
/// <summary>
/// 解析PHY日志
/// </summary>
private bool ParsePhyLog(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;
}
HasPhy = true;
HasCell = true;
HasRnti = true;
// 解析PHY参数
var lines = log.GetData();
foreach (var line in lines)
{
var parts = line.Split('=');
if (parts.Length == 2)
{
ParsePhyParameter(log, parts[0].Trim(), parts[1].Trim());
}
}
return true;
}
/// <summary>
/// 解析PHY参数
/// </summary>
private void ParsePhyParameter(LTELog log, string param, string value)
{
switch (param.ToLower())
{
case "frame":
if (int.TryParse(value, out var frame))
{
log.Frame = frame;
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;
HasRb = true;
}
break;
case "rb_count":
if (int.TryParse(value, out var rbCount))
{
log.RbCount = rbCount;
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(LTELog log)
{
// 处理HARQ信息
if (log.HarqId.HasValue)
{
ProcessHarqLog(log, log.HarqId.Value);
}
// 处理帧信息
if (log.Frame.HasValue)
{
var frame = log.Frame.Value;
var hfn = frame >> 10;
var sfn = frame & 0x3FF;
if (Frame.Timestamp == -1)
{
Frame.Timestamp = log.Timestamp;
Frame.Hfn = hfn;
Frame.Last = sfn;
}
else
{
var diff = sfn - Frame.Last;
if (diff < -HFN_WRAP_THRESHOLD)
{
Frame.Hfn++;
}
else if (diff > HFN_WRAP_THRESHOLD)
{
Frame.Hfn--;
}
Frame.Last = sfn;
}
}
}
/// <summary>
/// 处理HARQ日志
/// </summary>
private void ProcessHarqLog(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 (!LastHarq.ContainsKey(cell))
{
LastHarq[cell] = new Dictionary<int, LTELog>();
}
LastHarq[cell][key] = log;
}
}
#endregion
#region 其他层解析方法
/// <summary>
/// 解析小区ID
/// </summary>
private void ParseCellId(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;
HasCell = true;
}
}
}
/// <summary>
/// 设置日志信息
/// </summary>
private bool SetLogInfo(LTELog log, string info)
{
if (string.IsNullOrEmpty(info) || info == "?")
return false;
log.Info = StringToId(info);
return true;
}
/// <summary>
/// 设置相同UE
/// </summary>
private void SetSameUe(LTELog log, int ueId)
{
log.UeId = ueId;
if (!UeList.ContainsKey(ueId))
{
UeList[ueId] = new UEInfo { UeId = ueId };
}
}
/// <summary>
/// 处理RRC日志
/// </summary>
private void ProcessRrcLog(LTELog log)
{
// 处理RRC TMSI
var tmsiMatch = RegExpRRC_TMSI.Match(log.Message);
if (tmsiMatch.Success)
{
SetTmsi(log, tmsiMatch.Groups[2].Value);
}
// 处理RRC CRNTI
var crntiMatch = RegExpRRC_CRNTI.Match(log.Message);
if (crntiMatch.Success)
{
SetRnti(log, crntiMatch.Groups[1].Value);
}
}
/// <summary>
/// 处理NAS日志
/// </summary>
private void ProcessNasLog(LTELog log)
{
// NAS日志处理逻辑
}
/// <summary>
/// 解析MAC日志
/// </summary>
private void ParseMacLog(LTELog log)
{
// MAC日志解析逻辑
}
/// <summary>
/// 处理提示信息
/// </summary>
private void ProcessHints(LTELog log, Dictionary<string, object>? config = null)
{
// 处理提示信息逻辑
}
/// <summary>
/// 设置TMSI
/// </summary>
private void SetTmsi(LTELog log, string tmsi)
{
var tmsiId = int.Parse(tmsi, System.Globalization.NumberStyles.HexNumber);
if (log.UeId.HasValue)
{
TmsiToUeId[tmsiId] = log.UeId.Value;
}
}
/// <summary>
/// 设置RNTI
/// </summary>
private void SetRnti(LTELog log, string rnti)
{
var rntiId = int.Parse(rnti, System.Globalization.NumberStyles.HexNumber);
if (log.UeId.HasValue)
{
RntiToUeId[rntiId] = log.UeId.Value;
}
2 months ago
}
#endregion
1 month ago
#region 私有辅助方法
2 months ago
/// <summary>
/// 设置状态
/// </summary>
private void SetState(ClientState state)
{
if (State != state)
{
State = state;
switch (state)
{
case ClientState.Stop:
ResetLogs();
break;
case ClientState.Start:
case ClientState.Connected:
break;
case ClientState.Destroy:
return;
}
}
}
/// <summary>
/// 重置解析器状态
/// </summary>
private void ResetParserState()
{
LastHarq.Clear();
LastNbHarq.Clear();
Frame = new FrameInfo();
TmsiToUeId.Clear();
RntiToUeId.Clear();
UeList.Clear();
LastTimestamp = 0;
TimestampOffset = 0;
LastCell = null;
1 month ago
// 注意:LogsManager 的字符串ID缓存是全局的,不需要重置
// 这样可以保持字符串ID的一致性
2 months ago
}
/// <summary>
/// 生成客户端ID
/// </summary>
private static int GenerateClientId()
{
return Interlocked.Increment(ref _clientIdCounter);
}
/// <summary>
/// 生成日志ID
/// </summary>
private static int GenerateLogId()
{
return Interlocked.Increment(ref _logIdCounter);
}
#endregion
#region 静态字段
private static int _clientIdCounter = 0;
private static int _logIdCounter = 0;
#endregion
1 month ago
#region 模型管理方法
/// <summary>
/// 模型提示信息
/// </summary>
private string? _modelHint;
/// <summary>
/// 传输方向
/// </summary>
public int TxDir { get; set; }
/// <summary>
/// 接收方向
/// </summary>
public int RxDir { get; set; }
/// <summary>
/// RAN ID列表
/// </summary>
public List<string> RanIds { get; set; } = new();
/// <summary>
/// 层方向配置
/// </summary>
private Dictionary<string, Dictionary<string, int>> _layerDir = new();
/// <summary>
/// 尝试模型提示
/// </summary>
/// <param name="models">模型列表</param>
/// <returns>是否成功设置模型</returns>
public bool TryModelHint(List<string> models)
{
if (string.IsNullOrEmpty(_modelHint))
return false;
foreach (var model in models)
{
if (_clientModelConfigs.TryGetValue(model, out var config))
{
var modelHint = config.ModelHint;
if (string.IsNullOrEmpty(modelHint))
{
// 没有提示,基于模型名称
modelHint = model;
}
if (Regex.IsMatch(_modelHint, modelHint, RegexOptions.IgnoreCase))
{
return SetModel(model);
}
}
}
return false;
}
/// <summary>
/// 设置模型
/// </summary>
/// <param name="model">模型名称</param>
/// <param name="force">是否强制设置</param>
/// <returns>是否成功设置</returns>
public bool SetModel(string model, bool force = false)
{
// 如果模型相同且不强制,直接返回成功
if (model == Config.Model && !force)
return true;
Config.Model = model;
// 根据模型设置传输方向
switch (model?.ToUpper())
{
case "ENB":
TxDir = 2; // DIR_DL
RxDir = 1; // DIR_UL
RanIds.Clear();
break;
case "UE":
TxDir = 1; // DIR_UL
RxDir = 2; // DIR_DL
break;
default:
break;
}
// 设置层方向配置
if (_clientModelConfigs.TryGetValue(model ?? "", out var config))
{
_layerDir = config.LayerDir ?? new Dictionary<string, Dictionary<string, int>>();
}
else
{
_layerDir.Clear();
}
// 通知日志管理器更新网格列
_lteLogs.GridColumnsUpdate();
return true;
}
/// <summary>
/// 设置模型提示
/// </summary>
/// <param name="hint">提示信息</param>
public void SetModelHint(string hint)
{
_modelHint = hint;
}
#endregion
#region 模型配置
/// <summary>
/// 客户端模型配置类
/// </summary>
public class ClientModelConfig
{
public string? Icon { get; set; }
public string? ModelHint { get; set; }
public string? Title { get; set; }
public Dictionary<string, Dictionary<string, int>>? LayerDir { get; set; }
}
/// <summary>
/// 客户端模型配置字典
/// </summary>
private static readonly Dictionary<string, ClientModelConfig> _clientModelConfigs = new()
{
{ "RUE", new ClientModelConfig { Icon = "icon-ue2" } },
{ "UE", new ClientModelConfig { Icon = "icon-ue" } },
{ "PROBE", new ClientModelConfig() },
{ "ENB", new ClientModelConfig { Icon = "icon-air", ModelHint = "enb|gnb|bbu", Title = "RAN" } },
{ "N3IWF", new ClientModelConfig { Icon = "icon-air", ModelHint = "n3iwf", Title = "N3IWF" } },
{ "MME", new ClientModelConfig { Icon = "icon-server", ModelHint = "epc|mme|amf", Title = "CN" } },
{ "MBMSGW", new ClientModelConfig { Icon = "icon-download", ModelHint = "mbms" } },
{ "IMS", new ClientModelConfig { Icon = "icon-dial" } },
{ "MONITOR", new ClientModelConfig { Icon = "icon-monitor" } },
{ "LICENSE", new ClientModelConfig { Icon = "icon-file" } }
};
#endregion
2 months ago
}
/// <summary>
/// 客户端日志配置
/// </summary>
public class ClientLogsConfig
{
/// <summary>
/// 日志层配置
/// </summary>
public Dictionary<string, LogLayerConfig> Layers { get; set; } = new();
/// <summary>
/// 是否启用信号日志
/// </summary>
public bool? Signal { get; set; }
/// <summary>
/// 是否启用控制信道日志
/// </summary>
public bool? Cch { get; set; }
// 允许其他未明确定义的属性
[JsonExtensionData]
public Dictionary<string, object>? ExtensionData { get; set; }
}
2 months ago
/// <summary>
/// 客户端配置
/// </summary>
public class ClientConfig
{
/// <summary>
/// 客户端名称
/// </summary>
2 months ago
public string Name { get; set; } = string.Empty;
/// <summary>
/// 服务器地址
/// </summary>
public string Address { get; set; } = string.Empty;
/// <summary>
/// 是否启用
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// 密码
/// </summary>
public string Password { get; set; } = string.Empty;
/// <summary>
/// 重连延迟(毫秒)
/// </summary>
public int ReconnectDelay { get; set; } = 5000;
/// <summary>
/// 是否启用SSL
/// </summary>
2 months ago
public bool Ssl { get; set; }
/// <summary>
/// 日志配置
/// </summary>
public ClientLogsConfig Logs { get; set; } = new();
/// <summary>
/// 是否暂停
/// </summary>
2 months ago
public bool Pause { get; set; }
/// <summary>
/// 是否只读
/// </summary>
2 months ago
public bool Readonly { get; set; }
/// <summary>
/// 是否跳过日志菜单
/// </summary>
2 months ago
public bool SkipLogMenu { get; set; }
/// <summary>
/// 是否锁定
/// </summary>
2 months ago
public bool Locked { get; set; }
/// <summary>
/// 是否激活
/// </summary>
2 months ago
public bool Active { get; set; }
/// <summary>
/// 模型
/// </summary>
public string? Model { get; set; }
2 months ago
}
/// <summary>
/// 客户端状态
/// </summary>
public enum ClientState
{
Stop,
Start,
Loading,
Connecting,
Connected,
Error,
Destroy
}
/// <summary>
/// 帧信息
/// </summary>
public class FrameInfo
{
public int Last { get; set; }
public int Hfn { get; set; }
public long Timestamp { get; set; } = -1;
}
/// <summary>
/// UE信息
/// </summary>
public class UEInfo
{
public int UeId { get; set; }
public string? Imsi { get; set; }
public string? Imei { get; set; }
public object? Caps { get; set; }
}