diff --git a/src/main/java/org/apache/datasketches/frequencies/FrequentLongsSketch.java b/src/main/java/org/apache/datasketches/frequencies/FrequentLongsSketch.java index 81fba6fc8..4d5935d76 100644 --- a/src/main/java/org/apache/datasketches/frequencies/FrequentLongsSketch.java +++ b/src/main/java/org/apache/datasketches/frequencies/FrequentLongsSketch.java @@ -259,6 +259,16 @@ public static FrequentLongsSketch getInstance(final MemorySegment srcSeg) { "Possible Corruption: Empty flag does not match PreLongs: flags " + extractFlags(pre0) + ", preLongs " + preLongs); } + if (lgCurMapSize < LG_MIN_MAP_SIZE) { //Byte 4 + throw new SketchesArgumentException( + "Possible Corruption: LgCurMapSize must be at least " + LG_MIN_MAP_SIZE + ": " + + lgCurMapSize); + } + if (lgCurMapSize > lgMaxMapSize) { //Byte 4 and Byte 3 + throw new SketchesArgumentException( + "Possible Corruption: LgCurMapSize must not exceed LgMaxMapSize: " + + lgCurMapSize + " > " + lgMaxMapSize); + } if (preLongsEq1) { //empty is determined by PreLongs return new FrequentLongsSketch(lgMaxMapSize, LG_MIN_MAP_SIZE); @@ -335,6 +345,21 @@ public static FrequentLongsSketch getInstance(final String string) { "Possible Corruption: Incorrect # of tokens: " + numTokens + ", numActive: " + numActive); } + if (lgCur < LG_MIN_MAP_SIZE) { + throw new SketchesArgumentException( + "Possible Corruption: Map length must be at least " + (1 << LG_MIN_MAP_SIZE) + ": " + + tokens[STR_PREAMBLE_TOKENS + 1]); + } + if (lgCur > lgMax) { + throw new SketchesArgumentException( + "Possible Corruption: Map length must not exceed max map length " + (1L << lgMax) + ": " + + tokens[STR_PREAMBLE_TOKENS + 1]); + } + final long maxActive = (long) ((1L << lgCur) * ReversePurgeLongHashMap.getLoadFactor()); + if ((numActive < 0) || (numActive > maxActive)) { + throw new SketchesArgumentException( + "Possible Corruption: numActive exceeds map capacity: " + numActive); + } final FrequentLongsSketch sketch = new FrequentLongsSketch(lgMax, lgCur); sketch.streamWeight = streamWt; diff --git a/src/test/java/org/apache/datasketches/frequencies/LongsSketchTest.java b/src/test/java/org/apache/datasketches/frequencies/LongsSketchTest.java index 4df537c93..b6527b178 100644 --- a/src/test/java/org/apache/datasketches/frequencies/LongsSketchTest.java +++ b/src/test/java/org/apache/datasketches/frequencies/LongsSketchTest.java @@ -25,6 +25,8 @@ import static org.apache.datasketches.frequencies.DistTest.randomGeometricDist; import static org.apache.datasketches.frequencies.PreambleUtil.FAMILY_BYTE; import static org.apache.datasketches.frequencies.PreambleUtil.FLAGS_BYTE; +import static org.apache.datasketches.frequencies.PreambleUtil.LG_CUR_MAP_SIZE_BYTE; +import static org.apache.datasketches.frequencies.PreambleUtil.LG_MAX_MAP_SIZE_BYTE; import static org.apache.datasketches.frequencies.PreambleUtil.PREAMBLE_LONGS_BYTE; import static org.apache.datasketches.frequencies.PreambleUtil.SER_VER_BYTE; import static org.apache.datasketches.frequencies.PreambleUtil.STREAMLENGTH_LONG; @@ -577,6 +579,68 @@ public void checkStringDeserEmptyFlagWithStreamLength() { FrequentLongsSketch.getInstance(s); } + @Test(expectedExceptions = SketchesArgumentException.class, timeOut = 5000) + public void checkStringDeserMapLengthTooSmall() { + final String s = "1," //serVer + + "10," //FamID + + "3," //lgMaxMapSz + + "5," //Empty Flag + + "0," //stream Len: empty + + "0," //error offset + + "0," //numActive + + "2,"; //curMapLen ... corrupted, below the minimum of 8 + final FrequentLongsSketch fls = FrequentLongsSketch.getInstance(s); + fls.update(1); + fls.update(2); + fls.update(3); + } + + @Test(expectedExceptions = SketchesArgumentException.class, timeOut = 5000) + public void checkStringDeserNumActiveExceedsMapCapacity() { + final StringBuilder sb = new StringBuilder(); + sb.append("1,") //serVer + .append("10,") //FamID + .append("3,") //lgMaxMapSz + .append("0,") //Empty Flag = false + .append("9,") //stream Len + .append("0,") //error offset + .append("9,") //numActive ... corrupted, more than the map can hold + .append("8,"); //curMapLen + for (int i = 1; i <= 9; i++) { sb.append(i).append(",1,"); } + FrequentLongsSketch.getInstance(sb.toString()); + } + + @Test(expectedExceptions = SketchesArgumentException.class, timeOut = 5000) + public void checkStringDeserMapLengthExceedsMax() { + final String s = "1," //serVer + + "10," //FamID + + "3," //lgMaxMapSz + + "5," //Empty Flag + + "0," //stream Len: empty + + "0," //error offset + + "0," //numActive + + "16,"; //curMapLen ... corrupted, above the max of 8 + FrequentLongsSketch.getInstance(s); + } + + @Test(expectedExceptions = SketchesArgumentException.class, timeOut = 5000) + public void checkSegDeserLgCurMapSizeTooSmall() { + final FrequentLongsSketch sk = new FrequentLongsSketch(1 << 8); + sk.update(1); + final byte[] bytes = sk.toByteArray(); + bytes[LG_CUR_MAP_SIZE_BYTE] = (byte) (LG_MIN_MAP_SIZE - 1); //corrupted, below the minimum + FrequentLongsSketch.getInstance(MemorySegment.ofArray(bytes)); + } + + @Test(expectedExceptions = SketchesArgumentException.class, timeOut = 5000) + public void checkSegDeserLgCurMapSizeExceedsLgMax() { + final FrequentLongsSketch sk = new FrequentLongsSketch(1 << 8); + sk.update(1); + final byte[] bytes = sk.toByteArray(); + bytes[LG_CUR_MAP_SIZE_BYTE] = (byte) (bytes[LG_MAX_MAP_SIZE_BYTE] + 1); //corrupted, above max + FrequentLongsSketch.getInstance(MemorySegment.ofArray(bytes)); + } + @Test public void checkGetEpsilon() { assertEquals(FrequentLongsSketch.getEpsilon(1024), 3.5 / 1024, 0.0);