using System.Collections.Immutable; using System.Text.RegularExpressions; using Newtonsoft.Json; namespace LTEMvcApp.Models; /// /// LTE客户端类 - 对应JavaScript中的lte.client对象 /// public class LTEClient { #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 /// public int ClientId { get; set; } /// /// 客户端名称 /// public string Name { get; set; } = string.Empty; /// /// 客户端配置 /// public ClientConfig Config { get; set; } = new(); /// /// 客户端状态 /// public ClientState State { get; set; } = ClientState.Stop; /// /// 版本信息 /// public string? Version { get; set; } /// /// 许可证信息 /// public string? License { get; set; } /// /// 模型类型 /// public string? Model { get; set; } /// /// 是否有信号记录 /// public bool HasSignalRecord { get; set; } /// /// 头信息 /// public string[]? Headers { get; set; } #endregion #region 日志相关属性 /// /// 日志列表 /// public List Logs { get; set; } = new(); /// /// 日志数量 /// public int LogCount => Logs.Count; /// /// 是否已过滤 /// public bool Filtered { get; set; } /// /// 重置标志 /// public bool ResetFlag { get; set; } #endregion #region 解析器状态 /// /// 最后HARQ信息 /// public Dictionary> LastHarq { get; set; } = new(); /// /// 最后NB HARQ信息 /// public Dictionary>> LastNbHarq { get; set; } = new(); /// /// 帧信息 /// public FrameInfo Frame { get; set; } = new(); /// /// TMSI到UE ID的映射 /// public Dictionary TmsiToUeId { get; set; } = new(); /// /// RNTI到UE ID的映射 /// public Dictionary RntiToUeId { get; set; } = new(); /// /// UE列表 /// public Dictionary UeList { get; set; } = new(); /// /// 最后时间戳 /// public long LastTimestamp { get; set; } /// /// 时间戳偏移 /// public long TimestampOffset { get; set; } /// /// 最后小区 /// public int? LastCell { get; set; } #endregion #region 功能标志 /// /// 是否有小区信息 /// public bool HasCell { get; set; } /// /// 是否有物理层信息 /// public bool HasPhy { get; set; } /// /// 是否有数据 /// public bool HasData { get; set; } /// /// 是否有RNTI /// public bool HasRnti { get; set; } /// /// 是否有资源块 /// public bool HasRb { get; set; } #endregion #region 参数和组件 #region private int PDSCH; private int PDCCH; private int EPDCCH; private int PUSCH; private int PUCCH; private int NPDSCH; private int NPUSCH; private int NPBCH; private int BCCH_NR; private int ID_HINTS; private int SRS; private int CSI; private int SIM_EVENT; private int IP_SIM; #endregion /// /// 参数信息 /// public Dictionary Parameters { get; set; } = new(); /// /// 组件列表 /// public Dictionary Components { get; set; } = new(); #endregion #region 依赖注入 /// /// 日志管理器 /// private readonly LogsManager _lteLogs; /// /// 获取日志管理器实例 /// public LogsManager LogsManager => _lteLogs; #endregion #region 构造函数 public LTEClient(ClientConfig config, LogsManager logsManager) { Config = config; Name = config.Name; ClientId = GenerateClientId(); _lteLogs = logsManager; ResetParserState(); } /// /// 使用名称创建客户端 /// /// 客户端名称 /// 日志管理器 public LTEClient(string name, LogsManager logsManager) { Config = new ClientConfig { Name = name, Enabled = true }; Name = name; ClientId = GenerateClientId(); _lteLogs = logsManager; ResetParserState(); } #endregion #region 客户端控制方法 /// /// 启动客户端 /// public void Start() { SetState(ClientState.Start); } /// /// 停止客户端 /// public void Stop() { SetState(ClientState.Stop); } /// /// 销毁客户端 /// public void Destroy() { SetState(ClientState.Destroy); } #endregion #region 日志管理方法 /// /// 重置日志 /// public void ResetLogs() { if (LogCount > 0) { ResetFlag = true; Logs.Clear(); ResetParserState(); HasCell = false; HasPhy = false; HasData = false; HasRnti = false; HasRb = false; HasSignalRecord = false; } } /// /// 添加日志 /// 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(); } #endregion #region 工具方法 /// /// 设置头信息 - 对应JavaScript的setHeaders方法 /// /// 头信息数组 public void SetHeaders(string[] headers) { // 保存头信息 Headers = headers; var cells = new List>(); for (int i = 0; i < headers.Length; i++) { var header = headers[i]; Match? info; // 检查版本信息 info = Regex.Match(header, @"lte(\w+) version ([\d-]+)", RegexOptions.IgnoreCase); if (info.Success) { if (string.IsNullOrEmpty(Config.Model)) { var model = info.Groups[1].Value.ToUpper(); if (_clientModelConfigs.ContainsKey(model)) { SetModel(model); } } Version = info.Groups[2].Value; } // 检查许可证信息 else if (Regex.IsMatch(header, @"Licensed to")) { License = header; } // 检查元数据信息 else if ((info = Regex.Match(header, @"Metadata:(.+)$")).Success) { try { var metadata = JsonConvert.DeserializeObject>(info.Groups[1].Value); if (metadata.TryGetValue("model", out var model)) { SetModel(model.ToString()); } } catch { // 忽略JSON解析错误 } } // 检查RAN ID信息 else if ((info = Regex.Match(header, @"(global_(ran_node|enb)_id)=([\d\.]+)")).Success) { SetModel("ENB"); SetRanId(info.Groups[3].Value); } // 检查小区信息 else if ((info = Regex.Match(header, @"Cell 0x(\d+): (.*)")).Success) { var cell = new Dictionary { ["cell_id"] = int.Parse(info.Groups[1].Value, System.Globalization.NumberStyles.HexNumber), ["sib1Decoded"] = true, ["sib2Decoded"] = true }; var list = info.Groups[2].Value.Split(' '); foreach (var param in list) { var parts = param.Split('='); if (parts.Length == 2) { cell[parts[0]] = HeaderCellParam(parts[0], parts[1]); } } cells.Add(cell); } // 检查小区参数信息 else if (cells.Count > 0) { info = Regex.Match(header, @"([UD]L): (.*)"); if (info.Success) { var cell = cells[cells.Count - 1]; var dir = info.Groups[1].Value; var list = info.Groups[2].Value.Split(' '); foreach (var param in list) { var parts = param.Split('='); if (parts.Length == 2) { cell[parts[0]] = HeaderCellParam(parts[0], parts[1]); } } } } } // 添加所有小区 foreach (var cell in cells) { var cellId = (int)cell["cell_id"]; AddCell(cellId, cell); } // 通知日志管理器刷新客户端网格 _lteLogs.RefreshClientGrid(); } /// /// 处理头信息中的小区参数 - 对应JavaScript的_headerCellParam方法 /// /// 参数名 /// 参数值 /// 处理后的值 private object HeaderCellParam(string param, string value) { switch (param) { case "br_dl_sf_bitmap": case "nb_dl_sf_bitmap": case "label": return value; default: if (int.TryParse(value, out var intValue)) return intValue; return value; } } /// /// 添加小区 - 对应JavaScript的_addCell方法 /// /// 小区ID /// 小区配置 /// 小区对象 public LTECell AddCell(int id, Dictionary config) { var cell = new LTECell(id, config); if (Parameters.ContainsKey("cells") && Parameters["cells"] is Dictionary cells) { cells[id.ToString()] = cell; } else { Parameters["cells"] = new Dictionary { [id.ToString()] = cell }; } return cell; } /// /// 设置RAN ID - 对应JavaScript的setRanId方法 /// /// RAN ID public void SetRanId(string ranId) { RanIds.Add(ranId); // 处理RAN ID链接(简化实现,因为C#版本没有clientList) // 在实际应用中,这里可能需要与其他客户端进行协调 } /// /// 初始化模型猜测 /// public void LogModelGuessInit() { if (string.IsNullOrEmpty(Config.Model)) { // 尝试所有模型 var modelList = new List { "RUE", "UE", "PROBE", "ENB", "N3IWF", "MBMSGW", "MME", "IMS", "LICENSE", "MONITOR" }; if (!TryModelHint(modelList)) { SetModel("MME"); // 默认模型 } // 初始化模型猜测字典 _modelGuess = new Dictionary(); var layerConfig = _lteLogs.GetLayerConfig(); foreach (var layer in layerConfig.Keys) { _modelGuess[layer] = false; } } } /// /// 模型猜测字典 /// private Dictionary? _modelGuess; /// /// 模型猜测 /// /// 日志列表 public void LogModelGuess(List> logs) { 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; } } // 从 LogsManager 获取层配置 var layerConfig = _lteLogs.GetLayerConfig(); var modelList = new List { "RUE", "UE", "PROBE", "ENB", "N3IWF", "MBMSGW", "MME", "IMS", "LICENSE", "MONITOR" }; var availableModels = new List(modelList); // 根据层配置过滤模型(与 JavaScript 版本保持一致) foreach (var layer in modelGuess.Keys) { if (!modelGuess[layer]) continue; if (layerConfig.TryGetValue(layer, out var config)) { // 使用反射获取 dir 属性 var configType = config.GetType(); var dirProperty = configType.GetProperty("dir"); if (dirProperty != null) { var dir = dirProperty.GetValue(config) as Dictionary; if (dir != null) { // 移除不支持的模型(与 JavaScript 版本逻辑一致) for (int j = 0; j < modelList.Count; j++) { var model = modelList[j]; if (!dir.ContainsKey(model)) { var idx = availableModels.IndexOf(model); if (idx >= 0) availableModels.RemoveAt(idx); } } } } } // 如果只剩下一个模型,直接设置(与 JavaScript 版本一致) if (availableModels.Count < 2) break; } // 设置模型(与 JavaScript 版本一致) if (availableModels.Count > 0) { if (availableModels.Count == 1) { SetModel(availableModels[0]); } else { // 支持多个模型的情况,传递第一个模型 SetModel(availableModels[0]); } _modelGuess = null; // 清除猜测字典 } } /// /// 方向转换 - 对应JavaScript的_dirConvert方法 /// /// 日志对象 /// 转换后的方向值 public int DirConvert(LTELog log) { // 获取当前层的方向配置 if (_layerDir.TryGetValue(log.Layer, out var layerDir)) { // 根据日志的原始方向获取转换后的方向 if (layerDir.TryGetValue(log.Direction.ToString(), out var convertedDir)) { return convertedDir; } } return 0; // 默认返回0(DIR_NONE) } /// /// 字符串转ID - 委托给LogsManager /// public int StringToId(string str) { return _lteLogs.String2Id(str); } /// /// ID转字符串 - 委托给LogsManager /// public string IdToString(int id) { return _lteLogs.Id2String(id); } #endregion #region 日志解析方法 /// /// 解析日志列表 - 对应JavaScript的_logListParse方法 /// /// 日志列表 /// 是否为WebSocket消息 public void ParseLogList(List 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(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层解析方法 /// /// 解析PHY日志 /// 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; } /// /// 解析PHY参数 /// 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; } } /// /// 处理PHY日志 /// 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; } } } /// /// 处理HARQ日志 /// 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(); } LastHarq[cell][key] = log; } } #endregion #region 其他层解析方法 /// /// 解析小区ID /// 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; } } } /// /// 设置日志信息 /// private bool SetLogInfo(LTELog log, string info) { if (string.IsNullOrEmpty(info) || info == "?") return false; log.Info = StringToId(info); return true; } /// /// 设置相同UE /// private void SetSameUe(LTELog log, int ueId) { log.UeId = ueId; if (!UeList.ContainsKey(ueId)) { UeList[ueId] = new UEInfo { UeId = ueId }; } } /// /// 处理RRC日志 /// 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); } } /// /// 处理NAS日志 /// private void ProcessNasLog(LTELog log) { // NAS日志处理逻辑 } /// /// 解析MAC日志 /// private void ParseMacLog(LTELog log) { // MAC日志解析逻辑 } /// /// 处理提示信息 /// private void ProcessHints(LTELog log, Dictionary? config = null) { // 处理提示信息逻辑 } /// /// 设置TMSI /// 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; } } /// /// 设置RNTI /// 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; } } #endregion #region 私有辅助方法 /// /// 设置状态 /// 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; } } } /// /// 重置解析器状态 /// private void ResetParserState() { LastHarq.Clear(); LastNbHarq.Clear(); Frame = new FrameInfo(); TmsiToUeId.Clear(); RntiToUeId.Clear(); UeList.Clear(); LastTimestamp = 0; TimestampOffset = 0; LastCell = null; PDSCH = _lteLogs.String2Id("PDSCH"); PDCCH = _lteLogs.String2Id("PDCCH"); EPDCCH = _lteLogs.String2Id("EPDCCH"); PUSCH = _lteLogs.String2Id("PUSCH"); PUCCH = _lteLogs.String2Id("PUCCH"); NPDSCH = _lteLogs.String2Id("NPDSCH"); NPUSCH = _lteLogs.String2Id("NPUSCH"); NPBCH = _lteLogs.String2Id("NPBCH"); BCCH_NR = _lteLogs.String2Id("BCCH-NR"); ID_HINTS = _lteLogs.String2Id("HINTS"); SRS = _lteLogs.String2Id("SRS"); CSI = _lteLogs.String2Id("CSI"); SIM_EVENT = _lteLogs.String2Id("SIM-Event"); IP_SIM = _lteLogs.String2Id("IP-SIM"); // 注意:LogsManager 的字符串ID缓存是全局的,不需要重置 // 这样可以保持字符串ID的一致性 } /// /// 生成客户端ID /// private static int GenerateClientId() { return Interlocked.Increment(ref _clientIdCounter); } /// /// 生成日志ID /// private static int GenerateLogId() { return Interlocked.Increment(ref _logIdCounter); } #endregion #region 静态字段 private static int _clientIdCounter = 0; private static int _logIdCounter = 0; #endregion #region 模型管理方法 /// /// 模型提示信息 /// private string? _modelHint; /// /// 传输方向 /// public int TxDir { get; set; } /// /// 接收方向 /// public int RxDir { get; set; } /// /// RAN ID列表 /// public List RanIds { get; set; } = new(); /// /// 层方向配置 /// private Dictionary> _layerDir = new(); /// /// 尝试模型提示 /// /// 模型列表 /// 是否成功设置模型 public bool TryModelHint(List 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; } /// /// 设置模型 /// /// 模型名称 /// 是否强制设置 /// 是否成功设置 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>(); } else { _layerDir.Clear(); } // 初始化层方向配置 InitializeLayerDirectionConfig(model); // 通知日志管理器更新网格列 _lteLogs.GridColumnsUpdate(); return true; } /// /// 设置模型提示 /// /// 提示信息 public void SetModelHint(string hint) { _modelHint = hint; } /// /// 初始化层方向配置 - 对应JavaScript版本的层方向配置初始化 /// /// 模型名称 private void InitializeLayerDirectionConfig(string? model) { // 获取层配置 var layerConfig = _lteLogs.GetLayerConfig(); // 为每个层初始化默认方向配置 foreach (var layer in layerConfig.Keys) { if (!_layerDir.ContainsKey(layer)) { _layerDir[layer] = new Dictionary { { "UL", LogsManager.DIR_UL }, { "DL", LogsManager.DIR_DL }, { "FROM", LogsManager.DIR_DL }, { "TO", LogsManager.DIR_UL }, { "-", LogsManager.DIR_NONE } }; } } // 根据模型设置特殊的方向配置(与 JavaScript 版本保持一致) switch (model?.ToUpper()) { case "MME": // MME 模型特殊配置 foreach (var layer in layerConfig.Keys) { if (layer != "IMS" && layer != "CX" && layer != "RX" && layer != "S6" && layer != "S13" && layer != "SGsAP" && layer != "SBcAP" && layer != "N8" && layer != "N12" && layer != "N13" && layer != "N17" && layer != "N50" && layer != "MMS" && layer != "HTTP2" && layer != "LCSAP" && layer != "NL1" && layer != "GTPC") { if (_layerDir.ContainsKey(layer)) { _layerDir[layer]["FROM"] = LogsManager.DIR_UL; _layerDir[layer]["TO"] = LogsManager.DIR_DL; } } } break; case "IMS": // IMS 模型特殊配置 foreach (var layer in layerConfig.Keys) { if (_layerDir.ContainsKey(layer)) { _layerDir[layer]["FROM"] = LogsManager.DIR_UL; _layerDir[layer]["TO"] = LogsManager.DIR_DL; } } break; case "N3IWF": // N3IWF 模型特殊配置 if (_layerDir.ContainsKey("IKEV2")) { _layerDir["IKEV2"]["FROM"] = LogsManager.DIR_UL; _layerDir["IKEV2"]["TO"] = LogsManager.DIR_DL; } if (_layerDir.ContainsKey("IPSEC")) { _layerDir["IPSEC"]["FROM"] = LogsManager.DIR_UL; _layerDir["IPSEC"]["TO"] = LogsManager.DIR_DL; } break; case "ENB": // ENB 模型特殊配置 if (_layerDir.ContainsKey("TRX")) { _layerDir["TRX"]["FROM"] = LogsManager.DIR_UL; _layerDir["TRX"]["TO"] = LogsManager.DIR_DL; } break; case "UE": // UE 模型特殊配置 if (_layerDir.ContainsKey("GTPU")) { _layerDir["GTPU"]["FROM"] = LogsManager.DIR_UL; _layerDir["GTPU"]["TO"] = LogsManager.DIR_DL; } break; } } #endregion #region 模型配置 /// /// 客户端模型配置类 /// public class ClientModelConfig { public string? Icon { get; set; } public string? ModelHint { get; set; } public string? Title { get; set; } public Dictionary>? LayerDir { get; set; } } /// /// 客户端模型配置字典 /// private static readonly Dictionary _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 } /// /// 客户端日志配置 /// public class ClientLogsConfig { /// /// 日志层配置 /// public Dictionary Layers { get; set; } = new(); /// /// 是否启用信号日志 /// public bool? Signal { get; set; } /// /// 是否启用控制信道日志 /// public bool? Cch { get; set; } // 允许其他未明确定义的属性 [JsonExtensionData] public Dictionary? ExtensionData { get; set; } } /// /// 客户端配置 /// public class ClientConfig { /// /// 客户端名称 /// public string Name { get; set; } = string.Empty; /// /// 服务器地址 /// public string Address { get; set; } = string.Empty; /// /// 是否启用 /// public bool Enabled { get; set; } /// /// 密码 /// public string Password { get; set; } = string.Empty; /// /// 重连延迟(毫秒) /// public int ReconnectDelay { get; set; } = 5000; /// /// 是否启用SSL /// public bool Ssl { get; set; } /// /// 日志配置 /// public ClientLogsConfig Logs { get; set; } = new(); /// /// 是否暂停 /// public bool Pause { get; set; } /// /// 是否只读 /// public bool Readonly { get; set; } /// /// 是否跳过日志菜单 /// public bool SkipLogMenu { get; set; } /// /// 是否锁定 /// public bool Locked { get; set; } /// /// 是否激活 /// public bool Active { get; set; } /// /// 模型 /// public string? Model { get; set; } } /// /// 客户端状态 /// public enum ClientState { Stop, Start, Loading, Connecting, Connected, Error, Destroy } /// /// 帧信息 /// public class FrameInfo { public int Last { get; set; } public int Hfn { get; set; } public long Timestamp { get; set; } = -1; } /// /// UE信息 /// public class UEInfo { public int UeId { get; set; } public string? Imsi { get; set; } public string? Imei { get; set; } public object? Caps { get; set; } } /// /// LTE小区类 - 对应JavaScript的LTECell /// public class LTECell { /// /// 小区ID /// public int CellId { get; set; } /// /// 无线接入技术 /// public string Rat { get; set; } = "lte"; /// /// 下行资源块数量 /// public int NRbDl { get; set; } = 0; /// /// 上行资源块数量 /// public int NRbUl { get; set; } = 0; /// /// 下行多用户 /// public int DlMu { get; set; } = 0; /// /// 上行多用户 /// public int UlMu { get; set; } = 0; /// /// 模式 /// public string Mode { get; set; } = "FDD"; /// /// 物理小区ID /// public string Pci { get; set; } = "?"; /// /// 循环前缀 /// public int CyclicPrefix { get; set; } = 0; /// /// PUCCH偏移 /// public int DeltaPucchShift { get; set; } = 2; /// /// CQI资源块数量 /// public int NRbCqi { get; set; } = 0; /// /// 下行子帧位图 /// public string? BrDlSfBitmap { get; set; } /// /// NB下行子帧位图 /// public string? NbDlSfBitmap { get; set; } /// /// PRACH频率偏移 /// public int PrachFreqOffset { get; set; } = 2; /// /// PRACH配置索引 /// public int PrachConfigIndex { get; set; } = 2; /// /// 标签 /// public string? Label { get; set; } /// /// SIB1是否已解码 /// public bool Sib1Decoded { get; set; } = true; /// /// SIB2是否已解码 /// public bool Sib2Decoded { get; set; } = true; /// /// 其他参数 /// public Dictionary Parameters { get; set; } = new(); /// /// 构造函数 /// /// 小区ID /// 配置字典 public LTECell(int cellId, Dictionary config) { CellId = cellId; // 从配置字典设置属性 foreach (var kvp in config) { SetProperty(kvp.Key, kvp.Value); } } /// /// 设置属性 /// /// 属性名 /// 属性值 private void SetProperty(string name, object value) { switch (name) { case "cell_id": CellId = Convert.ToInt32(value); break; case "rat": Rat = value.ToString() ?? "lte"; break; case "n_rb_dl": NRbDl = Convert.ToInt32(value); break; case "n_rb_ul": NRbUl = Convert.ToInt32(value); break; case "dl_mu": DlMu = Convert.ToInt32(value); break; case "ul_mu": UlMu = Convert.ToInt32(value); break; case "mode": Mode = value.ToString() ?? "FDD"; break; case "pci": Pci = value.ToString() ?? "?"; break; case "cyclic_prefix": CyclicPrefix = Convert.ToInt32(value); break; case "delta_pucch_shift": DeltaPucchShift = Convert.ToInt32(value); break; case "n_rb_cqi": NRbCqi = Convert.ToInt32(value); break; case "br_dl_sf_bitmap": BrDlSfBitmap = value.ToString(); break; case "nb_dl_sf_bitmap": NbDlSfBitmap = value.ToString(); break; case "prach_freq_offset": PrachFreqOffset = Convert.ToInt32(value); break; case "prach_config_index": PrachConfigIndex = Convert.ToInt32(value); break; case "label": Label = value.ToString(); break; case "sib1Decoded": Sib1Decoded = Convert.ToBoolean(value); break; case "sib2Decoded": Sib2Decoded = Convert.ToBoolean(value); break; default: // 其他参数存储在Parameters字典中 Parameters[name] = value; break; } } /// /// 设置RAT类型 /// /// RAT类型 /// 是否强制设置 public void SetRat(string rat, bool force = false) { if (rat == Rat && !force) return; switch (rat.ToLower()) { case "nbiot": NRbDl = 2; NRbUl = 12; break; } Rat = rat; } /// /// 获取时隙数量 /// /// 是否为上行 /// 时隙数量 public int GetSlotCount(bool ul) { return 10 * (1 << (ul ? UlMu : DlMu)); } /// /// 获取下行比例 /// /// 下行比例 public double GetDLRatio() { if (Mode != "TDD") return 1; var ratios = new[] { 4.0 / 10, 6.0 / 10, 8.0 / 10, 7.0 / 10, 8.0 / 10, 9.0 / 10, 5.0 / 10 }; if (Parameters.TryGetValue("uldl_config", out var config) && config is int uldlConfig) { return uldlConfig < ratios.Length ? ratios[uldlConfig] : 1; } return 1; } /// /// 获取小区名称 /// /// 小区名称 public string GetName() { return !string.IsNullOrEmpty(Label) ? Label : $"#{CellId}"; } }