diff --git a/src/ZB.MOM.WW.ScadaBridge.Transport/Import/LineDiffer.cs b/src/ZB.MOM.WW.ScadaBridge.Transport/Import/LineDiffer.cs
index 88a830ee..beb4f98a 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Transport/Import/LineDiffer.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Transport/Import/LineDiffer.cs
@@ -51,6 +51,16 @@ public sealed record LineDiffResult(
///
public static class LineDiffer
{
+ ///
+ /// Hard ceiling on the combined old+new line count fed to the Myers algorithm. Myers
+ /// records a V frontier snapshot per edit distance d (up to n + m rounds),
+ /// each snapshot sized O(n + m), so a fully-divergent diff is O((n + m)^2)
+ /// transient memory. To keep a bloated or crafted bundle from OOM-ing the active central
+ /// node during an import preview, oversized inputs short-circuit to a summary-only result
+ /// (the same shape the output cap emits) instead of running the trace.
+ ///
+ private const int MaxInputLines = 4000;
+
///
/// Computes a minimal per-line diff between and
/// (either may be , treated as empty).
@@ -68,6 +78,13 @@ public static class LineDiffer
string[] oldLines = SplitLines(oldText);
string[] newLines = SplitLines(newText);
+ // Input-size guard: bound the ALGORITHM INPUT (distinct from the output cap below) so an
+ // oversized diff degrades to a summary instead of allocating O((n + m)^2) Myers snapshots.
+ if (oldLines.Length + newLines.Length > MaxInputLines)
+ {
+ return SummaryOnlyResult(oldLines.Length, newLines.Length);
+ }
+
IReadOnlyList full = BuildDiff(oldLines, newLines, out int added, out int removed);
bool truncated = full.Count > maxLines;
@@ -91,6 +108,15 @@ public static class LineDiffer
return new LineDiffResult(lines, truncated, added, removed);
}
+ ///
+ /// Builds the truncated summary-only shape returned when the diff cannot be materialized in
+ /// full — no hunks, set, and the add/remove totals
+ /// approximated as a full replacement (every new line added, every old line removed). Used
+ /// by the input-size guard so a diff too large to run cheaply still reports its magnitude.
+ ///
+ private static LineDiffResult SummaryOnlyResult(int oldCount, int newCount)
+ => new([], Truncated: true, AddedCount: newCount, RemovedCount: oldCount);
+
///
/// Normalizes line endings (\r\n and lone \r to \n) and splits on \n.
/// A or empty input yields an empty array (no lines), so the diff of two
diff --git a/tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/Import/LineDifferTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/Import/LineDifferTests.cs
index fb84625e..cd655b05 100644
--- a/tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/Import/LineDifferTests.cs
+++ b/tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/Import/LineDifferTests.cs
@@ -1,3 +1,5 @@
+using System.Diagnostics;
+
using ZB.MOM.WW.ScadaBridge.Transport.Import;
namespace ZB.MOM.WW.ScadaBridge.Transport.Tests.Import;
@@ -189,4 +191,29 @@ public sealed class LineDifferTests
Assert.Equal(0, result.AddedCount);
Assert.Equal(0, result.RemovedCount);
}
+
+ [Fact]
+ public void OversizedInputs_ZeroCommonLines_DegradesToSummary_WithoutRunningMyers()
+ {
+ // Two 10,000-line inputs with zero common lines. Running the Myers trace here would
+ // record ~20,000 V-snapshots of ~160 KB each (multi-GB transient allocation), so the
+ // input-size guard must short-circuit to a summary-only result long before that.
+ var oldText = string.Join("\n", Enumerable.Range(1, 10_000).Select(i => $"old-line-{i}"));
+ var newText = string.Join("\n", Enumerable.Range(1, 10_000).Select(i => $"new-line-{i}"));
+
+ var sw = Stopwatch.StartNew();
+ var result = LineDiffer.Diff(oldText, newText);
+ sw.Stop();
+
+ // Summary-only shape: same as the output cap — Truncated, full totals, no hunks.
+ Assert.True(result.Truncated);
+ Assert.Empty(result.Lines);
+ Assert.Equal(10_000, result.AddedCount);
+ Assert.Equal(10_000, result.RemovedCount);
+
+ // Must return within a small time budget; pathological slowness is a failure.
+ Assert.True(
+ sw.Elapsed < TimeSpan.FromSeconds(2),
+ $"Diff of oversized inputs took {sw.Elapsed.TotalSeconds:F1}s — the input-size guard did not short-circuit.");
+ }
}