From e7f4f655e5ad0cfeb4933d464fd89a4e09a3d40f Mon Sep 17 00:00:00 2001 From: Steve Pinkham Date: Wed, 11 Nov 2026 03:22:44 -0500 Subject: [PATCH] C4FM: bounds check the start of the buffer in the sync score (#2622) A lagging sync detect on the first symbol after a buffer reload, followed by a coarse timing search that steps all the way left, scores a candidate whose first sync symbol is one sample before the buffer start. score() only checked the end of the buffer, so it threw ArrayIndexOutOfBoundsException: Index -1. Co-authored-by: Claude Opus 5.5 --- .../dsp/c4fm/C4FMSymbolProcessor.java | 2 +- .../c4fm/C4FMSymbolProcessorScoreTest.java | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 src/test/java/io/github/dsheirer/dsp/c4fm/C4FMSymbolProcessorScoreTest.java diff --git a/src/main/java/io/github/dsheirer/dsp/c4fm/C4FMSymbolProcessor.java b/src/main/java/io/github/dsheirer/dsp/c4fm/C4FMSymbolProcessor.java index 4bf5c346..b23cfcaf 100644 --- a/src/main/java/io/github/dsheirer/dsp/c4fm/C4FMSymbolProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/c4fm/C4FMSymbolProcessor.java @@ -1313,7 +1313,7 @@ public abstract class C4FMSymbolProcessor for(int x = 0; x < patternSymbols.length; x++) { - if(bufferPointer < maxPointer) + if(bufferPointer >= 0 && bufferPointer < maxPointer) { softSymbol = interpolate(mBuffer[bufferPointer], mBuffer[bufferPointer + 1], fractional) * gain + balance; } diff --git a/src/test/java/io/github/dsheirer/dsp/c4fm/C4FMSymbolProcessorScoreTest.java b/src/test/java/io/github/dsheirer/dsp/c4fm/C4FMSymbolProcessorScoreTest.java new file mode 100644 index 00000000..3cbe27d6 --- /dev/null +++ b/src/test/java/io/github/dsheirer/dsp/c4fm/C4FMSymbolProcessorScoreTest.java @@ -0,0 +1,50 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2026 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.c4fm; + +import io.github.dsheirer.module.decode.p25.phase1.P25P1SymbolProcessorC4FM; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +/** + * JUnit tests for the sync correlation score in the C4FM symbol processor. + */ +public class C4FMSymbolProcessorScoreTest +{ + /** + * A lagging sync detect on the first symbol after a buffer reload, followed by a coarse timing search that steps + * all the way left, scores a candidate whose first sync symbol starts just before the start of the buffer. + */ + @Test + public void scoreBeforeBufferStart() + { + float samplesPerSymbol = 25000f / 4800f; + P25P1SymbolProcessorC4FM processor = new P25P1SymbolProcessorC4FM(null, null); + processor.setSamplesPerSymbol(samplesPerSymbol); + //Sync workspace, sized as in setSamplesPerSymbol(), plus a 1024 sample buffer + int syncWorkspaceLength = (int)Math.ceil((processor.mSyncDetector.getDibitLength() + 1) * samplesPerSymbol); + processor.mBuffer = new float[syncWorkspaceLength + 1024]; + + //Offset to the final sync symbol that puts the first sync symbol at buffer index -0.3 + double offset = (processor.mSyncDetector.getDibitLength() - 1) * samplesPerSymbol - 0.3; + assertDoesNotThrow(() -> processor.score(offset, samplesPerSymbol)); + } +}