using Opc.Ua; using Serilog; namespace ZB.MOM.WW.OtOpcUa.Client.Shared.Helpers; /// /// The client-side HistoryRead paging loop shared by the raw and modified reads. Continuation pages are /// READ (releaseContinuationPoints = false) while the caller still wants values; a read stopped by the /// caller's own cap while the server still holds a cursor releases that cursor so it does not occupy one of the /// session's continuation-point slots until eviction. /// internal static class HistoryReadPaging { private static readonly ILogger Logger = Log.ForContext(typeof(HistoryReadPaging)); /// Reads (or, with , releases) one page. /// The cursor to resume, or for the first page. /// to release the cursor instead of reading. /// The caller's cancellation token. /// The per-node result, or when the response carried none. public delegate Task PageReader( byte[]? continuationPoint, bool releaseContinuationPoints, CancellationToken ct); /// Reads pages until the server finishes or the caller's cap is reached. /// The element type extracted from each page. /// Issues one HistoryRead for the single node. /// Extracts the page's elements from a result. /// The caller's cap; <= 0 means no cap (follow every continuation point). /// The node being read, for refusal messages. /// The caller's cancellation token. /// Every element read. /// A page was refused (see ). public static async Task> ReadAllAsync( PageReader readPage, Func> extract, int maxValues, NodeId nodeId, CancellationToken ct) { var all = new List(); byte[]? continuationPoint = null; while (true) { var result = await readPage(continuationPoint, false, ct).ConfigureAwait(false); if (result == null) return all; HistoryReadStatus.ThrowIfRefused(result.StatusCode, nodeId); all.AddRange(extract(result)); continuationPoint = result.ContinuationPoint; if (continuationPoint is not { Length: > 0 }) return all; if (maxValues > 0 && all.Count >= maxValues) break; } // Stopped by the client's own cap: the server still holds a cursor for this session. Release it; failing to // is not worth failing a read whose values are already in hand. try { await readPage(continuationPoint, true, ct).ConfigureAwait(false); } catch (Exception ex) when (ex is not OperationCanceledException) { Logger.Warning(ex, "Releasing the HistoryRead continuation point for {NodeId} failed", nodeId); } return all; } }